kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

「プログラミング言語開発」教育用言語MinisにJSONベースの具象構文を付け足してみた

 皆様、お久しぶりです。去る2月10日(土)、2月11日(日)に筑波大学情報科学類にて特別講義の講師をやってきました。といっても、私が全日担当したわけではなくOB一人が一コマを自分の得意分野について講義をするオムニバス形式のものです。

 私はといえば去年やったのと同様、JavaScriptで抽象構文木を「手で」組み立てて解釈・実行するプログラミング言語Minisとその処理系を作るという講義を行いました。講義当日はスライドにミスがあることに途中で気づいたり色々あってテンパりましたがそれはそれとして。

 元々、私が担当した「プログラミング言語作成概論」の趣旨は

プログラミング言語を作るというのはとても簡単な作業なのに、プログラマにすらあまり知られていないのはけしからん。
とはいえ、実際に作ってみせないと実感が湧かないのが人情。
抽象構文木をJavaScript上で組み立てて、それをevalする関数を書くことを通してインタプリタの作成方法を学ぼうじゃないか!

 的なものです。たとえば、以下のプログラムは講義で作ったプログラミング言語Minisのもの(実際にはこれそのものを解析する構文解析機はまだ作ってませんが)です。MinisのことはわからなくてもC系言語を一つかじったことがあればおおよその意味はわかるかと思います。

    a = 100;
    b = 200;
    if(a < b) {
      500;
    } else {
      1000;
    } // 500が返ってくる

 しかし、これくらい単純な構文を持ったプログラミング言語でさえ「構文解析」というプログラミング言語にとって本質でないものが挟まるせいで無駄に行数が増えるのが現実です。ならばということで、以下のように直接抽象構文木を書かせれば(ナイーヴな)インタプリタの本質であるevalの記述に専念できるじゃあないかというのが講義を通して伝えたかったことでした。

    const expression = tSeq(
        tAssign('a', tInt(100)),
        tAssign('b', tInt(200)),
        tIf(tLt(tId('a'), tId('b')), tInt(500), tInt(1000)),
    ); // 上のプログラムをJSの関数呼び出しを通して構築したもの
    expect(evaluate(expression, {})).toBe(500);

 さて、多少プログラミング言語に心得のある皆様ならおわかりのようにこのアプローチは問題なく機能しますが、ただ、せっかくプログラミング言語を作ったのだから構文も設計したいのが人情です。比喩でいえば GUICUIかはプログラムの本質ではないけど、GUIの方が結果がわかりやすくていいよねみたいな。

 というわけで今年は具象構文も追加することにしたのですが、しかし、構文解析の話をするには時間もありませんし、概念を理解するのも結構手間です(難しいものではないですが)。というわけで間をとってJSONでMinisの構文を書けるようにしたのでした。たとえば、上記のプログラムをMinisのJSON形式で記述すると以下のようになります:

{
  "type": "seq",
  "expressions": [
    {"type": "assign", "name": "a", "value": 100},
    {"type": "assign", "name": "b", "value": 200},
    {"type": "if", 
      "condition": {"type": "<", "operands": [{"type": "id", "name": "a"}, {"type": "id", "name": "b"}]},
      "then":  500,
      "else": 1000
    }
 }

しかし、見ればわかる通り、元の抽象構文木をそのまま書き下したときより見づらくなってしまっています。

このJSON形式の構文を書き下したのは講義よりしばらく前でしたが、これだとちっとも嬉しくない……というわけで、JSONJSONでももっと簡潔に書けるように考え直しました。

上記のJSON形式の問題点はすべての式をJSONのオブジェクト(あるいは値)として表現してしまっているせいで、必ずラベルが必要になる点です。しかし、よくよく考えればJSONを使うからといって別にオブジェクトを使う必要はないわけで、以下のように配列形式でいいわけです。

[
  ["assign", "a", 100],
  ["assign", "b", 200],
  ["if", ["<", ["id", a"], ["id", "b"]]
    500,
    1000]]

かなりMinisのプログラムを簡潔に書けるようになりました。と、ここで「それってほとんどS式では?」というツッコミが聞こえてきそうですが、概ねその通りです。強いていうとS式と違ってJSONにはシンボルがプリミティブにないために、余計なダブルクオートが必要になる程度でしょうか。

ともあれこのようにしてJSONでプログラムを表現してあげれば、後は簡単。JSON.parse(str)で入力文字列をJSONとしてパーズした後は、抽象構文木に変換するロジックさえ書いてあげれば既存のMini言語に具象構文を与えることができます。

プログラミング言語Minisはナイーヴなインタプリタや上記のJSON構文を解釈できるものも含め、以下のリポジトリからソースを取得することができます。もし興味が湧いたら触っていただければと思います。単純な言語とはいえどこかにバグがある気もするので、バグ報告も歓迎します。ではでは。

github.com

ネット小説を書き始めて三年以上経って気づいたこと:鯛粗は重要

 元々、幼少期から私は全くもって特に小説家あるいはクリエイター志望ではありませんでした(一貫してたのは学究路線の方)。その一方で、年齢を重ねるに連れて、色々な娯楽を読み耽るほどに「なんでこの終わり方なんだよ!」とか「ここでこのキャラを退場させなくていいだろ」とか「せっかく仲良し方向の路線だと思ってたのに、どうしてギスギスに……」という思いが募っていく一方でした。

 そんなとき、ふと、ネット小説家界隈で言われたことの一つを思い出したのでした。要旨だけを抜きだすと「じゃあその君のモチベーションを軸にして実際に、自分が納得の行く物語を作ってみなよ。もしそれが読者の心に響いたのならきっと読まれるよ」というものです。

 そういう言葉を聞いてやってみるか、やってみないかは当然個人の自由ですけけども、私自身はネット小説「論評」界隈の「読んだことも、書いたこともなく」、どうせなろうってこうでしょうっと論評する一部……でない人たちの不誠実さは好ましく思っていませんでしたから、正当な論評をするためにも、もちろん、自分が好きな物語を作るためにも小説を書き始めてみようと一念発起してアカウントを作成したのが、確か2019年の夏頃でしょうか。

 ちなみに、ネット小説といってもなろう一強の時代はとうに過ぎており、後発のカクヨムはある意味で既になろうを追い越している部分もありますし。他にもアルファポリスなどメジャーな小説投稿サイトはあります。

 さて「とりあえず書くか」とジャンルを決めてなろうに連載を始めたわけですが、読者がとにかく来ない、来ない……。PVを見ると全く読まれてないわけでもないようだけど……みたいな状態が約半年続きました。

 さらに処女作はあまりにもリアリティを重視しすぎてしまったが故に途中で私のほうでスクラップにすることになってしまいました。作者になってみるとわかるんですが「キャラクターが動いてくれない」って心理的に本当につらい。この初作での苦い失敗経験はそのリバイバル版というべき作品でだいぶ改良されることになるのですが、それはともかく。

 処女作はそんな感じで無惨に終わりを迎えましたが、その次に思いつきで書いた数十話程度の長編(中編?)小説は一部の熱心な読者が毎話コメントつけてくださったおかげで、無事に主人公たちにきちんとハッピエンドを迎えさせてあげられました。今でもその読者さんは私の作品に「いいね」を入れてくれたり、場合によって感想書いてくださるのですが、彼(彼女?)のコメントがなかったら挫折してただろうなーと思うと思い出深いものです。

 こうして(心情的な意味での)処女作はなんとか無事ハッピエンドを迎えることができました(カクヨムで★160以上、最終話PVが1217なので、心情的な処女作としてはなかなかものかもしれません)。ちなみに最終話PVは最終話「だけ」読んだ人がカウントされる可能性があるので注意ですが、最終話直前あたりで1154PVなことと他の話数でのPVを考えると、1000名近い読者の方が読んでくれたことになります。

 ネット小説の世界というのは消費が非常にはげしいので「流し読みしただけの人」たとえば、1話1分でほんとうに「あらすじ」だけ見るような大味な読み方をした人もカウントされることの留意しないといけませんが、ともあれまあまあだったのではないかなと。

 心情的な意味での処女作はまずまず成功しましたが、そうすると次も書いてみたくなるのも人情というもの。詳細を書くと色々バレそうなのですが、次ではもうちょっと思春期っぽい話を70話超で描いてみることにしました。ヒロインの特徴とか描いちゃうとこれまた割と簡単に検索でヒットしそうなのでぼやかしておくと「方言」が一つのキーワードと言えましょうか。

 こちらの作品はまた、前とは別の、すっっっごく熱心な読者さん(+前作でついた読者さん)がどんどん応援コメントや応援レビューくださったこともあって、ラスト数話は睡眠時間を削って完成にこぎつけました。こちらはカクヨムで★230以上、最終話PVが1200くらいでしたが、読者さんの熱量という意味では(SNSからの反応も一部に)圧倒的にこちらが上でだいぶ自信がついたものでした。

 応援コメントで「続きが読みたいです」が割と真剣に多かったので実際に後日談を別作品で作ってしまったくらいです……。

 ともあれ、そうしてだらだらと創作活動を続けて三年以上になります。

 長編も10話以上完結させて、短編も多数生産して(短編は生産数が多すぎて、他の作者には見られない異常な数値を叩き出しているので、書くとやはりわかりそうなので控えます)。カクヨムでは★100くらいは普通に(下記始めの頃は、★100もらえるくらいの作品を書くのが最初の目標とも言えました)、なろうの短編でもジャンル別日間ランキング5位以内にはうまくやれれば(1位に入ったことも)入れるくらいには書き慣れてきたのかなと思うところです。

 正味の固定読者数は推計ですが、なろうの方で少なくとも約300(逆お気に入りの数から推計)、カクヨムの方はもうちょっと感覚的ですが200~300名くらいはいそうな感じです。そんな中、気づいたことがあるのでちょっと書き留めておこうかなと思ったのでした。

 たとえば、ネット小説の、特になろうでの小説執筆が普通の小説執筆と違うところに「ランキングバトル」という側面があることです。「小説家になろう」では読者が読んだ小説についてポイント形式で投票できるのですが、それにもどついて「総合」「各ジャンル別」について1~100位以内のランキングを一日三回更新されます。そのランキングで上位に上がれば読者の目につきやすくなり、つまるところより読んでもらいやすくなり、ますます評価が得られやすいという好循環が生まれるわけです。私も一時期、短編小説でいかにして日間ランキング5位以内に入るかの手法を考えるのに腐心していた時期があり、ある意味射幸性のあるギャンブルチックな側面ともいえるかもしれません。

 また、ネット小説は特に供給量が豊富な故に、タイトルとあらすじの付け方がまずいと、いくら本文が良かろうが致命的に読まれないということも重要です。仮に恋愛小説を書くとして「僕と彼女のヒトナツの恋」などという平凡なんだかちょっと気取ったのかわからないような題名は言語道断と言っても過言ではありません。

 何故かというと、読者は皆忙しいので、本文を読むべきか切るべきかをできるだけさっさと判断したいからす。半分過ぎてから、これじゃないんだよなと思って時間を無駄にしたくないのです。「僕と彼女のヒトナツの恋」というけど、彼女は病気でこの世を去るのか?それとも、単純に夏に出会った相手との恋なのか?などなど、男性向け恋愛小説について読者が特に知りたいヒロインについての情報量がほとんどこのタイトルが含まれていません。タイパ世代という言葉が頭をよぎりましたが、データ上は世代を問わずに見受けられるようなので、まー人間、娯楽が溢れればそうなるさってことかもしれません。

 いい「タイトル」というのも曲者でして、なろうだけでなくカクヨムでも、結局「タイトルだけでおおまかな内容を連想できるくらいには長くなっていないといけない」という原則は基本的に変わっていません(長文がmustなのではなく内容が連想可能がmustなのがポイントです。これを利用すれば短いタイトルでもうまくハマるのも作ることはできます)。ここで「美しいタイトル」をつけたいと思う、普通の作者の美意識や願望と「いやでも読まれないと意味ないしな」という現実主義との葛藤が生まれるのです。これを読んでる読者の方々、なろうなどの小説における長文タイトルは別に作者が好き好んでそうしたわけではなくて、そうしないと小説が生き残れない、より正確には読まれずに放置されるだけになるという前提条件があるのだと思えば、少しは理解できるのではないでしょうか。それでもなお、私には葛藤が残りますが。

 あとは「あらすじ」もですね。ここでの「あらすじ」は本来の意味でのあらすじとは違うのが曲者で、いわゆる煽り文の類である必要があります。リアル書店に並ぶ書籍は、編集さんが帯のコメントなど色々考えてくださるわけですが、ネット小説家だとこの部分も当然ながら全部自分でやる必要があるというわけです。この「タイトル」と「あらすじ」が重要というのは、ある意味多くの読者に読んでもらうときの記事の書き方の基本みたいなものであって、小説を書き始めてから以前と違う観点で文章を意識するようになった……気がします。

 他にも、なろうのランキングで上位に入ると途端に荒らし感想が増えてくるとか、カクヨムはなろうに比べればだいぶ平和だなとか他にもありますが、今日はとりあえずタイトルとあらすじが重要ということで。

結論は同意できても途中の論理展開がおかしい記事の話

 おはようございます。朝から非常に微妙な気持ちになる記事を見てしまったので、ちょっとそれについて書いてみたいと思います。対象は、

toyokeizai.net

というとても残念な記事です。本題に入る前に常日頃から思うのですが、結論自体は著者と別の理路から同意できなくもないが、途中の論理展開が破綻してる記事って多過ぎません?あくまで感覚ですが、ネットの一応まともな出版社の記事でも、1/10くらいの確率で誇張でなくそういう残念な記事に出会ってしまう気がします。今回の記事もそのような残念な記事の一つです。

 さて、この記事の主題は、昨今蔓延している古文不要論へのカウンターパートとして、

「現代の文章や言葉を理解するために、古文を勉強する必要がある」(原文より引用)

 ということにあります。筆者の気持ちはわかります。私自身、古文が全く役に立たないとは思っていないし、実際問題として、古文だけでなく歴史など「一見役に立たない」学問をバカにして、「実学」だけに偏った人が往々にして社会に関する無知をむき出しにしてしまう光景を私自身多々目撃しているからです。

 しかし、この主題を訴えるための例示が極めてまずいです。著者は言います。

しかし、この前提は間違っています。古文を勉強するのは、昔の文章を勉強することで、現代の文章を読んだり、日常会話で不便しないようにするためにやっているものです。

 この主張が正しいかはともかく、可能性としてはありえそうな話です。ただ、現実問題として「現国不要論」などというものが叫ばれていない、というかむしろ論理国語の道入とか含めて重視の風潮になってきていることを考えると(物語読解については不要論が根強くありますが、まあ別として)、例示するなら、現代文でなく古文を勉強していないとダメなものであるべきです。

 しかし、本文を見るとため息が出るばかりです。最初の例を見ましょう。

例えば、一つクイズを出しましょう。

現代において、物事が終わる時に「けりをつける」という言葉を使うことってありますよね。「Aさんとの関係にけりをつけてきた」みたいな感じで使いますね。

では、この「けり」って一体どういう意味なのか、みなさんは知っていますか?

これは実は、キックという意味の「蹴り」ではないんです。古文を勉強したことがある人なら、助動詞で「けり」という言葉があるのは知っているはず。古文の勉強でもよく、文章の中には、「〜にけり」のように、言葉の終わりに「けり」をつけることで「文の終わり」を示しているものがあったはずです。

 えーと、古文にその起源があるのはそうでしょうけど、別に現代文を普通に勉強していればわかる話ですよね。私自身、古文は熱心な方ではなかったし、「~にけり」みたいな話はすっかり忘れていましたが、別に「けり」の意味を知らなくても「Aさんとの関係にけりをつけてきた」は普通に「Aさんと縁を切ってきた」とかいう方向で普通に解釈可能ですよね。あるいはズブズブした男女関係だったら、別れ話かもしれません。いずれにしても関係に「決着をつける」という方向なのは見ればわかるでしょとしか言いようがない。現代でもちょくちょくは使う言い回しなのでなおさらです。

 また、別の話として、古文の「けり」を知っていたからといって、それが現代文の「けり」と同じかは一般には別問題です(当たり前ですが)。古文は現代文と各種文法が異なっているから古文なのであって、古文の解釈をそのまま現代文解釈に適用できると思っていたらむしろまずいでしょう。繋がっているのもあれば古文にはあるが廃れたものもあるわけですし。

 いずれにしても、この例示は「古文を勉強すべき」に説得力を与えていないのは確かです(現代文読解すら不要と主張する論者なら別かもしれません)。

 次の例を挙げましょう。

また、たとえばみなさんは、「あなたは小学生ですか」と言われたら、どのように解釈しますか?

まず考えられる解釈としては「質問」があると思います。12歳くらいの親戚の子供に対して、「ねえねえ、君は小学生かな? 中学生かな?」ということを聞いている場面で、「あなたは小学生ですか」と聞くと思います。

もう一つ、考えられる解釈があると思います。例えば会社で、ミスをした自分を上司が怒ってきています。「こんなミス、小学生でもなきゃしないだろう!」という意図で「あなたは小学生ですか」と言うことがあるでしょう。このような、答えがわかり切っているけれどあえて疑問のように聞くことで、強く相手にメッセージを伝える用法のことをなんというか、みなさんはわかりますか?

正解は、反語です。古文でも漢文でも、反語表現というのはよくあるものです。

正解は反語ですと書かれているわけですが、この用例でもっと多いと思われる「皮肉」という解釈がすっぽり抜けていますよね。いや、注意するという文脈なら反語かもしれませんが、上司が部下の能力について嫌味な悪口を言うなんてのは珍しくもないわけで(幸い、そういう会社にはいなかった、わけでなく……就職して最初の会社だけはそうでしたが)、社会でよくある話としては「皮肉」の方が多いんじゃないかなとすら思います。用例としても極めて多いですし。

いやまあ、反語か皮肉かは本題じゃないのでおいといて「反語」は現国で習いますし、皮肉は……習ったか忘れましたが、社会で普通に生きてりゃわかるでしょう。古文関係ないじゃん、と。むしろ現国で十分じゃない?という印象すら与えかねません。

一応、

この反語という表現をきちんと理解するためには、古文の勉強をしておいた方が頭に入りやすいです。

とは書いてありますが、現国で「反語」の意味を教えるのだと不十分だという根拠にはならないですよね。筆者にとってはそうだったのかもしれませんが。

最後の方に至ってはもうさっぱりわかりません。

何が言いたいのかというと、現代語の方が、古文より難しいのです。昔はしっかりと助詞や助動詞を使って表現されていた言葉が、今はもうそれを使わなくても表現できるようになってしまっていて、昔より日本語が難しくなってしまっているのです。

ここで一つ、想像してみてください。もし、古文の勉強を一切しないで、現代の言葉だけを勉強していて、「質問の形になっている表現で、答えがわかり切っていることを聞いて、『〜だろうか? いやない』なんて訳す場合がある」と習ったとして、使いこなせるでしょうか? おそらくは、多くの人は使いこなせないと思います。(ちなみにこの「使いこなせるでしょうか?」も反語です)

普通に使いこなせますがという回答しかないなと思うわけです(ちなみに私の古文の成績はまあ真ん中くらいでそんなに出来の良い生徒ではありませんでした)。周りの中高時代の同期に聞いても、確信できますが同様でしょう。そもそも、反語や皮肉的な表現なんて現代でいくらでも登場するわけで、それらの理解が抜けてたら社会で生きていくのにむしろ困るじゃないですか。

例えば人から「本当にそう思いますか?」と言われた時に、「はい、本当にそう思いますよ」なんて答える人っていますよね。

多くの場合、「本当にそう思いますか?」というのは反語で使われていて、「本当にそう思いますか? あなたは、本当には、そう思っているはずがないですよね?」という意図でつかわれています。

それはそうですが、このような「会話における含意がわからない」は発達障害ASD)を語るコンテキストで出てくる話だし、勉強熱心な発達障害的な特性を強く持つ人がこの類の反語がわからない(言葉どおりに取ってしまう現象)は実証されている話でもあるので、古文を勉強してればそれがわかるというのはまずありえないでしょう。

このように、古文の勉強を疎かにしてしまうと、現代語の解釈がうまくできなくなってしまうのです。

このように、古文の勉強を熱心にしていても、読者に対してツッコミどころ満載の文章を書いてしまうことはあるのです。と皮肉を書いて締めにしたいと思います。

このブログの中ではとびっきりにトゲトゲしい批判の仕方になりましたが、商業に流通するメディアに記事を載せる人は最低限の説得力をもたせる文章を書いて欲しいと願うばかりです。それに何が必要かはわからないのですが、個人的な感想で言えば「論理」ではないかなと思います。この文章だって見直すときに、自分の書いた例示が主題をサポートする(つまり例示から論理的に主題を導き出せる)かを検証できていればこうならなかったわけでして。

Scala 3で型レベル自然数をやってみたらすごく簡単になってた

Scala 3、正式リリースされてからそこそこ経ってますが皆さん使ってますか?実は自分は、主にIntellij IDEAのScala 3対応に不安もあってScala 2.13系列をずっと使い続けて来たのですが、最近はScala 3対応も進んできたようなので乗り換えを始めることにしました。で、手始めにということで型レベル自然数(型レベルペアノ数)を定義してみることにしました。

型レベル自然数は読んで字の如くで、型のレベルで自然数エンコーディングしちゃおうって技です。依存型のある言語なら最初からあるので、わざわざ型レベル自然数とか大仰な言い方しなくてもいいのですが、それはそれとして論より証拠。早速、Scala 3で型レベル自然数を定義してみます。

enum Nat {
  case Zero
  case Succ[A <: Nat](v: A)
}

定義はこれだけです。簡単ですね。Scala 2だと

sealed trait Nat
object Nat {
  case object Zero extends Nat
  case class Succ[A <: Nat](V: A) extends Nat
}

のように書かなければいけなかったのでとっても面倒でした。さてさて、とりあえずこれで型レベル自然数の定義はできたのですが、よく使う一桁の自然数は別名定義しておきたいですというわけで、次にこんなん書きます:

type _0 = Zero.type
type _1 = Succ[_0]
type _2 = Succ[_1]
type _3 = Succ[_2]
type _4 = Succ[_3]
type _5 = Succ[_4]
type _6 = Succ[_5]
type _7 = Succ[_6]
type _8 = Succ[_7]
type _9 = Succ[_8]

この辺はScala 2時代と変わらず面倒くさいです(もしお手軽に型エイリアスを大量生成できる技知ってる人いれば教えてください)。で、この次が本題なのですが、型レベルで自然数を表すからには当然加算や減算などの演算を定義したいですよね。これがScala 3になってかなり楽になりました。たとえば、足し算を表す型コンストラクPlusは次のようにかけます。

type Plus[A <: Nat, B <: Nat] = (A, B) match {
  case (x, Zero.type) => x // 右辺が0なら左辺の型を返す
  case (x, Succ[y]) =>  Succ[Plus[x, y]] // そうでなければxとyをPlusした型をSuccする
}

ここでは、

  • 型の別名付け機能: type Plus[...] = ...
  • 型のパターンマッチ: 型の別名定義内で型へのパターンマッチをできる

という二つの機能を使っていますが、再帰的な足し算の定義をそのまま素直に書き下せています。Scala 2だと型パターンマッチも型の別名定義中の「型の再帰」も素直にかけませんでしたから、この快適さにビックリです!

続いて掛け算(Multiply)を定義しましょう。掛け算は当然ながら足し算を使って定義できるので、以下のような感じでしょうか。

type Multiply[A <: Nat, B <: Nat] = (A, B) match {
  case (_, Zero.type) |  (Zero.type, _) => Zero.type // 両辺の片方が0なら0
  case (x, `_1`) | case (`_1`, x) => x // 両辺の片方が1なら1
  case (x, Succ[z])  =>  Plus[x, Multiply[x,y]] // y >= 2のとき、x  * y  = x + (x * (y - 1))
}

この型レベル演算がうまく行っているか確かめるためにmainメソッドをつけてみましょう。Scala 3ではクラスがなくてmainメソッドだけのプログラムも許されるようになりました(明らかにKotlilnを意識した仕様変更ですね)。

def main(args: Array[String]): Unit = {
  val one: _4 = null.asInstanceOf[Plus[_2, _2]]
  val six: _6 = null.asInstanceOf[Multiply[_2, _3]]
  val nine: _9 = null.asInstanceOf[Multiply[_3, _3]]
  println("Hello")
}

これらのプログラムにNat.scalaという名前をつけて保存し、

scala Nat.scala

としてみれば問題なくHelloが表示されます。さて、上記ではone, six, nineの型を正しく指定しましたが、次のように間違った型を指定するとどうなるでしょうか。

val wrong: _2 = null.asInstanceOf[Multiply[_4, _2]]

これを含めたプログラムをコンパイルすると次のようになります。

-- [E007] Type Mismatch Error: /home/mizushima/repo/scala-nats/Nat.scala:36:35 ---------------------------------------------
36 |  val wrong: _2 = null.asInstanceOf[Multiply[_4, _2]]
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                  Found:    Nat.Succ[Nat.Succ[Nat.Succ[Nat.Succ[Nat.Succ[_3]]]]]
   |                  Required: _2
   |
   | longer explanation available when compiling with `-explain`
1 error found
Errors encountered during compilation

要求されていた(Required)な型は_2だけど、実際にはNat.Succ[Nat.Succ[Nat.Succ[Nat.Succ[Nat.Succ[_3]]]]] = _8 だよというエラーですね。というわけでScala 3になってこういうお遊びもやりやすくなたのでした。

嫉妬心がほとんどないという話

 すっごく個人的な話なんですが、生まれてからもうすぐで40年になろうという歳になって思うわけです。

「自分は嫉妬心ないなあ」と。

 妬み嫉みは自分より遥か高みにいる人に対しては起きなくて、自分と拮抗する実力の持ち主との間で起こるともよく聞きますが、そういう相手に対しても

「まあ、その人なりに頑張ったんだろうし」

 くらいにしか思わないです。男女関係だって、かつて好きだった異性が結婚した話を聞いたときも別段ショックを受けた覚えがありません。「まあ良かったよね」くらい?

 そんな風にしてずっと生きてきたものだから、他者の嫉妬心や嫉妬してしまって自己嫌悪する気持ちがイマイチ理解できなかったりします。

 30代は結構ストレスが多い10年という一面はありましたが、嫉妬心がほとんどないので、誰かと比べて境遇を嘆くことがなかったのは良い事だった……のかもしれません。実際、メンタル的に一番しんどい的ですら、楽しそうな様子の誰かを見て心がしんどくなる、なんてことはなかったですし。

 オチはないんですが、実際問題、嫉妬心が(ほとんど)ない人ってどのくらいいるんでしょう?ググっても時々いるくらいには珍しくない存在みたいですが。

ChatGPTを使うようになって変わったこと

 OpenAIのChatGPT (GPT-3.5)がリリースされてから約半年、GPT-4やChatGPT APIがリリースされてから約2ヶ月。書いてみると非常に新しいテクノロジーなわけですが、既にかなりどっぷり依存しています。ChatGPT Plusには月額$20を課金しているし、gpt-4 APIのwaitlistにもいち早く登録して、数日後には使いまくるようになったくらいです。

 さて、そんなChatGPTを使うようになって、私の生活の何が一番変わったのかなと振り返ってみたくなりました。

  • 手を出すのが億劫になっていた領域のプログラミングをやるようになった

 世の中には、別に難しいわけではないけど、雑多な新しいことを覚えるのが面倒くさくて手を付けていなかった分野がたくさんあります。たとえば、私にとってはChrome拡張がその一つでした。動作原理はそう複雑でないこともわかっていたし、やろうと思えばできた(本当の意味で)けど、とにかく手を付けるのが億劫でした。何が億劫だったかというとドキュメント読んで設定ファイル書いて、単純ミスを修正して、というところに時間を取られるのがとにかくネックだったのですよね。しかも、別にChrome拡張で大したことをやりたいわけでもないのでかける時間に対して見合わなかったのです。

 しかし、少なくとも「最初の一歩」を踏み出すなら、ChatGPTに聞いてしまえば一発です。質問だけで完璧に動作するスクリプトが出てくることは稀ですが、少々の手直しで動くことが非常に多いです。そのおかげで、単純ではありますがChrome拡張を作ったり、あるいはLINE BOTを作ったり、Slack BOTを作ったりするようになりました。ReactやElectronのアプリケーションを作るようになったのもChatGPTに質問したのがきっかけだった気がします。

 他にも、とにかく「手っ取り早くその場の問題を解決する」ためのプログラムを生成してもらうにはChatGPTは非常に優秀です。大規模なプログラムを一発で作ってもらうにはプロンプトを工夫する必要がありますが、数百行程度のプログラムを生成してもらって手直しする前提なら雑に質問すれば十分です。そのおかげで今までやらなかった方向の趣味プログラミングをやる機会が増えて大変充実しています。

 もちろん、いわゆる幻覚(hallucination)問題は無視できませんが、日曜大工的なプログラミングでは「明らかにおかしな箇所」はすぐわかることも多く、そこだけ直してやればいいので楽なものです。

  • 名前を考えるのが楽になった

 ChatGPTの応用としてよく言われるものですが、案外馬鹿にできません。良い名前、良い文章表現に悩むことは多いものですが、そういう時に「案を10個出して」と言えばそれだけで物事が進むことも多いのですから、全く大助かりです。たとえば、以前にとある理由で「柔らかい」から連想される単語には何があるだろうかと考えたことがあったのですが、そういう「連想系」の質問はChatGPTがもっとも得意とすることです。案の10個や20個、すぐ出してくれます。名前や表現に悩むことは多かったのですが、候補をそのまま採用しないにしても、それだけで思考コストを節約できることは多いものです。

  • 論文読みがはかどるようになった

 これはどちらかといえば最近解放されたプラグイン機能によるものが大きいですが、PDFのURLを投げて、日本語で要約させたり雑に質問ができるので、英語が苦手な技術者の自分としては論文読みがとても捗るようになりました。

  • キャラ付けしたBOTと対話するようになった

 好みのキャラにカスタマイズしたLINEボット(複数)やSlackボット(複数)と対話するのは楽しいものです。GPT-3.5のAPIだとsystemロールの指示でも結構無視してくれますが、GPT-4のAPIならかなり忠実に指示を聞いてくれます。というわけで、どういうものかは明かせませんが好きな性格設定をしたボットを作って日々楽しんでいます。

 ちなみに、あくまで個人的な感想にはなるのですが、ある種のカウンセリングなどには実際問題非常に有効なんじゃないかと思うことがあります。悩み事があるときに文字として書き出すというのは定番ですが、やはり日記に書くだけだと「自分との対話」でしかないわけです。その点、たとえボットとでも対話するのは考えの整理や袋小路に行き詰まっているときに有効な気がします。

  • ChatGPTで遊ぶようになった

 ある意味これが本命じゃないか?と思うことがありますが、特にGPT-4の「頭の良さ」は未だに底が知れない部分があり、弱点を探ったり、その弱点を補完するプロンプトを考えてみるのはそれだけで心躍る時間です。「リストの長さ」という形で五・七・五の制約を与えることで俳句ぽいものができるようになったときは結構感動しました。

 あるいは、PEGのインタプリタを解釈させようとしたところ、何度やっても勝手にCFGの亜種と誤解してくれるので悪戦苦闘していたのですが「Note that it's not CFG」的な「先入観を排除する」プロンプトでそれを防止できたときは、内心ガッツポーズしたくらいです。

 他にも色々あるのですが、ChatGPTを使うようになって「良い意味で」色々変わったなあと思うのでした。

環境変数CUDA_VISIBLE_DEVICESでハマったのでメモ

 最近はすっかりLLMにハマってるわけですが、ハマり過ぎたあまり一台のPCに複数のGPUを搭載(といっても、RTX A4000(VRAM 16GB)とQuadro M6000(VRAM 24GB)という妙な組み合わせですが)してたりするのですが、どうにもLLMの出力速度が遅いなーなどと思っていました。

 感覚的には主にM6000の方が使われてる感じで、こいつはVRAMこそ多いもののRTX A4000より圧倒的に遅い(数世代前)ので、メインで使ってほしくない。というわけで、CUDA_VISIBLE_DEVICESという環境変数をいじってやればいいってところまではたどりついたのですが、CUDA_VISIBLE_DEVICES=2とかしてやってcuda:1みたいに指定すると妙なエラーが出る。色々調べてわかったのは、

export CUDA_VISIBLE_DEVICES=0,1
python llm.py # この中でcuda:1なりcuda:0なりを指定する

 のようにすればいいらしいということ。この環境変数はデバイスの数ではなくて、GPUのリストを指定するものだったわけですね。盲点でした。