JSのオブジェクトの分割代入で既定値が割り当てられるのはundefinedの場合のみ
「JSのオブジェクトの分割代入で既定値が割り当てられるのはundefinedの場合のみ」ということは、MDNのAssigning to new variable names and providing default valuesにAssigned a default value in case the unpacked value is undefined.
と書いてあることや、13 ECMAScript Language: Expressionsの13.15.5.3 Runtime Semantics: PropertyDestructuringAssignmentEvaluationに4. If Initializeropt is present and v is undefined, then
とあることから分かる。
ここからNode.jsのfs.readを使ってみていく。
node/lib/fs.js#L605~ に以下のコードがある。
function read(fd, buffer, offsetOrOptions, length, position, callback) { ... } else if (arguments.length === 3) { // This is fs.read(fd, bufferOrParams, callback) if (!isArrayBufferView(buffer)) { // This is fs.read(fd, params, callback) params = buffer; ({ buffer = Buffer.alloc(16384) } = params ?? kEmptyObject); } ... ... ({ offset = 0, length = buffer.byteLength - offset, position = null, } = params ?? kEmptyObject); ... validateBuffer(buffer); ...
例えばfs.read(fd, {offset: 1}, callback)
というように呼び出すと、
params = {offset: 1}
({ buffer = Buffer.alloc(16384) } = {offset: 1}
buffer = Buffer.alloc(16384)
というように処理される。
もしfs.read(fd, {buffer: null}, callback)
というように呼び出すと、
params = {buffer: null}
({ buffer = Buffer.alloc(16384) } = {buffer: null}
buffer = null
というように処理される。
この場合、validateBuffer
が呼びされる前にlength = buffer.byteLength - offset
でbuffer = null
が参照されてしまう。
これを解決したのがこのPR。
github.com
ちなみにdeno_stdは現時点でNodeのv18.8.0との互換を保っているため、以下のように意図的にnullを参照するようにしている。
github.com
// @ts-ignore: Intentionally create TypeError for passing test-fs-read.js#L87 length = opt.buffer.byteLength;
RFC8259 の「Implementations MUST NOT add a byte order mark to the beginning of a networked-transmitted JSON text」について
『プログラマのための文字コード技術入門』の p.216 にある、
「JSONでは、データ先頭にBOMをつけないことが求められています(RFC8259)」
についてちょっとだけ詳しく調べる。
RFC8259の該当箇所は8.1. Character Encodingで、
「Implementations MUST NOT add a byte order mark (U+FEFF) to the beginning of a networked-transmitted JSON text」
とある。
続いて
「In the interests of interoperability, implementations that parse JSON texts MAY ignore the presence of a byte order mark rather than treating it as an error」
とあるように、相互運用性の観点から、BOMがあればエラーとして扱うのではなく、無視してもいいらしい。
BOM(Byte Order Mark)とは、ビッグエンディアンかリトルエンディアンのどちらのバイト順を採用しているかを示すために、データの先頭に付ける印のこと。
U+FEFF
の符号位置を用い、ビッグエンディアンではFE FF
、リトルエンディアンではFF FE
という2バイトの列になる。
符号位置は、文字コード表の中の位置のこと。
ビッグエンディアンは、上位8ビットが先頭にくるバイト順、リトルエンディアンは、下位8ビットが先頭にくるバイト順のこと。
U+FEFF
のU+
は、Unicodeの符号位置を表すのに付ける接頭辞。
16ビットのデータを8ビット単位のバイト列にするときには、ビッグエンディアンかリトルエンディアンのどちらを採用するかの問題がある。
Unicodeの符号化方式の中の1つであるUTF-16は、16ビット単位であるためこの問題が発生し、解消するためにBOMを付けることがある。
JSONでBOMを付けないとなると、バイト順の区別はどうするのだろうと思ったところ、同じ RFC8259 8.1. Character Encoding にこう書いてあった。
「JSON text exchanged between systems that are not part of a closed ecosystem MUST be encoded using UTF-8」
UTF-16を扱わないのにRFCなぜBOMに言及しているのだろうと思ったところ、『プログラマのための文字コード技術入門』の p.154 にこう書いてあった。
「バイト順が問題になることのないUTF-8にはBOMは本来関係がありません。ところが、BOMをUTF-8で表現した3バイトの値(EF BB BF)が、UTF-8のデータ列の先頭についていることがあります。バイト順の印という元々の意味を離れて、UTF-8のデータであることの印として利用価値があるという考えもあります。」
ネットワーク転送されるJSONにはその利用価値は必要なく、相互運用性に支障をきたすから、「Implementations MUST NOT add a byte order mark to the beginning of a networked-transmitted JSON text」とされている。
BOMに関する実際の問題の例として、node-fetchのHandling a BOM with .json() #541というissueにある、レスポンスをJSONとして取得する際にエラーが起こるという問題がある。
node-fetchの`.json()`には内部的に`JSON.parse`が用いられており、引数のtextにBOMが含まれていれば、そこでエラーが生じてしまう。JSONの構文はRFC8259に準拠しているからだ。
ユーザー側の解決例としてはここにあるように、textとして取得してから、BOMに当たる0xFEFF
を除去し、JSONにする例がある。
node-fetch側のリリースされている解決法は、fix: handle bom in text and json #1482にあるように、内部でbuffer.toString()
ではなくTextDecoder().decode()
を使うようにしている。
buffer.toString()
では、BOMに当たる0xFEFF
があったとしてもそのまま文字列化してしまい、それをJSON.parse
するためエラーが生じる。
TextDecoder()のパラメータであるutfLabel
のデフォルトは"utf-8"
。
そして重要なのがデフォルトがfalse
のTextDecoder.ignoreBOM。
命名がややこしいが、TextDecoder.prototype.ignoreBOM not working as expectedの回答に
「If on the other hand you want it to be removed from the output, then leave it as false, the parser will treat it specially, and remove it from the output」
とあるように、TextDecoder().decode(buffer)
の結果にはBOMが含まれなくなり、問題なくJSON.parse(text)
ができる。
このように目に見えないBOMと戦ってJSONが生成される例がある。
野良コミッターがOSSの仕様を決める
OSS活動を始めて今まで157PRがCloseされた。翻訳やtypoの修正やテスト、ドキュメントの整備も含まれるけど、野良コミッターでも仕様を決める、もしくはそれに近いことができたのでいくつかまとめておく。
仕様の変更の後継をする
ESLintで最新のecmaVersionを"latest"で指定できるようになったことを受け、TypeScript ESLintでも"latest"指定できるようにするというfiskerさんのPRが詰まっていたのを発見し、実装を進めた
開発の方針を決める
denoland/deno_std/node/_tools/test
に生成されるNodeのバージョンを上げた
github.com
denoland/deno_std/path
とdenoland/deno_std/node/path
の関係を整理して進めていった
github.com
こんな感じでorgに所属してない、コミッター認定されてない野良コミッターでも頑張れば割と大きい部分の仕様に関わることができる。
GitHubでの活動はGitHubの登場はコミッタを特権階級から追い落とす革命だった - Hello, world! - s21gのタイトル通り、何の資格も持っていない文系大学生にとっては飛び道具だった。
日本の方のSocket migration for SO_REUSEPORTという発表を知って、こういうことができるようになりたいと思った。
こういうこととはどういうことかを考えると、世界の仕様を決めるということで、今まで自分が何をできてきたのかをまとめたくなって書いた。
逆に何が足りないかを再認識できてきたので、それを踏まえて活動を進めていく。
機械学習の知識・2022春
- 数IA,IIBはセンター試験8~9割とってた記憶
- 数IIIは以下のように独学した
- 以下の本は何周もして理解を進めてきた
- ディープラーニングの理論と実装の理解を進めてきた
- OSS活動の指標の一つとしてInterested in AI Frameworksを掲げ、まずはGiNZAにコミットした
github.com
wafuwafu13.hatenadiary.com
- 統計検定1級に落ちたことがある
- Kaggleはタイタニックだけしたことがある
- コンペに参加する予定
- 英語: 論文読んだりはまだできていない スピーキングはDMM英会話で修行中
- 以下の本は何度か理解しようと試みてきたが進捗はイマイチ
- その他読んで面白かった本
GiNZAにコミットしながらNLP Libraryの勉強
環境構築
GiNZA - Japanese NLP Library | Universal Dependenciesに基づくオープンソース日本語NLPライブラリを眺めてとりあえず$ pip install -U ginza ja_ginza
を試してみると以下のエラー。
raise VersionConflict(dist, req).with_context(dependent_req) pkg_resources.VersionConflict: (setuptools 49.2.1 (/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages), Requirement.parse('setuptools>=58.0')) [end of output] note: This error originates from a subprocess, and is likely not a problem with pip. error: subprocess-exited-with-error × Getting requirements to build wheel did not run successfully. │ exit code: 1 ╰─> See above for output.
setuptoolsのバージョンの問題っぽかったから$ pip install setuptools --upgrade
してから$ pip install -U ginza ja_ginza
やり直しても同じエラー。
$ pip install setuptools --upgrade Defaulting to user installation because normal site-packages is not writeable Requirement already satisfied: setuptools in /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages (49.2.1) Collecting setuptools Using cached setuptools-62.1.0-py3-none-any.whl (1.1 MB) Installing collected packages: setuptools Successfully installed setuptools-62.1.0
仕方ないから https://pypi.org/project/setuptools/#filesからダウンロードしてインストールしてみる。良さそう。
$ sudo python3 setup.py install Installed /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages/setuptools-62.1.0-py3.8.egg Processing dependencies for setuptools==62.1.0 Finished processing dependencies for setuptools==62.1.0 /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages
元から入っていたsetuptoolsは削除する。
/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages $ sudo rm -r setuptools
これで$ pip install -U ginza ja_ginza
は成功する。
コミット
https://github.com/megagonlabs/ginza#development-environmentに沿って環境構築をして、コードを眺めて気になったところに修正コミット。
github.com
github.com
https://github.com/megagonlabs/ginza#run-testsに沿ってテストを走らせてカバレッジを上げるコミット。
github.com
split_mode
深堀
既存のコマンドラインのテストによるとsplit_mode
にA
, B
, C
を選択でき、それぞれ以下のように形態素解析がされる。
("A", "機能性食品", ["機能", "性", "食品"]), ("B", "機能性食品", ["機能性", "食品"]), ("C", "機能性食品", ["機能性食品"]),
さらにコードを追っていくと、split_mode
をセットしている箇所が2箇所ある。
1箇所目では、output_format
にmecabが指定されている場合、SudachiPyのSplitModeの値が設定される。
https://github.com/megagonlabs/ginza/blob/d60d3d4bf4af0c5879357a5f95b36f72ea8eb317/ginza/analyzer.py#L67-L68
https://github.com/megagonlabs/ginza/blob/d60d3d4bf4af0c5879357a5f95b36f72ea8eb317/ginza/analyzer.py#L23-L28
def try_sudachi_import(split_mode: str): """SudachiPy is required for Japanese support, so check for it. It it's not available blow up and explain how to fix it. split_mode should be one of these values: "A", "B", "C", None->"A".""" try: from sudachipy import dictionary, tokenizer split_mode = { None: tokenizer.Tokenizer.SplitMode.A, "A": tokenizer.Tokenizer.SplitMode.A, "B": tokenizer.Tokenizer.SplitMode.B, "C": tokenizer.Tokenizer.SplitMode.C, }[split_mode] tok = dictionary.Dictionary().create(mode=split_mode) return tok ... def set_nlp(self) -> None: if self.output_format in ["2", "mecab"]: nlp = try_sudachi_import(self.split_mode) ...
SudachiPyの実装をさらに追うと、mode
として渡され、以下のように分岐される。
https://github.com/WorksApplications/SudachiPy/blob/6fb25bd20e206fc7e7e452b2dafdb2576dcf3a69/sudachipy/tokenizer.pyx#L127
https://github.com/WorksApplications/SudachiPy/blob/6fb25bd20e206fc7e7e452b2dafdb2576dcf3a69/sudachipy/tokenizer.pyx#L172-L180
def tokenize(self, text: str, mode=None, logger=None) -> MorphemeList: """ tokenize a text. In default tokenize text with SplitMode.C Args: text: input text mode: split mode ... """ ... def _split_path(self, path: List[LatticeNode], mode: SplitMode) -> List[LatticeNode]: if mode == self.SplitMode.C: return path new_path = [] for node in path: if mode is self.SplitMode.A: wids = node.get_word_info().a_unit_split else: wids = node.get_word_info().b_unit_split
LatticeNode#get_word_info
はWordInfo
を返す...と続いていくが詳細には追えなかった。
WordInfo
のコンストラクタにa_unit_split
とb_unit_split
があることまで確認できた。
... cdef class LatticeNode: ... def get_word_info(self) -> WordInfo: if not self._is_defined: return UNK if self.extra_word_info: return self.extra_word_info return self.lexicon.get_word_info(self.word_id)
2箇所目では、spaCyで何かしらのモデルがロードされた後、set_split_mode(nlp, self.split_mode)
が呼び出される。
... def set_nlp(self) -> None: ... else: # Work-around for pickle error. Need to share model data. if self.model_name_or_path: nlp = spacy.load(self.model_name_or_path) else: try: nlp = spacy.load("ja_ginza_electra") except IOError as e: try: nlp = spacy.load("ja_ginza") except IOError as e: raise OSError("E050", 'You need to install "ja-ginza" or "ja-ginza-electra" by executing `pip install ja-ginza` or `pip install ja-ginza-electra`.') if self.disable_sentencizer: nlp.add_pipe("disable_sentencizer", before="parser") if self.split_mode: set_split_mode(nlp, self.split_mode) ...
https://spacy.io/api/language#factoryによると@Language.factory
でLanguage.add_pipe
ができて...みたいな説明があった。
https://github.com/megagonlabs/ginza/blob/d60d3d4bf4af0c5879357a5f95b36f72ea8eb317/ginza/__init__.py#L48-L54
https://github.com/megagonlabs/ginza/blob/d60d3d4bf4af0c5879357a5f95b36f72ea8eb317/ginza/__init__.py#L111-L114
@Language.factory( "compound_splitter", requires=[], assigns=[], retokenizes=True, default_config={"split_mode": None}, ) ... def set_split_mode(nlp: Language, mode: str): if nlp.has_pipe("compound_splitter"): splitter = nlp.get_pipe("compound_splitter") splitter.split_mode = mode
compound_splitter
はginza/compound_splitter.py at develop · megagonlabs/ginza · GitHubがあるからおそらくカスタム関数をパイプラインに追加してそうだけど、まだPipelinesがよくわかってないから勉強してから出直す。
spacy.io
www.youtube.com
とりあえずNLP Libraryの実装の雰囲気が掴めたから終わり。
TOEIC 675 から 825 に上げた
1年3ヶ月前は市販模試を3回分やって受けて675(305/370)だった。
今回は真面目に対策をして受けてみることにした。勉強期間は半日(3時間くらい)を1ヶ月半くらい。
リスニング対策は「極めろ!リスニング解答力TOIEC L&R TEST」をした。
692ページあるから忍耐力が必要だった。
リーディングのPart5対策は「TOIEC L&Rテスト文法でる1000問」をした。
1000問解く必要があったのかはわからない。
Part6,7 は英文を読むことに慣れる必要がありそうだったから「英文解釈教室」を読んだ。
文法のことが大体わかる良書。
長文を読むことに慣れるために「英文 詳説世界史 WORLD HISTORY for High School」を読んだ。
日本語の教科書があったから英語->日本語(流し読み)をした。世界史の復習にもなるから一石二鳥。
あとはSRE本の英語版が公開されているからこれも日本語と合わせて読んだ。
仕上げに市販の模試「新メガ模試1200問」を解いた。
スコアは1問5店換算で750(390/360) -> 745(425/320) -> 810(425/385) -> 790(400/390) -> 845(420/425) -> 800(415/385) だったから本番で思ったより取れてた。
リーディングは時間が余って自信があったけどそこまで伸びていなかった。
リスニングは単に問題の形式とか傾向に慣れて伸びた部分が多いと思う。
ライティングとスピーキングの学習動機としてTOEFLを検討してるけど3万円に見合うかどうか...
株式会社はてなに入社しました
株式会社はてなに入社しました
2.5年目