見出し画像

tokenとtokenizerとSwiftを作ったクリス・ラトナー

トークンっていう言葉、日本人にはピンとこないと思うんですよ。

でもアメリカだと、これはごく普通のモノの名前なんですね。向こうのコインシャワーやコインランドリーは、本物のお金を入れるんじゃなくて、ゲームセンターのメダルみたいなものを入れて使います。これをトークンと呼びます。まず窓口でお金とトークンを交換して、そのトークンを機械に入れる。

なんでそんな面倒なことをしているかというと、機械にお金が溜まっていると、そこを壊して盗まれるからです。トークンしか入っていなければ盗む価値がない。

つまり向こうの人にとってトークンは「本物のお金の代わりに使う、その場でしか通用しないコイン」です。それをそのまま持ってきたのが AI のトークンという言葉です。トークナイザーのほうも、綴りを見ればわかる通り「トークンにするもの」くらいの意味合いです。

トークナイザーは言葉と番号を置き換えている

こういう言葉のイメージを先に持っておくと、トークナイザーの仕組みの説明もわかりやすくなります。要するに、言葉と番号を置き換える仕組みなんですね。

この番号表は人間が作っているわけじゃなくて、数が多いものから順に、ソフトが適当に番号を振っていく。単語が数字に置き換わるので、コンピューターのメモリの中ではものすごく情報が圧縮された状態になります。

バイブコーディングをする人にとっては、この感覚がわかっているといいと思います。予約語がすごく長かったとしても、トークナイズすれば番号になっている。複数のプログラミング言語に同じ意味合いの言葉があったとして、それぞれ数字になっているわけです。だから文字数の長さでコンテキストウィンドウの消費が大きくなったりはしません。

変数名は略さないほうがトークンは少ない

コンテキストウィンドウの消費を節約しようとして変な略語を使う人はいると思うんですよ。でも実際は、キャメル記法で普通に単語がフルに書いてある状態のほうが、トークン数としては短くなる傾向にあります。

じゃあ略語が全部ダメなのかというと、そうでもないんですね。気になったので実際にトークナイザーに通して数えてみました。

  • configuration / config / cfg — どれも1トークン

  • temporary / temp / tmp — どれも1トークン

  • message / msg — どちらも1トークン

  • database / db — どちらも1トークン

一般的な略語は、フルスペルと同じで全部1トークンでした。文字数が3分の1になっても消費は変わらない。関数名で見ても、initializeAuthentication と initAuth はどちらも2トークンです。

損をするのは、誰も使っていない略し方をしたときだけでした。

  • calculateTotalPrice — calculate / Total / Price で3トークン

  • calcTtlPrc — calc / T / tl / P / rc で5トークン

文字数は減っているのにトークンは増えています。Ttl や Prc みたいな綴りは番号表に載っていないので、1文字ずつバラけるわけです。

とはいえ、書くたびに「これは一般的な略語か、自分だけのオリジナルか」なんて考えてもしょうがないんですよ。だったら普通にキャメル記法で、単語を省略せずにフルに書けばいい。後で読んで読みやすいですし、文字数が増えたところでコンテキストウィンドウを圧迫するわけじゃない。読みやすいようにコードを書くほうが、後々得です。

コードを一切自分で手入力しないタイプのバイブコーディングをやっている人には、あんまり関係ない話かもしれません。でも自分で手を動かして勉強しつつバイブコーディングもしつつ、みたいなタイプの人は、その辺を意識しておくと効率がよくていいんじゃないかなと思います。

Swift はやたら丁寧に書く言語

ここまで来たところで紹介しておきたいのが Swift 言語です。手入力すると本当にめんどくさいと思うくらい、ラベルもきちんとつけて、文章みたいに読めるように書く言語なんですよ。

読んだことねーよという人のために、サンプルはこんな感じです。

func transferMoney(amount: Int, from sender: Account, to receiver: Account) throws {
    guard sender.balance >= amount else {
        throw TransferError.insufficientFunds
    }
    sender.balance -= amount
    receiver.balance += amount
}

try transferMoney(amount: 5000, from: aliceAccount, to: bobAccount)

使ったことがなくても、まあまあ意味は捉えられるんじゃないでしょうか。特に最後の呼び出しているところ、金額いくらを誰から誰へ、と英語の文章みたいに読めます。すごく丁寧な言語だと思いませんか。

で、ここまでに紹介してきたトークナイザーの仕組みからすると、Swift はこんな書き方をしていても、猛烈にコンテキストウィンドウを消費するという感じじゃないんです。

ちょこちょこ note の記事でも書いてるんですけど、Swift だとバイブコーディングがうまくいくことが多いんですよ。

なので大規模なソフト、Rust で2万行いくようなものを作るときは、最近はテスト的にこんなやり方をしています。まず Swift で一旦完成させる。プロトタイプって言うと変な言葉ですけど、試作じゃなくて完成品まで作っちゃう。そのあとで、わざわざ Rust にもう一度コンバートする。

なんでそんな手間をかけるかというと、Swift だと使える場所が macOS だけになっちゃうからです。マルチプラットフォームにするには、結局どこかで作り直すことになる。

もう一つの理由は、Swift だとそのくらいの規模のソースコードを作っても破綻がないことです。暴走列車みたいにめちゃくちゃになったりもしないし、そこからさらに機能を追加しても破綻しない。

体感的には、TypeScript より Rust のほうが破綻しにくくて、Swift はさらに破綻しにくい。ただ Rust もだいたい1万行くらいで苦しくなってきます。そんなわけで、Swift でお手本になるまるっきり同じ機能のものを作ってまでして、Rust で作り直すということをやっています。どうしても必要なケースでは、そこまでやる。

Swift だと破綻しない理由

コンテキストウィンドウを何パーセント消費しているかを見ている感じだと、体感では Swift のほうが TypeScript よりやや消費が早いなという印象です。

でもそれでいて、難しいものを作っていても、ぐちゃぐちゃの暴走列車みたいになることが少ないんですよ。A を直したら B が壊れて、B を直したら A が壊れて、というのがぐるぐる回り続ける状況になりにくい。

たぶん、その行だけ読めばわかるように書いてある言語なので、局所的な理解ができていれば失敗しない、という傾向があるんだと思います。Claude Code に聞いてみても、同じようなことを言ってました。

Claude によると、以下のような理由で Swift は破綻しづらいという説明をされました。

一つめは、その行だけ読めば意味がわかるように書いてあること。引数にラベルがついていて、型も書いてあるので、離れた場所を見に行かなくて済む。AI は離れた場所を参照するのが苦手なので、その場で完結している記述が多いほど間違えにくい。

二つめは、コンパイラが厳しいこと。値が入っていないかもしれない変数は必ず開かないと使えないし、条件分岐に漏れがあると通らない。AI が雑に書いた瞬間にコンパイルが止まるので、間違ったまま先に進まない。

三つめは、変数が既定で書き換え不可なこと。途中で中身が変わっているかもしれない、という疑いを持たなくていいぶん、追いかける範囲が狭くて済む。

逆に Rust が1万行あたりから苦しくなるのは、この裏返しらしいです。所有権や借用は、その行が正しいかどうかが離れた場所の事情で決まる。コードを足すと既存の部分が壊れることがあって、関係が増えるほど絡み合いも増えていく。Swift はそのへんを全部ランタイムが引き受けてくれるので、増えない。

Swift と LLVM は同じ人が作っている

個人的な感想で言うと、LLVM IR をもっと読みやすくしたような形が Swift なんじゃないか、という感覚があります。

LLVM IR というのは、コンパイラが機械語を吐く前に一度通す中間の形式です。値ごとに型が必ず書いてあって、前の行を見に行かなくてもその行だけで意味が確定するように作られている。誰も誤解しようがない書き方です。

で、これ調べてみたら納得したんですけど、Swift を作ったのはクリス・ラトナーという人で、LLVM を作ったのも同じ人なんですよ。大学院時代に LLVM を始めて、その後 Apple に入って Swift を立ち上げている。中間表現を当たり前のものとして体に入れている人が、そのまま人間向けの言語を設計した。曖昧さを残さない書き方が骨の髄まで染みているんでしょうね。

もう一つ、引数にラベルをつけて文章みたいに読ませるところは Objective-C から来ています。あれももともと [account transferMoney:amount from:sender to:receiver] みたいに書く言語でした。Swift はその文化を引き継いでいます。

つまり「読んで文章になる」ところは Objective-C から、「型で全部確定させて曖昧さを残さない」ところは LLVM を作った人の性格から。両方が合わさってああなったんだと思います。それが結果的に AI と相性がよかった、ということなのかなと。

ちなみに私、Objective-C は書けるんですけど、Swift は自分で入力して書くことはできません。今さら手入力で Swift でプログラミングするとかは、さすがに考えてないです。

でももともとすごく読みやすい言語なので、書けなくても読めればいいかなという感じで、こんなことに使っています。

#トークン #トークナイザー #Swift #バイブコーディング #AI開発 #プログラミング #ClaudeCode #生成AI #Rust #LLVM

いいなと思ったら応援しよう!