Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

한글라이즈 재제작기 2부

186 views

Published on

스피커덱에서 더 편하게 보실 수 있습니다: https://subl.ee/~gokr1808.2

https://hangulize.org/

〈한글라이즈〉는 외국어 단어를 외래어 표기법에 따라 한글로 옮겨 적어주는 도구입니다. "Espresso"를 "에스프레소"로, "東京"을 "도쿄"로 변환할 수 있죠. 본래 Python으로 구현했던 한글라이즈를 Go로 재구현하면서 겪은 경험과 느낀점을 공유합니다.

고랭코리아 2018년 8월 밋업에서 발표했습니다.

슬라이드셰어에 슬라이드 300장 제한이 있어서 부득이하게 2부로 나눠서 올렸습니다. 보는 데 불편하시겠지만 양해를 부탁드립니다.

- 1부: https://subl.ee/~gokr1808
- 2부: https://subl.ee/~gokr1808.2

Published in: Software
  • Be the first to comment

한글라이즈 재제작기 2부

  1. 1. 이흥섭 2018년 8월 고랭코리아 밋업 재제작기 2부
  2. 2. 슬라이드셰어에 슬라이드 300장 제한이 있어서 부득이하게 2부로 나눠서 올렸습니다. 보는 데 불편하시겠지만 양해를 부탁드립니다.
  3. 3. "^gli{@}" "^^(a|e|i|o|u)" "{~@}sub<cons>" HRE 한글라이즈 정규표현식 방언 한편 한글라이즈에선 패턴 찾아 바꾸기 할 때
  4. 4. "^gli{@}" "^^(a|e|i|o|u)" "{~@}sub<cons>" HRE 한글라이즈 정규표현식 방언 "HRE"로 명명한 한글라이즈 전용 정규표현식 방언을 쓰고 있어요.
  5. 5. "^gli{@}" "^^(a|e|i|o|u)" "{~@}sub<cons>" HRE 한글라이즈 정규표현식 방언 파란색으로 강조한 부분은 RE2 정규식엔 없는 문법인데
  6. 6. HRE를 RE2로 "^gli{@}"HRE /((?:^|s+|{}))()gli(a|e|i|o|u)()/RE2 RE2가 이해할 수 있는 문법으로 번역해서 사용하고 있죠.
  7. 7. HRE 룩어라운드 "foo{bar}" "foo{~bar}" 그 중 구현하기 골치 아팠던 룩어라운드에 대해 얘기해 드리겠습니다.
  8. 8. /^foo/ foo인데 맨 앞 /foo$/ foo인데 맨 뒤 정규식에서 /^/과 /$/가 무엇을 뜻하는진 다들 잘 아시죠?
  9. 9. /^foo/ foo인데 맨 앞 /foo$/ foo인데 맨 뒤 /^foo/는 "foo"인데 문자열 맨 앞에 있는 경우에 매치되고
  10. 10. /^foo/ foo인데 맨 앞 /foo$/ foo인데 맨 뒤 /foo$/는 "foo"인데 문자열 맨 뒤에 있는 경우에 매치됩니다.
  11. 11. "foo{bar}" foo인데 bar 바로 전 "foo{~bar}" foo인데 bar가 아닌 것 바로 전 "{bar}foo" foo인데 bar 바로 뒤 "{~bar}foo" foo인데 bar가 아닌 것 바로 뒤 룩어라운드도 비슷해요.
  12. 12. "foo{bar}" foo인데 bar 바로 전 "foo{~bar}" foo인데 bar가 아닌 것 바로 전 "{bar}foo" foo인데 bar 바로 뒤 "{~bar}foo" foo인데 bar가 아닌 것 바로 뒤 "foo{bar}"라고 쓰면 "bar" 바로 앞에 있는 "foo"에만 매치되고
  13. 13. "foo{bar}" foo인데 bar 바로 전 "foo{~bar}" foo인데 bar가 아닌 것 바로 전 "{bar}foo" foo인데 bar 바로 뒤 "{~bar}foo" foo인데 bar가 아닌 것 바로 뒤 "foo{~bar}"라고 쓰면 "bar"가 아닌 것 바로 앞에 있는 "foo"에만 매치되죠.
  14. 14. "foo{bar}" foo인데 bar 바로 전 "foo{~bar}" foo인데 bar가 아닌 것 바로 전 "{bar}foo" foo인데 bar 바로 뒤 "{~bar}foo" foo인데 bar가 아닌 것 바로 뒤 "{bar}foo", "{~bar}foo" 처럼 "{}"가 앞에 올 수도 있습니다.
  15. 15. "foo{bar}" 포지티브 룩어헤드 "foo{~bar}" 네거티브 룩어헤드 "{bar}foo" 포지티브 룩비하인드 "{~bar}foo" 네거티브 룩비하인드 각각의 문법을 "포지티브 룩어헤드", "네거티브 룩어헤드",
  16. 16. "foo{bar}" 포지티브 룩어헤드 "foo{~bar}" 네거티브 룩어헤드 "{bar}foo" 포지티브 룩비하인드 "{~bar}foo" 네거티브 룩비하인드 "포지티브 룩비하인드", "네거티브 룩비하인드"라고 부릅니다.
  17. 17. ".^" foo foobar foobaz foobarr foofoobar
  18. 18. ".^" foo foobar foobaz foobarr foofoobar 룩어라운드가 실제로 매치되는 모습을 한 번 보겠습니다.
  19. 19. ".^" foo foobar foobaz foobarr foofoobar 위쪽은 패턴이고 아래쪽은 대상 문자열이에요.
  20. 20. ".^" foo foobar foobaz foobarr foofoobar ".^"은 아무 데도 매치되지 않는 모순 패턴이죠.
  21. 21. "foo" foo foobar foobaz foobarr foofoobar 패턴 "foo"를 찾으면 모든 "foo"에 불이 들어옵니다.
  22. 22. "foo(bar)" foo foobar foobaz foobarr foofoobar "foo" 뒤에 "(bar)"를 붙이면 "foobar"에 불이 들어오고
  23. 23. "foo{bar}" foo foobar foobaz foobarr foofoobar "(bar)" 대신 "{bar}"를 써서 포지티브 룩어헤드로 만들면
  24. 24. "foo{bar}" foo foobar foobaz foobarr foofoobar "foobar" 중 "foo"에만 불이 들어옵니다.
  25. 25. "foo{bar}" foo foobar foobaz foobarr foofoobar 이런 식으로 "{}" 속 단어는 매치에 참고는 되지만 실제 결과에 포함되진 않아요.
  26. 26. "foo{~bar}" foo foobar foobaz foobarr foofoobar "{bar}"에 물결을 붙여 "{~bar}"로 바꿔서 네거티브 룩어헤드로 만들면
  27. 27. "foo{~bar}" foo foobar foobaz foobarr foofoobar "foobar"가 아닌 곳의 "foo"에만 불이 들어옵니다.
  28. 28. •어두의 n은 초성 ㄴ으로 치환 •모음 뒤 n은 종성 ㅇ으로 치환 •m 뒤 n은 묵음이니 삭제 룩어라운드 쓰임새 한글라이즈에선 이런 룩어라운드가 꽤 자주 쓰이는데
  29. 29. •어두의 n은 초성 ㄴ으로 치환 •모음 뒤 n은 종성 ㅇ으로 치환 •m 뒤 n은 묵음이니 삭제 룩어라운드 쓰임새 특히 같은 글자라고 해도 앞뒤 글자에 따라 다르게 바꿔야 하는 경우에 주로 쓰고 있습니다.
  30. 30. /foo(?=bar)/ 포지티브 룩어헤드 /foo(?!bar)/ 네거티브 룩어헤드 /(?<=bar)foo/ 포지티브 룩비하인드 /(?<!bar)foo/ 네거티브 룩비하인드 PCRE 룩어라운드 사실 룩어라운드는 HRE에만 있는 게 아니고 PCRE에서 따온 것입니다.
  31. 31. /foo(?=bar)/ 포지티브 룩어헤드 /foo(?!bar)/ 네거티브 룩어헤드 /(?<=bar)foo/ 포지티브 룩비하인드 /(?<!bar)foo/ 네거티브 룩비하인드 PCRE 룩어라운드 여기선 룩어라운드를 /(?=)/, /(?<!)/ 등으로 표현하는데
  32. 32. /foo(?=bar)/ 포지티브 룩어헤드 /foo(?!bar)/ 네거티브 룩어헤드 /(?<=bar)foo/ 포지티브 룩비하인드 /(?<!bar)foo/ 네거티브 룩비하인드 PCRE 룩어라운드 문법이 다소 어려워서 "{}"와 "~"로 좀 더 쉽게 표현했던 거죠.
  33. 33. /foo(?=bar)/ 포지티브 룩어헤드 /foo(?!bar)/ 네거티브 룩어헤드 /(?<=bar)foo/ 포지티브 룩비하인드 /(?<!bar)foo/ 네거티브 룩비하인드 PCRE 룩어라운드 파이썬 re는 PCRE 룩어라운드를 지원해서 옛 한글라이즈에선 "{}" 문법을 단순히
  34. 34. /foo(?=bar)/ 포지티브 룩어헤드 /foo(?!bar)/ 네거티브 룩어헤드 /(?<=bar)foo/ 포지티브 룩비하인드 /(?<!bar)foo/ 네거티브 룩비하인드 PCRE 룩어라운드 PCRE 문법으로 번역하는 것만으로 쉽게 룩어라운드를 구현할 수 있었습니다.
  35. 35. •안전한 정규표현식 구현 •문자열 길이에 대해 선형 시간 •고의적으로 일부 기능 미지원 RE2 by Google 그런데 아까 RE2가 고의적으로 일부 기능을 지원하지 않는다고 말씀 드렸잖아요?
  36. 36. 룩어라운드 사용 불가 (?=bar) 룩어라운드도 그 중 하나였습니다.
  37. 37. 룩어라운드 사용 불가 (?=bar) 옛 한글라이즈에서처럼 단순한 번역으로는 구현할 수 없었죠.
  38. 38. import "github.com/glenn-brown/.../pcre" import "github.com/dlclark/regexp2" 찾아보니 서드파티 라이브러리 중에
  39. 39. import "github.com/glenn-brown/.../pcre" import "github.com/dlclark/regexp2" PCRE나 .NET을 기반으로 한 정규식 엔진도 발견할 수 있었는데
  40. 40. import "github.com/glenn-brown/.../pcre" import "github.com/dlclark/regexp2" 이런 라이브러리를 쓰면 정규식의 모든 기능을 쓸 수 있기야 하지만
  41. 41. 이왕이면 표준 이왕이면 표준 정규식 라이브러리를 쓰고 싶어서
  42. 42. 이왕이면 표준 RE2만으로 룩어라운드를 구현할 대안을 고민해보게 됐습니다.
  43. 43. "foo{bar}" "{bar}foo" 포지티브 룩어라운드 대안 /()(foo)(bar)/ /(bar)(foo)()/
  44. 44. "foo{bar}" "{bar}foo" 포지티브 룩어라운드 대안 /()(foo)(bar)/ /(bar)(foo)()/ 포지티프 룩어라운드의 대안 구현은 간단했어요.
  45. 45. "foo{bar}" "{bar}foo" 포지티브 룩어라운드 대안 /()(foo)(bar)/ /(bar)(foo)()/ 그냥 룩어라운드 부분까지 포함시켜서 매치하고
  46. 46. $2만 취하기 "foo{bar}" "{bar}foo" 포지티브 룩어라운드 대안 /()(foo)(bar)/ /(bar)(foo)()/ $2 그룹만 취해서 앞뒤를 잘라내는 것으로 충분했습니다.
  47. 47. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 반면 네거티브 룩어라운드는 살짝 복잡했어요.
  48. 48. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 일단 RE2 용으로 번역한 정규식에는 "{~}" 부분을 아예 넣지 않았습니다.
  49. 49. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 이 경우 모든 "foo"에 대해서 매치되겠죠.
  50. 50. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 뒤쪽이 /^bar/이면 건너뛰기 앞쪽이 /bar$/이면 건너뛰기 배제 패턴 그러고 나서 "{~}" 속에 있는 배제 패턴을 매치됐던 곳 앞뒤에 한 번 더 매치시켜 봅니다.
  51. 51. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 뒤쪽이 /^bar/이면 건너뛰기 앞쪽이 /bar$/이면 건너뛰기 배제 패턴 만약 앞뒤가 이 패턴에 매치되면 최종 결과에선 빠지는 거죠.
  52. 52. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 뒤 3글자가 /^bar/이면 건너뛰기 앞 3글자가 /bar$/이면 건너뛰기 bar에 맞춰서 길이 제한 이때 살펴보는 앞뒤의 길이를
  53. 53. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 뒤 3글자가 /^bar/이면 건너뛰기 앞 3글자가 /bar$/이면 건너뛰기 bar에 맞춰서 길이 제한 배제 패턴이 매치될 수 있을 만한 최대 길이로 제한하고 있어요.
  54. 54. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 뒤 3글자가 /^bar/이면 건너뛰기 앞 3글자가 /bar$/이면 건너뛰기 bar에 맞춰서 길이 제한 예시로 든 배제 패턴은 /^bar/, /bar$/인데 앞뒤가 "bar"인지 아닌지는
  55. 55. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 뒤 3글자가 /^bar/이면 건너뛰기 앞 3글자가 /bar$/이면 건너뛰기 bar에 맞춰서 길이 제한 3글자만 보면 되니까 딱 그만큼만 보는 거죠.
  56. 56. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 뒤 3글자가 /^bar/이면 건너뛰기 앞 3글자가 /bar$/이면 건너뛰기 bar에 맞춰서 길이 제한 이 길이 제한이 있어야만 대상 문자열이 아무리 길어져도
  57. 57. "foo{~bar}" "{~bar}foo" 네거티브 룩어라운드 대안 /()(foo)()/ /()(foo)()/ 뒤 3글자가 /^bar/이면 건너뛰기 앞 3글자가 /bar$/이면 건너뛰기 bar에 맞춰서 길이 제한 네거티브 룩어라운드를 빠르게 끝낼 수 있습니다.
  58. 58. syntax.Parse("a?|b", syntax.Perl) import "regexp/syntax" Alternate '|' Literal 'b'Quest '?' Literal 'a' 패턴이 매치될 수 있는 문자열의 최대 길이를 구하는 건 간단했습니다.
  59. 59. syntax.Parse("a?|b", syntax.Perl) import "regexp/syntax" Alternate '|' Literal 'b'Quest '?' Literal 'a' regexp/syntax 표준 라이브러를 쓰면 정규식을 해석한 추상구문트리를 얻을 수 있는데
  60. 60. Alternate '|' Literal 'b'Quest '?' Literal 'a' 트리 살펴보면 최대 길이 계산 가능 트리 노드를 적당히 살펴보는 것만으로 최대 길이를 쉽게 계산할 수 있었죠.
  61. 61. "{~a}foo" O(n) "{~a+}foo" O(n²) 네거티브 룩어라운드 성능 물론 배제 패턴의 매치 최대 길이가 무한대인 경우엔
  62. 62. "{~a}foo" O(n) "{~a+}foo" O(n²) 네거티브 룩어라운드 성능 앞뒤를 딱 필요한 만큼 자를 수 없어서 최악의 경우 제곱 시간이 걸릴 수도 있어요.
  63. 63. "{~a+}foo" O(n²) 이런 패턴은 금지 하지만 다행히 이런 패턴은 그동안 한 번도 쓰였던 적이 없었고
  64. 64. "{~a+}foo" O(n²) 이런 패턴은 금지 간단히 금지하기로 결정할 수 있었습니다.
  65. 65. /()(foo)()/ foo foobar foobaz foobarr foofoobar 뒤 3글자가 /^bar/이면 건너뛰기
  66. 66. /()(foo)()/ foo foobar foobaz foobarr foofoobar 뒤 3글자가 /^bar/이면 건너뛰기 이렇게 만들어진 한글라이즈의 네거티브 룩어라운드가
  67. 67. /()(foo)()/ foo foobar foobaz foobarr foofoobar 뒤 3글자가 /^bar/이면 건너뛰기 실제로 어떻게 돌아가는지 한 번 살펴보겠습니다.
  68. 68. /()(foo)()/ foo foobar foobaz foobarr foofoobar 뒤 3글자가 /^bar/이면 건너뛰기 우선 번역한 정규식으로 매치를 돌려봅니다. 모든 "foo"가 매치되겠죠.
  69. 69. /()(foo)()/ foo foobar foobaz foobarr foofoobar 뒤 3글자가 /^bar/이면 건너뛰기 그 다음엔 매치된 "foo" 뒤 3글자씩을 살펴봅니다.
  70. 70. /()(foo)()/ foo foobar foobaz foobarr foofoobar 뒤 3글자가 /^bar/이면 건너뛰기 그 중에 /^bar/에 매치되는 게 있으면
  71. 71. /()(foo)()/ foo foobar foobaz foobarr foofoobar 뒤 3글자가 /^bar/이면 건너뛰기 결과에서 지워요.
  72. 72. /()(foo)()/ foo foobar foobaz foobarr foofoobar 뒤 3글자가 /^bar/이면 건너뛰기 이런 과정을 통하면 최종적으로 "bar"가 아닌 것 앞에 있는 "foo"만 남게 됩니다.
  73. 73. b. 말과 글
  74. 74. b. 말과 글 이번엔 한글라이즈에서 말과 글을 다루는 데 도움됐던
  75. 75. b. 말과 글 세 가지 Go 라이브러리를 소개해 드리겠습니다.
  76. 76. •한글 모아쓰기 •중국어 병음 사전 •일본어 형태소 분석 필요한 기능 필요한 기능은 한글 모아쓰기, 중국어 병음 사전, 일본어 형태소 분석이었습니다.
  77. 77. 한글 모아쓰기 ㅐㅍ-ㄹ 받침 표시 한글 모아쓰기는 이런 걸 얘기해요.
  78. 78. 한글 모아쓰기 ㅐㅍ-ㄹ 받침 표시 "ㅐㅍ-ㄹ"과 같이 자모를 나열했을 때
  79. 79. 한글 모아쓰기 애ㅐㅍ-ㄹ 받침 표시 불완전한 모음인 "ㅐ" 앞엔 "ㅇ"을 채워주고
  80. 80. 한글 모아쓰기 애플ㅐㅍ-ㄹ 받침 표시 불완전한 자음인 "ㅍ" 뒤엔 "ㅡ"를 채워서
  81. 81. 한글 모아쓰기 애플ㅐㅍ-ㄹ 받침 표시 온전한 한글 글자로 합쳐주는 기능입니다.
  82. 82. Διόνυσος 디오니소스 διονισοσ Transcribe ㄷㅣㅗㄴㅣㅅㅗㅅ RewriteP 한글라이즈 파이프라인의 Transcribe 단계에서 나온 자모의 나열을 최종결과로 만드는 데 쓰이죠.
  83. 83. suapapa/go_hangul 여기엔 이호민 님이 만드신 "go_hangul"을 사용했습니다.
  84. 84. •한글 판별 •자모 분리 및 조립 •한글 획 세기 import "github.com/suapapa/go_hangul" hangul.Join('ㅎ', 'ㅏ', 'ㄴ') == '한' hangul.Split('한') == 'ㅎ', 'ㅏ', 'ㄴ' hangul.IsJaeum('ㅋ') == true hangul.IsMoeum('ㅋ') == false 호민 님 감사합니다. 이 라이브러리엔 한글을 다루기 위한 유용한 기능이 많이 있어요.
  85. 85. •한글 판별 •자모 분리 및 조립 •한글 획 세기 import "github.com/suapapa/go_hangul" hangul.Join('ㅎ', 'ㅏ', 'ㄴ') == '한' hangul.Split('한') == 'ㅎ', 'ㅏ', 'ㄴ' hangul.IsJaeum('ㅋ') == true hangul.IsMoeum('ㅋ') == false 호민 님 감사합니다. 주어진 문자가 한글인지, 자음인지, 모음인지 판별해주는 함수와
  86. 86. •한글 판별 •자모 분리 및 조립 •한글 획 세기 import "github.com/suapapa/go_hangul" hangul.Join('ㅎ', 'ㅏ', 'ㄴ') == '한' hangul.Split('한') == 'ㅎ', 'ㅏ', 'ㄴ' hangul.IsJaeum('ㅋ') == true hangul.IsMoeum('ㅋ') == false 호민 님 감사합니다. 한글을 자모 단위로 쪼개거나 합치는 기능을 포함해 여러가지를 제공합니다.
  87. 87. •한글 판별 •자모 분리 및 조립 •한글 획 세기 import "github.com/suapapa/go_hangul" hangul.Join('ㅎ', 'ㅏ', 'ㄴ') == '한' hangul.Split('한') == 'ㅎ', 'ㅏ', 'ㄴ' hangul.IsJaeum('ㅋ') == true hangul.IsMoeum('ㅋ') == false 호민 님 감사합니다. 특이하게 한글의 획을 세는 기능까지도 있더라고요.
  88. 88. Rewrite TranscribePhonemize四川 쓰촨 si chuan si, chwan, ㅆㅡㅊㅘ-ㄴ
  89. 89. Rewrite TranscribePhonemize四川 쓰촨 si chuan si, chwan, ㅆㅡㅊㅘ-ㄴ 나머지 두 라이브러리는 Phonemize 단계에서 소리글자를 만드는 데에 쓰고 있습니다.
  90. 90. 중국어 병음 Qīng dǎo青岛 칭 다오 그 중 첫 번째는 중국어 병음입니다.
  91. 91. 중국어 병음 Qīng dǎo青岛 칭 다오 중국어 한자엔 대응되는 로마자 표기가 있는데 이를 "병음"이라고 불러요.
  92. 92. 중국어 병음 Qīng dǎo青岛 칭 다오 예를 들어 "青岛"의 병음은 "Qīng dǎo"로 표현되죠.
  93. 93. •중국 한자를 병음으로 변환 •단순 사전이라 빠르다. •의외로 용량도 안 크다. (800KB) import "github.com/mozillazg/go-pinyin" args := pinyin.NewArgs() pinyin.SinglePinyin('青', args) == "Qīng" "go-pinyin"이라는 라이브러리를 쓰면 손쉽게 병음을 구할 수 있습니다.
  94. 94. •중국 한자를 병음으로 변환 •단순 사전이라 빠르다. •의외로 용량도 안 크다. (800KB) import "github.com/mozillazg/go-pinyin" args := pinyin.NewArgs() pinyin.SinglePinyin('青', args) == "Qīng" 이 라이브러리는 단순한 사전이어서 속도도 빠르고
  95. 95. •중국 한자를 병음으로 변환 •단순 사전이라 빠르다. •의외로 용량도 안 크다. (800KB) import "github.com/mozillazg/go-pinyin" args := pinyin.NewArgs() pinyin.SinglePinyin('青', args) == "Qīng" 의외로 데이터도 별로 크지 않아서 800KB 밖에 되지 않습니다.
  96. 96. Hangulize("chi", "北京") Hangulize("chi", "章子怡") == "베이징" == "장쯔이" 이 라이브러리 덕분에 한글라이즈에 중국어를 새로 추가할 수 있게 됐어요.
  97. 97. Hangulize("chi", "李興燮") == "리싱셰" 이 흥 섭 중국어가 되니까 자기 한자 이름 넣어보는 재미도 나름 쏠쏠하더라고요.
  98. 98. 陸カ審月名検ナレチカ可栄1経断ヒヲカツ宅意へざン丁可ホ 載省末応をじいく行昧7年てぼかせ答予シ告断ハヌカサ武元 争ちぞけみ。26丈なづ広靱増理ソヒタセ士定ワオトヘ整判 べっ集遺ワサヤイ聞出じどれな勝際ずンれ線芸せ年今ロエヒ 常政よのトッ上日やみぎね告創ゅど。作ケヨワ逮氏ッじルへ 地能どぜ祉83派あ織口ケ更探づを車下べひく毎旅まひづゅ意 幕づ務鋼けぶ要情ハリス択著乞伍トごへれ。 일본어 형태소 분석 일본어 로렘 입숨
  99. 99. 陸カ審月名検ナレチカ可栄1経断ヒヲカツ宅意へざン丁可ホ 載省末応をじいく行昧7年てぼかせ答予シ告断ハヌカサ武元 争ちぞけみ。26丈なづ広靱増理ソヒタセ士定ワオトヘ整判 べっ集遺ワサヤイ聞出じどれな勝際ずンれ線芸せ年今ロエヒ 常政よのトッ上日やみぎね告創ゅど。作ケヨワ逮氏ッじルへ 地能どぜ祉83派あ織口ケ更探づを車下べひく毎旅まひづゅ意 幕づ務鋼けぶ要情ハリス択著乞伍トごへれ。 일본어 형태소 분석 일본어 로렘 입숨 다음은 일본어인데
  100. 100. 陸カ審月名検ナレチカ可栄1経断ヒヲカツ宅意へざン丁可ホ 載省末応をじいく行昧7年てぼかせ答予シ告断ハヌカサ武元 争ちぞけみ。26丈なづ広靱増理ソヒタセ士定ワオトヘ整判 べっ集遺ワサヤイ聞出じどれな勝際ずンれ線芸せ年今ロエヒ 常政よのトッ上日やみぎね告創ゅど。作ケヨワ逮氏ッじルへ 地能どぜ祉83派あ織口ケ更探づを車下べひく毎旅まひづゅ意 幕づ務鋼けぶ要情ハリス択著乞伍トごへれ。 일본어 형태소 분석 일본어 로렘 입숨 일본어는 특히 처리하기 까다로웠던 것 같아요.
  101. 101. 陸カ審月名検ナレチカ可栄1経断ヒヲカツ宅意へざン丁可ホ 載省末応をじいく行昧7年てぼかせ答予シ告断ハヌカサ武元 争ちぞけみ。26丈なづ広靱増理ソヒタセ士定ワオトヘ整判 べっ集遺ワサヤイ聞出じどれな勝際ずンれ線芸せ年今ロエヒ 常政よのトッ上日やみぎね告創ゅど。作ケヨワ逮氏ッじルへ 地能どぜ祉83派あ織口ケ更探づを車下べひく毎旅まひづゅ意 幕づ務鋼けぶ要情ハリス択著乞伍トごへれ。 일본어 형태소 분석 일본어 로렘 입숨 일본어에선 뜻글자인 한자와 소리글자인 가나를 섞어 씁니다.
  102. 102. 陸カ審月名検ナレチカ可栄1経断ヒヲカツ宅意へざン丁可ホ 載省末応をじいく行昧7年てぼかせ答予シ告断ハヌカサ武元 争ちぞけみ。26丈なづ広靱増理ソヒタセ士定ワオトヘ整判 べっ集遺ワサヤイ聞出じどれな勝際ずンれ線芸せ年今ロエヒ 常政よのトッ上日やみぎね告創ゅど。作ケヨワ逮氏ッじルへ 地能どぜ祉83派あ織口ケ更探づを車下べひく毎旅まひづゅ意 幕づ務鋼けぶ要情ハリス択著乞伍トごへれ。 일본어 형태소 분석 일본어 로렘 입숨 그리고 띄어쓰기를 사용하지 않아요.
  103. 103. 陸カ審月名検ナレチカ可栄1経断ヒヲカツ宅意へざン丁可ホ 載省末応をじいく行昧7年てぼかせ答予シ告断ハヌカサ武元 争ちぞけみ。26丈なづ広靱増理ソヒタセ士定ワオトヘ整判 べっ集遺ワサヤイ聞出じどれな勝際ずンれ線芸せ年今ロエヒ 常政よのトッ上日やみぎね告創ゅど。作ケヨワ逮氏ッじルへ 地能どぜ祉83派あ織口ケ更探づを車下べひく毎旅まひづゅ意 幕づ務鋼けぶ要情ハリス択著乞伍トごへれ。 일본어 형태소 분석 일본어 로렘 입숨 여기 붙여 둔 문단은 일본어 로렘 입숨인데 보시다시피 띄어쓰기가 없습니다.
  104. 104. 陸カ審月名検ナレチカ可栄1経断ヒヲカツ宅意へざン丁可ホ 載省末応をじいく行昧7年てぼかせ答予シ告断ハヌカサ武元 争ちぞけみ。26丈なづ広靱増理ソヒタセ士定ワオトヘ整判 べっ集遺ワサヤイ聞出じどれな勝際ずンれ線芸せ年今ロエヒ 常政よのトッ上日やみぎね告創ゅど。作ケヨワ逮氏ッじルへ 地能どぜ祉83派あ織口ケ更探づを車下べひく毎旅まひづゅ意 幕づ務鋼けぶ要情ハリス択著乞伍トごへれ。 일본어 형태소 분석 일본어 로렘 입숨 전각문자 마침표와 띄어쓰기처럼 보이는 이것도 사실은 전각문자 1개죠.
  105. 105. 일본어 형태소 분석: 띄어쓰기 宮本茂일본어 ▶ 미야모토 시게루 일본인 이름은 이렇게 한자 서너자로 이뤄진 경우가 많은데
  106. 106. 일본어 형태소 분석: 띄어쓰기 宮本茂일본어 ▶ 미야모토 시게루 어디까지가 성이고 어디까지가 이름인지 구별해서
  107. 107. 일본어 형태소 분석: 띄어쓰기 宮本茂일본어 ▶ 미야모토 시게루 전사 결과엔 띄어쓰기를 넣어줘야 합니다.
  108. 108. 일본어 형태소 분석: 띄어쓰기 宮本茂일본어 ▶ 미야모토 시게루 형태소를 분석해보면 이름 경계를 알 수 있는데
  109. 109. 일본어 형태소 분석: 띄어쓰기 宮本茂일본어 ▶ 미야모토 시게루 이게 일본어에 형태소 분석이 필요했던 첫 번째 이유죠.
  110. 110. 일본어 형태소 분석: 후리가나 ニッポンダ ニホンゴ 日本だ 日本語 니 고혼 닛 폰 다 또 다른 이유는 후리가나입니다.
  111. 111. 일본어 형태소 분석: 후리가나 ニッポンダ ニホンゴ 日本だ 日本語 니 고혼 닛 폰 다 일본어에서 한자의 읽는 법을 소리글자로 쓰는 걸 "후리가나"라고 하는데
  112. 112. 일본어 형태소 분석: 후리가나 ニッポンダ ニホンゴ 日本だ 日本語 니 고혼 닛 폰 다 같은 한자라도 문맥에 따라서 후리가나가 달라질 수 있습니다.
  113. 113. 일본어 형태소 분석: 후리가나 ニッポンダ ニホンゴ 日本だ 日本語 니 고혼 닛 폰 다 한자어인 "日本"의 경우 독립적으로 쓰일 땐 "닛폰"으로 읽히지만
  114. 114. 일본어 형태소 분석: 후리가나 ニッポンダ ニホンゴ 日本だ 日本語 니 고혼 닛 폰 다 다른 한자어의 일부로 쓰일 땐 "니혼"으로 읽히더라고요.
  115. 115. 일본어 형태소 분석: 후리가나 ニッポンダ ニホンゴ 日本だ 日本語 니 고혼 닛 폰 다 이런 걸 판단해서 한자로부터 후리가나를 추출해줄 도구가 필요했죠.
  116. 116. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 마지막으로 장모음 문제도 있었습니다.
  117. 117. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 우리말에선 사라진 장모음/단모음 구별이 일본어엔 있는데
  118. 118. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 외래어 표기법에선 장모음을 단모음처럼 취급하거든요.
  119. 119. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 그래서 첫 번째 예시인 "いいえ"(iie)의 경우 "いい"(ii)가 장음이라서
  120. 120. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 "이이에"가 아닌 "이에"로 옮깁니다.
  121. 121. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 하지만 똑같이 "いい"(ii)라고 써있더라도 장모음이 아니라
  122. 122. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 형태소의 경계로 나뉘어 있는 경우도 있어요.
  123. 123. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 두 번째 예시인 "かわいい"(kawaii)가 그런 경우인데
  124. 124. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 여기의 "いい"(ii)는 장모음이 아니고 각각 다른 형태소에 속한 두 개의 "い"(i)죠.
  125. 125. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 이때는 단모음처럼 합치면 안 되고 "이이"라고 구별해서 적어줘야 됩니다.
  126. 126. 일본어 형태소 분석: 장모음 イーエ カワイ·イ いいえ かわいい 가 이i i e iiwaka 에 와 이 이 이처럼 일본어는 제대로 한글로 옮기려면 꽤 정밀한 형태소 분석을 필요로 하는데
  127. 127. •순수 Go •일본어 형태소 분석기 import "github.com/ikawaha/kagome.ipadic" 다행히 "kagome"라는 적합한 라이브러리가 있었습니다.
  128. 128. •순수 Go •일본어 형태소 분석기 import "github.com/ikawaha/kagome.ipadic" kagome는 Go만으로 구현된 일본어 형태소 분석기인데
  129. 129. •순수 Go •일본어 형태소 분석기 import "github.com/ikawaha/kagome.ipadic" 여기에 일본어 문장을 입력하면
  130. 130. •순수 Go •일본어 형태소 분석기 import "github.com/ikawaha/kagome.ipadic" 형태소의 품사, 원형, 읽는 법까지 꽤 자세한 정보를 구할 수 있어요.
  131. 131. •순수 Go •일본어 형태소 분석기 •무거워요 import "github.com/ikawaha/kagome.ipadic" 단, 다소 무거운 편입니다.
  132. 132. •순수 Go •일본어 형태소 분석기 •무거워요 import "github.com/ikawaha/kagome.ipadic" 초기화 속도도 느리고 분석 속도도 그리 빠르진 않더라고요.
  133. 133. Hangulize("jpn", "任天堂") Hangulize("jpn", "新海誠") Hangulize("jpn", "天空の城ラピュタ") == "닌텐도" == "신카이 마코토" == "덴쿠노시로라퓨타" 어쨌든 옛 한글라이즈의 일본어 규칙에선
  134. 134. Hangulize("jpn", "任天堂") Hangulize("jpn", "新海誠") Hangulize("jpn", "天空の城ラピュタ") == "닌텐도" == "신카이 마코토" == "덴쿠노시로라퓨타" 소리글자인 가나만 지원했고 장모음 구별도 못 했는데
  135. 135. Hangulize("jpn", "任天堂") Hangulize("jpn", "新海誠") Hangulize("jpn", "天空の城ラピュタ") == "닌텐도" == "신카이 마코토" == "덴쿠노시로라퓨타" kagome 덕분에 새 한글라이즈는 웬만한 일본어 문장까지도 지원할 수 있게 됐습니다.
  136. 136. •hangulize 8.1MB •go-pinyin 3.3MB •kagome.ipadic 13MB •합치면 20MB 빌드 용량
  137. 137. •hangulize 8.1MB •go-pinyin 3.3MB •kagome.ipadic 13MB •합치면 20MB 빌드 용량 그런데 go-pinyin이랑 kagome까지 한글라이즈 패키지에 포함하려고 보니
  138. 138. •hangulize 8.1MB •go-pinyin 3.3MB •kagome.ipadic 13MB •합치면 20MB 빌드 용량 빌드 용량이 너무 커지더라고요.
  139. 139. •hangulize 8.1MB •go-pinyin 3.3MB •kagome.ipadic 13MB •합치면 20MB 빌드 용량 특히 JavaScript 빌드도 같이 뽑고 있었는데 20MB면 JS 파일 하나치곤 너무 커서
  140. 140. import "github.com/hangulize/hangulize" import "github.com/hangulize/hangulize/phonemize/furigana" func main() { hangulize.UsePhonemizer(&furigana.P) fmt.Println(hangulize.Hangulize("jpn", "秋葉原")) // Output: 아키하바라 } 플러그인 이렇게 플러그인처럼 쓸 수 있도록 분리했어요.
  141. 141. import "github.com/hangulize/hangulize" import "github.com/hangulize/hangulize/phonemize/furigana" func main() { hangulize.UsePhonemizer(&furigana.P) fmt.Println(hangulize.Hangulize("jpn", "秋葉原")) // Output: 아키하바라 } 플러그인 여러분이 Go 코드에서 한글라이즈 라이브러리를 쓰실 경우엔
  142. 142. import "github.com/hangulize/hangulize" import "github.com/hangulize/hangulize/phonemize/furigana" func main() { hangulize.UsePhonemizer(&furigana.P) fmt.Println(hangulize.Hangulize("jpn", "秋葉原")) // Output: 아키하바라 } 플러그인 직접 UsePhonemizer를 호출해야만 중국어와 일본어도 제대로 다룰 수 있습니다.
  143. 143. c. 테스트와 문서화
  144. 144. c. 테스트와 문서화 다음으로 Go의 테스트 및 문서화 도구에서 인상적이었던 부분을 말씀드릴게요.
  145. 145. assert Portuguese({ 'bossa nova': '보사 노바', 'moor': '모르', 'maracas': '마라카스', }) 용례집 유닛테스트 국립국어원 사이트에선 이렇게 외래어 표기법 용례집을 구할 수 있는데
  146. 146. assert Portuguese({ 'bossa nova': '보사 노바', 'moor': '모르', 'maracas': '마라카스', }) 용례집 유닛테스트 옛 한글라이즈 때부터 이 용례집을 유닛테스트로 만들어 써왔어요.
  147. 147. assert Portuguese({ 'bossa nova': '보사 노바', 'moor': '모르', 'maracas': '마라카스', }) 용례집 유닛테스트 이것으로 전사 규칙을 제대로 만들었는지 쉽게 검증할 수 있었죠.
  148. 148. 기존 용례집 테스트 승계 5. 20. 재제작 시작 5. 27. 첫 릴리즈 5. 28. 기존 언어 모두 지원 8. 27. 발표 새 한글라이즈를 만들면서도 기존 용례집 테스트는 그대로 승계했습니다.
  149. 149. 기존 용례집 테스트 승계 5. 20. 재제작 시작 5. 27. 첫 릴리즈 5. 28. 기존 언어 모두 지원 8. 27. 발표 처음부터 테스트가 충분했던 덕분에
  150. 150. 기존 용례집 테스트 승계 5. 20. 재제작 시작 5. 27. 첫 릴리즈 5. 28. 기존 언어 모두 지원 8. 27. 발표 8일 첫 8일만에 기존 38가지 언어를 모두 지원하는 것도 가능했죠.
  151. 151. 기존 용례집 테스트 승계 5. 20. 재제작 시작 5. 27. 첫 릴리즈 5. 28. 기존 언어 모두 지원 8. 27. 발표 8일 그만큼 한글라이즈에서 테스트는 굉장히 중요한 축을 맡고 있습니다.
  152. 152. import "testing"
  153. 153. import "testing" "testing"은 Go의 표준 테스트 도구인데 기본 도구임에도 불구하고
  154. 154. import "testing" 상당히 강력한 테스트 프레임워크를 쓰는 기분이었습니다.
  155. 155. // $ go test -run TestLang/ita func TestLang(t *testing.T) { t.Run("jpn", testJpn) t.Run("chi", testChi) t.Run("ita", testIta) } 서브테스트 testing에서 인상적인 점 중 하나는 바로 서브테스트 기능이었어요.
  156. 156. // $ go test -run TestLang/ita func TestLang(t *testing.T) { t.Run("jpn", testJpn) t.Run("chi", testChi) t.Run("ita", testIta) } 서브테스트 서브테스트는 한 테스트케이스에서 여러 테스트케이스를 파생시키는 기능인데
  157. 157. // $ go test -run TestLang/ita func TestLang(t *testing.T) { t.Run("jpn", testJpn) t.Run("chi", testChi) t.Run("ita", testIta) } 서브테스트 본격적인 서드파티 테스트 프레임워크에서나 지원할 법한 걸
  158. 158. // $ go test -run TestLang/ita func TestLang(t *testing.T) { t.Run("jpn", testJpn) t.Run("chi", testChi) t.Run("ita", testIta) } 서브테스트 기본 도구가 지원한다는 점이 놀라웠죠.
  159. 159. // $ go test -run TestLang/ita func TestLang(t *testing.T) { t.Run("jpn", testJpn) t.Run("chi", testChi) t.Run("ita", testIta) } 서브테스트 한글라이즈에선 각 언어 별 용례집을 검증하는 테스트를
  160. 160. // $ go test -run TestLang/ita func TestLang(t *testing.T) { t.Run("jpn", testJpn) t.Run("chi", testChi) t.Run("ita", testIta) } 서브테스트 이런 식으로 서브테스트를 이용해 만들었습니다.
  161. 161. // $ go test -bench . // BenchmarkF 100 1078834 ns/op func BenchmarkF(b *testing.B) { for i := 0; i < b.N; i++ { f() } } 벤치마크
  162. 162. // $ go test -bench . // BenchmarkF 100 1078834 ns/op func BenchmarkF(b *testing.B) { for i := 0; i < b.N; i++ { f() } } 벤치마크 또 벤치마크 기능까지 제공하는 점도 신선했는데
  163. 163. // $ go test -bench . // BenchmarkF 100 1078834 ns/op func BenchmarkF(b *testing.B) { for i := 0; i < b.N; i++ { f() } } 벤치마크 "Benchmark"로 시작하는 함수에서 testing.B를 인자로 받아
  164. 164. // $ go test -bench . // BenchmarkF 100 1078834 ns/op func BenchmarkF(b *testing.B) { for i := 0; i < b.N; i++ { f() } } 벤치마크 b.N바퀴 도는 반복문 코드를 작성하는 식으로 만들더라고요.
  165. 165. // $ go test -bench . // BenchmarkF 100 1078834 ns/op func BenchmarkF(b *testing.B) { for i := 0; i < b.N; i++ { f() } } 벤치마크 테스트 실행할 때 "-bench" 옵션을 지정하면
  166. 166. // $ go test -bench . // BenchmarkF 100 1078834 ns/op func BenchmarkF(b *testing.B) { for i := 0; i < b.N; i++ { f() } } 벤치마크 벤치마크를 돌려본 후 루프 한 바퀴에 얼마나 걸렸는지 알려줍니다.
  167. 167. benchmark( setup='prepare()', run='do_it()', ) 흔히 보던 벤치마크 제가 흔히 보던 벤치마크 도구에선 보통
  168. 168. benchmark( setup='prepare()', run='do_it()', ) 흔히 보던 벤치마크 벤치마크를 준비하는 코드 "setup"과 벤치마크할 코드 "run"을
  169. 169. benchmark( setup='prepare()', run='do_it()', ) 흔히 보던 벤치마크 따로 입력하는 인터페이스가 있었는데
  170. 170. func BenchmarkDoIt(b *testing.B) { Prepare() b.ResetTimer() for i := 0; i < b.N; i++ { DoIt() } } Go 벤치마크 복잡한 추상화 없이 testing.B 인자만으로
  171. 171. func BenchmarkDoIt(b *testing.B) { Prepare() b.ResetTimer() for i := 0; i < b.N; i++ { DoIt() } } Go 벤치마크 훨씬 간결하게 같은 기능을 구현한다는 점에서 상당히 감탄했습니다.
  172. 172. func BenchmarkF(b *testing.B) { b.Run("1", genBenchmarkF(1)) b.Run("10", genBenchmarkF(10)) b.Run("100", genBenchmarkF(100)) b.Run("1000", genBenchmarkF(1000)) } 서브벤치마크 BenchmarkF/1 BenchmarkF/1000 시간
  173. 173. func BenchmarkF(b *testing.B) { b.Run("1", genBenchmarkF(1)) b.Run("10", genBenchmarkF(10)) b.Run("100", genBenchmarkF(100)) b.Run("1000", genBenchmarkF(1000)) } 서브벤치마크 BenchmarkF/1 BenchmarkF/1000 시간 벤치마크에서도 서브테스트와 같은 방식으로 서브벤치마크를 만들 수 있는데
  174. 174. func BenchmarkF(b *testing.B) { b.Run("1", genBenchmarkF(1)) b.Run("10", genBenchmarkF(10)) b.Run("100", genBenchmarkF(100)) b.Run("1000", genBenchmarkF(1000)) } 서브벤치마크 BenchmarkF/1 BenchmarkF/1000 시간 덕분에 알고리즘의 시간복잡도에 더 집중할 수 있었고
  175. 175. func BenchmarkF(b *testing.B) { b.Run("1", genBenchmarkF(1)) b.Run("10", genBenchmarkF(10)) b.Run("100", genBenchmarkF(100)) b.Run("1000", genBenchmarkF(1000)) } 서브벤치마크 BenchmarkF/1 BenchmarkF/1000 시간 개선하는 과정이 눈에 바로바로 들어와서 성취감도 높았습니다.
  176. 176. func TestF(t *testing.T) { if f() != 42 { t.Fail("f() does not return 42") } }
  177. 177. func TestF(t *testing.T) { if f() != 42 { t.Fail("f() does not return 42") } } 한 가지 아쉬웠던 건 Go에 assert 문법이 없어서
  178. 178. func TestF(t *testing.T) { if f() != 42 { t.Fail("f() does not return 42") } } 테스트케이스를 두꺼운 if문 덩어리로 만들어야 한다는 점이었는데
  179. 179. import "github.com/stretchr/testify/assert" func TestF(t *testing.T) { assert.Equal(t, 42, f()) assert.NotEqual(t, 21, f()) } 전 테스트케이스를 좀 더 편하게 쓰기 위해 서드파티 라이브러리인 "testify"를 쓰고 있습니다.
  180. 180. import "github.com/stretchr/testify/assert" func TestF(t *testing.T) { assert.Equal(t, 42, f()) assert.NotEqual(t, 21, f()) } testify의 "assert" 패키지가 제공하는 "Equal", "NotEqual" 같은 메소드를 이용하면
  181. 181. import "github.com/stretchr/testify/assert" func TestF(t *testing.T) { assert.Equal(t, 42, f()) assert.NotEqual(t, 21, f()) } 테스트 코드 작성도 훨씬 간편하고 테스트 실패 보고서도 읽기 더 좋더라고요.
  182. 182. import "github.com/stretchr/testify/assert" func TestF(t *testing.T) { assert.Equal(t, 42, f()) assert.NotEqual(t, 21, f()) } 워낙 유용하고 유명해서 아마 여러분 대부분도 이미 쓰고 계실 것 같습니다.
  183. 183. https://godoc.org/github.com/hangulize/hangulize
  184. 184. https://godoc.org/github.com/hangulize/hangulize 한편 패키지를 문서화하면서는 GoDoc의 초간단 문법이 인상적이었어요.
  185. 185. • **이런거**, _이런거_, `이런거` 없음 • 불릿 목록도 없음 • 그림도 없음 초라한 문법 그동안 마크다운에 익숙해져 있었고 마크다운도 충분히 간단하다고 생각했는데
  186. 186. • **이런거**, _이런거_, `이런거` 없음 • 불릿 목록도 없음 • 그림도 없음 초라한 문법 GoDoc의 문법은 훨씬 단순하더라고요.
  187. 187. • **이런거**, _이런거_, `이런거` 없음 • 불릿 목록도 없음 • 그림도 없음 초라한 문법 **볼드**, _이탤릭_, `코드`도 없고 불릿 목록도 없고 그림도 없었습니다.
  188. 188. • URL엔 하이퍼링크 • 들여쓰면 코드블록 • 대문자로 시작하고 마침표가 없는 줄은 제목 초간단 문법 그렇다고 문서화하는 데에 부족한 건 아니었어요.
  189. 189. • URL엔 하이퍼링크 • 들여쓰면 코드블록 • 대문자로 시작하고 마침표가 없는 줄은 제목 초간단 문법 URL엔 자동으로 하이퍼링크가 붙고 한 칸이라도 들여쓰기하면 코드블록으로 인식됩니다.
  190. 190. • URL엔 하이퍼링크 • 들여쓰면 코드블록 • 대문자로 시작하고 마침표가 없는 줄은 제목 초간단 문법 마크다운처럼 "#" 같은 걸 붙이지 않고도
  191. 191. • URL엔 하이퍼링크 • 들여쓰면 코드블록 • 대문자로 시작하고 마침표가 없는 줄은 제목 초간단 문법 대문자로 시작하고 마침표가 없는 줄은 제목으로 취급되죠.
  192. 192. $ go doc github.com/hangulize/hangulize PACKAGE DOCUMENTATION package hangulize import "github.com/hangulize/hangulize" Package hangulize transcribes non-Korean words into Hangul. "Hello!" -> "헬로!" Hangulize was inspired by Brian Jongseong Park (http://iceager.egloos.com/2610028). Based on this idea, the original 커맨드라인 문법이 이렇게 단순한 건 GoDoc이 웹 사이트 뿐만 아니라
  193. 193. $ go doc github.com/hangulize/hangulize PACKAGE DOCUMENTATION package hangulize import "github.com/hangulize/hangulize" Package hangulize transcribes non-Korean words into Hangul. "Hello!" -> "헬로!" Hangulize was inspired by Brian Jongseong Park (http://iceager.egloos.com/2610028). Based on this idea, the original 커맨드라인 커맨드라인 출력도 중요하게 다루기 때문이라고 합니다.
  194. 194. •.md, .rst에 비해 초라하지만 •외울 게 적어서 •서식을 헷갈리지 않아요. 쉬운 문법 아무튼 마크다운이나 reStructuredText에 비하면 상당히 초라한 문법이지만
  195. 195. •.md, .rst에 비해 초라하지만 •외울 게 적어서 •서식을 헷갈리지 않아요. 쉬운 문법 그만큼 외울 게 적어서 금방 익숙해질 수 있었고
  196. 196. •.md, .rst에 비해 초라하지만 •외울 게 적어서 •서식을 헷갈리지 않아요. 쉬운 문법 작성하면서 서식을 헷갈리는 일이 거의 없었던 것 같습니다.
  197. 197. $ godoc -http=:8080 -goroot="$GOPATH" 로컬 godoc localhost:8080/pkg/github.com/hangulize/hangulize 처음 쓸 때 조금 헷갈리더라도 로컬 GoDoc 서버를 이용해
  198. 198. $ godoc -http=:8080 -goroot="$GOPATH" 로컬 godoc localhost:8080/pkg/github.com/hangulize/hangulize 실시간으로 바로바로 확인하면서 쓰다 보니 금새 익힐 수 있었습니다.
  199. 199. func ExampleComposeHangul_perfect() 예제 코드 대상 이름 소제목 예제 코드로 인식되게 함
  200. 200. func ExampleComposeHangul_perfect() 예제 코드 대상 이름 소제목 예제 코드로 인식되게 함 또한 문서에 포함되는 예제 코드를 테스트 코드의 일종으로 다루는 것도 인상적이었는데
  201. 201. func ExampleComposeHangul_perfect() 예제 코드 대상 이름 소제목 예제 코드로 인식되게 함 정해진 형식에 맞춰 테스트 함수의 이름을 지정하면
  202. 202. 문서 상에 딱 그 대상 밑에
  203. 203. 함수 내용이 예제 코드로써 배치되는 방식이었어요.
  204. 204. $ go test --- FAIL: ExampleComposeHangul_perfect (0.00s) got: 하느글라이스 want: 한글라이즈 FAIL exit status 1 FAIL github.com/hangulize/hangulize 1.949s 예제 코드 방부처리 예제 코드가 테스트 코드의 일종이다 보니 혹시 나중에 그 내용이 틀리게 되더라도
  205. 205. $ go test --- FAIL: ExampleComposeHangul_perfect (0.00s) got: 하느글라이스 want: 한글라이즈 FAIL exit status 1 FAIL github.com/hangulize/hangulize 1.949s 예제 코드 방부처리 테스트가 바로 실패해서 언제나 올바른 상태를 유지할 수 있도록 도와주더라고요.
  206. 206. 3. 이룬 것
  207. 207. 3. 이룬 것 지금까지 한글라이즈를 Go로 다시 만들면서 인상적이었던 부분을 소개해드렸는데
  208. 208. 3. 이룬 것 마지막으로 이번 재제작 과정을 통해서 이룬 점들을 살펴보고 이 발표를 마치겠습니다.
  209. 209. 1. 성능 개선 이룬 것 첫 번째는 성능 개선이에요.
  210. 210. 1. 성능 개선 이룬 것 Go가 파이썬보다 훨씬 빠르기는 하지만 이번엔 알고리즘에도 꽤 신경쓰면서 작업했거든요.
  211. 211. Cappuccino이탈리아어 ▶ 카푸치노 •옛 한글라이즈 398㎲ •새 한글라이즈 85㎲ "카푸치노"같이 짧은 단어로 비교해보면 옛 한글라이즈에선 398㎲가 걸렸지만
  212. 212. Cappuccino이탈리아어 ▶ 카푸치노 •옛 한글라이즈 398㎲ •새 한글라이즈 85㎲ 새 한글라이즈에선 85㎲밖에 걸리지 않았습니다.
  213. 213. Cappuccino이탈리아어 ▶ 카푸치노 •옛 한글라이즈 398㎲ •새 한글라이즈 85㎲ 네다섯 배는 빨라진 거죠.
  214. 214. 단어 길이 옛 한글라이즈 새 한글라이즈 8만 자 630㎳ 465㎳ 80만 자 10초 5초 800만 자 14분 50초 시간 옛 한글라이즈 새 한글라이즈 단어 길이 단어가 길면 길 수록 새 한글라이즈가 옛 한글라이즈보다 더욱 빠른데
  215. 215. 단어 길이 옛 한글라이즈 새 한글라이즈 8만 자 630㎳ 465㎳ 80만 자 10초 5초 800만 자 14분 50초 시간 옛 한글라이즈 새 한글라이즈 단어 길이 800만 자짜리 단어에서 새 한글라이즈에선 50초 정도밖에 걸리지 않았지만
  216. 216. 단어 길이 옛 한글라이즈 새 한글라이즈 8만 자 630㎳ 465㎳ 80만 자 10초 5초 800만 자 14분 50초 시간 옛 한글라이즈 새 한글라이즈 단어 길이 옛 한글라이즈에선 14분이나 걸리더라고요.
  217. 217. O(n²) O(n) 옛 한글라이즈 새 한글라이즈 옛 한글라이즈엔 일부 비효율적인 코드가 있어서 시간복잡도가 제곱 시간이었는데
  218. 218. O(n²) O(n) 옛 한글라이즈 새 한글라이즈 그에 반해 새 한글라이즈는 선형시간을 보장하기 때문입니다.
  219. 219. 2. Phonemize 도입 이룬 것
  220. 220. 2. Phonemize 도입 이룬 것 두 번째는 이번에 새로 추가한 Phonemize 단계예요.
  221. 221. Rewrite TranscribePhonemize 재제작 과정에서 새로 도입 四川 쓰촨 si chuan si, chwan, ㅆㅡㅊㅘ-ㄴ Phonemize 단계가 옛 한글라이즈엔 없었거든요.
  222. 222. Rewrite TranscribePhonemize 재제작 과정에서 새로 도입 四川 쓰촨 si chuan si, chwan, ㅆㅡㅊㅘ-ㄴ 앞에서 살펴봤듯이 이 단계가 추가된 덕분에
  223. 223. Rewrite TranscribePhonemize 재제작 과정에서 새로 도입 四川 쓰촨 si chuan si, chwan, ㅆㅡㅊㅘ-ㄴ 중국어도 새로 지원할 수 있었고 일본어도 더 제대로 지원할 수 있게 됐죠.
  224. 224. Ghoti fishPhonemize 어쩌면 영어도? 어쩌면 Phonemize 단계를 이용해서 언젠간 영어도 지원할 수 있지 않을까 싶습니다.
  225. 225. 3. 생산성 개선 이룬 것
  226. 226. 3. 생산성 개선 이룬 것 그리고 전사 규칙 만들 때의 생산성도 상당히 좋아진 것 같아요.
  227. 227. • 중국어 전혀 모르는데 • 중국어 외래어 표기법 만드는 데 이틀 전사 규칙 제작 생산성 저는 중국어를 전혀 모르는데 중국어 외래어 표기법 자료만 보고
  228. 228. • 중국어 전혀 모르는데 • 중국어 외래어 표기법 만드는 데 이틀 전사 규칙 제작 생산성 규칙을 만드는 데엔 이틀 밖에 걸리지 않았습니다.
  229. 229. test: "郭廣昌" -> "궈광창" "劉鶴" -> "류허" "屠呦呦" -> "투유유" "許家印" -> "쉬자인" "王健林" -> "왕젠린" "廬志强" -> "루즈창" "馬化騰" -> "마화텅" "努爾白克力" -> "누얼바이커리" "金立群" -> "진리췬" "曹國偉" -> "차오궈웨이" "李河君" -> "리허쥔" "游客" -> "유커" "雷軍" -> "레이쥔" "李保樟" -> "리바오장" "古天樂" -> "구톈러" "鄧森悅" -> "덩썬웨" 이렇게 용례집에서 단어를 긁어서 테스트케이스에 대량으로 집어넣고
  230. 230. rewrite: "v" -> "yu" "{c|ch|r|s|sh|z|zh}i" -> "i," "{<jqx>}ue" -> "yue" "{<jqx>}uan" -> "yuan" "{<jqx>}un" -> "yun" transcribe: # 단운 "yu" -> "ㅟ" # 제치류 "{<J>}yang" -> "ㅏ-ㅇ" "{<J>}yan" -> "ㅔ-ㄴ" "{<J>}you" -> "ㅜ" "{<J>}yai" -> "ㅏㅣ" "{<J>}yao" -> "ㅏㅗ" "{<J>}ya" -> "ㅏ" "{<J>}yo" -> "ㅗ" "{<J>}ye" -> "ㅔ" 오른쪽과 같은 외래어 표기법 표를 참고해서
  231. 231. rewrite: "v" -> "yu" "{c|ch|r|s|sh|z|zh}i" -> "i," "{<jqx>}ue" -> "yue" "{<jqx>}uan" -> "yuan" "{<jqx>}un" -> "yun" transcribe: # 단운 "yu" -> "ㅟ" # 제치류 "{<J>}yang" -> "ㅏ-ㅇ" "{<J>}yan" -> "ㅔ-ㄴ" "{<J>}you" -> "ㅜ" "{<J>}yai" -> "ㅏㅣ" "{<J>}yao" -> "ㅏㅗ" "{<J>}ya" -> "ㅏ" "{<J>}yo" -> "ㅗ" "{<J>}ye" -> "ㅔ" 패턴 찾아 바꾸기 규칙만 몇 개 짜다 보니 금방 완성할 수 있었죠.
  232. 232. 4. Go 학습 이룬 것
  233. 233. 4. Go 학습 이룬 것 마지막으로 이번 한글라이즈 재제작을 통해서 이룬 가장 큰 성과는
  234. 234. 4. Go 학습 이룬 것 Go를 많이 익힐 수 있었다는 것입니다.
  235. 235. •고루틴은 끝내주는데 •그런데 너무 기능이 적다. •고퍼도 귀엽지 않고 Go 첫인상 Go에 대한 제 첫인상은 이랬죠.
  236. 236. •고루틴은 끝내주는데 •그런데 너무 기능이 적다. •고퍼도 귀엽지 않고 Go 첫인상 고루틴은 끝내주지만 언어의 기능이 너무 빈약해 보였습니다.
  237. 237. •고루틴은 끝내주는데 •그런데 너무 기능이 적다. •고퍼도 귀엽지 않고 Go 첫인상 고퍼도 안 귀엽고요.
  238. 238. • 세밀하게 절제된 최소주의 •실수하지 않도록 도와주는 환경 Go 지금 인상 하지만 지금은, 빈약해 보였던 언어가
  239. 239. • 세밀하게 절제된 최소주의 •실수하지 않도록 도와주는 환경 Go 지금 인상 사실은 세밀하게 절제된 최소주의를 따르는 것이었고
  240. 240. • 세밀하게 절제된 최소주의 •실수하지 않도록 도와주는 환경 Go 지금 인상 언어의 복잡성을 극도로 낮춰서
  241. 241. • 세밀하게 절제된 최소주의 •실수하지 않도록 도와주는 환경 Go 지금 인상 대규모 프로젝트에서도 실수하기 어렵고
  242. 242. • 세밀하게 절제된 최소주의 •실수하지 않도록 도와주는 환경 Go 지금 인상 조금만 신경 써도 좋은 코드를 작성할 수 있게 해준다는 걸 깨닫게 됐습니다.
  243. 243. • 세밀하게 절제된 최소주의 •실수하지 않도록 도와주는 환경 • 고퍼는 여전히 귀엽지 않다. Go 지금 인상 그래도 고퍼는 여전히 귀여워 보이지 않지만요.
  244. 244. •패키징 •테스팅 •문서화 •정규표현식 •파서 • 플러그인 • 성능 최적화 Go에 자신감 이번 경험으로 Go의 여러가지 영역에 입문했고
  245. 245. •패키징 •테스팅 •문서화 •정규표현식 •파서 • 플러그인 • 성능 최적화 Go에 자신감 덕분에 Go 코딩에 자신감도 많이 붙었어요.
  246. 246. - [한글라이즈 재제작기] 이흥섭(넥슨 왓 스튜디오) 〈한글라이즈〉는 외국어 단어를 외래어 표기법에 따라 한글로 옮겨 적어주는 도구입니다. "Espresso"를 "에스프레소"로, "東京 "을 "도쿄"로 변환할 수 있죠. 본래 Python으로 구현했던 한글라 이즈를 Go로 재구현하면서 겪은 경험과 느낀점을 공유합니다. Go에 근자감 어떻게 보면 근자감일 수도 있는데 결국 이 발표까지 하게 됐습니다.
  247. 247. - [한글라이즈 재제작기] 이흥섭(넥슨 왓 스튜디오) 〈한글라이즈〉는 외국어 단어를 외래어 표기법에 따라 한글로 옮겨 적어주는 도구입니다. "Espresso"를 "에스프레소"로, "東京 "을 "도쿄"로 변환할 수 있죠. 본래 Python으로 구현했던 한글라 이즈를 Go로 재구현하면서 겪은 경험과 느낀점을 공유합니다. Go에 근자감 이번 발표를 준비하면서
  248. 248. - [한글라이즈 재제작기] 이흥섭(넥슨 왓 스튜디오) 〈한글라이즈〉는 외국어 단어를 외래어 표기법에 따라 한글로 옮겨 적어주는 도구입니다. "Espresso"를 "에스프레소"로, "東京 "을 "도쿄"로 변환할 수 있죠. 본래 Python으로 구현했던 한글라 이즈를 Go로 재구현하면서 겪은 경험과 느낀점을 공유합니다. Go에 근자감 명확하게 정리하지 않고 넘어갔던 부분도 명확하게 이해할 수 있었고
  249. 249. - [한글라이즈 재제작기] 이흥섭(넥슨 왓 스튜디오) 〈한글라이즈〉는 외국어 단어를 외래어 표기법에 따라 한글로 옮겨 적어주는 도구입니다. "Espresso"를 "에스프레소"로, "東京 "을 "도쿄"로 변환할 수 있죠. 본래 Python으로 구현했던 한글라 이즈를 Go로 재구현하면서 겪은 경험과 느낀점을 공유합니다. Go에 근자감 한글라이즈를 더 많이 발전시킬 수 있는 기회가 되기도 했습니다.
  250. 250. - [한글라이즈 재제작기] 이흥섭(넥슨 왓 스튜디오) 〈한글라이즈〉는 외국어 단어를 외래어 표기법에 따라 한글로 옮겨 적어주는 도구입니다. "Espresso"를 "에스프레소"로, "東京 "을 "도쿄"로 변환할 수 있죠. 본래 Python으로 구현했던 한글라 이즈를 Go로 재구현하면서 겪은 경험과 느낀점을 공유합니다. Go에 근자감 발표 준비하는 동안 한글라이즈 코딩도 평소보다 더 많이 했던 것 같아요.
  251. 251. I GO 요즘은 매일 일과후에 Go로 코딩하는 것을 취미로 삼고 있는데
  252. 252. I GO Go는 놀이처럼 느껴질 정도로 코딩하기 즐거운 언어였습니다.
  253. 253. I GO 이렇게 Go 프로그래머 분들을 많이 만날 수 있는 기회가 생겨서 설레고
  254. 254. I GO 여러분과 교류하며 더 많이 배우고 싶습니다.
  255. 255. I GO 좀 더 이야기 나누고 싶은 분은 sub@subl.ee로 이메일 보내주세요.
  256. 256. 감사합니다! 이 제작물은 아모레퍼시픽의 아리따 돋움, 그리고 서울남산체, 조선일보명조, 스포카한산스, Ubuntu Mono를 사용하여 디자인 되었습니다. 제가 준비한 발표는 여기까지입니다.
  257. 257. 감사합니다! 이 제작물은 아모레퍼시픽의 아리따 돋움, 그리고 서울남산체, 조선일보명조, 스포카한산스, Ubuntu Mono를 사용하여 디자인 되었습니다. 끝까지 읽어주셔서 감사합니다.
  258. 258. 김영호, 김찬웅, 김향아, 박종성 만든이 도와주신 분 이흥섭

×