仕事で使う技術 X がある。
技術 X を習得したいが、そこまで興味もなくプライベートで学習するのが辛い。
とはいえ実際に手を動かしてみないと習得するには至らないだろう。
そうだ、モチベーションを高めるために技術 X を使ってアプリ A を作ろう。
もちろんお題となるアプリ A はプライベートで作れる程度の簡単なものだ。
ところが、アプリ A を作るのに最適な手段は技術 X よりももっと簡単な技術 Y だ。
ああ、技術 Y を使ってアプリ A が作りたい。
仕事で使う技術 X がある。
技術 X を習得したいが、そこまで興味もなくプライベートで学習するのが辛い。
とはいえ実際に手を動かしてみないと習得するには至らないだろう。
そうだ、モチベーションを高めるために技術 X を使ってアプリ A を作ろう。
もちろんお題となるアプリ A はプライベートで作れる程度の簡単なものだ。
ところが、アプリ A を作るのに最適な手段は技術 X よりももっと簡単な技術 Y だ。
ああ、技術 Y を使ってアプリ A が作りたい。
OSS とかのコードを巡っていると、時々「すごいコード」に出会うことがある。 もちろん「すごい」と言っても色々な凄さがあって、「読みやすくメンテしやすいコード」とか「技術的に凄いことをしている」とか「最新のライブラリを上手く使っているコード」はもちろんそうなのだが、それ以上に「圧倒される」コードがある。
全部頭に入ってる人が一気に書いたコードって、なんか見て分かるよね。また出会った。
— Yosuke Torii / ジンジャー (@jinjor) 2020年9月18日
凡人の発想と違うことがあり、一見して読みにくく取っつきにくいこともあるが、そういう感想はすぐに吹き飛んでしまう。
その特徴を思いつくままに列挙してみる。
別に型やテストがないのは見習うべき点ではない。一方で、これは絶対に真似できないなーと思う。
もし「独りよがりな書き方してんな!俺がマトモにしてやる!」とリファクタリングを始めたとしたら、しばらく経ってたちまち鬱になるのが目に見える。 3 行のコードが 20 行にフォーマットされ、共通化はしたものの良く分からない名前の変数や関数が増え、たまに共通化したコードを使い忘れている箇所があり、やたら型が増え、宣言的なのに簡潔でなく、無駄にメモリを食い、後になって「ここは共通化すべきじゃなかった」と言って戻す。
じゃあなんで彼らはそういうコードが書けるのかという仮説、あくまで仮説を立ててみる。
優れたプログラマは頭の中で全てを組み立て、手を動かし始めたら一気に頭の中にあるものを吐き出すだけ、と良く言われる。 翻って凡人プログラマである自分は逆に何をしているのかというと、とにかく常に迷い、試行錯誤している。
まず知識量の不足から仕様書を確信を持って読むのがとにかく難しい。簡潔に書かれているそれらは、おそらく背景知識があれば自明なのだろうが、一部が抜け落ちていると理解が困難になる。そして「分からん!とにかくサンプルをくれ!」となる。サンプルを通してやっと「あれはこういうことが言いたかったんだろうな」と理解した気になる。試行錯誤しなければ分からないとなると、大きな時間のロスになる。一つ確かめるのに20分かけてデプロイしたら、3回で1時間。なかなか進まない。また、サンプルにないパターンを自分で組み立てるのは不可能なので、「とにかく自分のやりたいことと似たことをやっているコードがないかググって探す」ことに時間を費やすことになる。まるで何か未解明の自然現象に触れるかのように「あれをしたらこんなことが起きた」を繰り返すのだが、実際にはブラックボックスでもなんでもなく仕様はちゃんと書かれている。
試しながら書いているととにかく書いている時間が長いので、書き始めた時と書き終わる頃で気分が変わって一貫性がなくなっていく。ある時は関数を上に書き、ある時は下に書く。その時々で「これで行こう」と判断した形跡が、ミクロにもマクロにもある。タイピングも遅いので同じコードを何度も書いているとだるくなってきてユーティリティ関数を作ったりする。が、関数を一つ作るということは名前を一つ考えるということでもあり、名前こそその時々の気分が反映される。行き当たりばったりで生み出した謎概念を、拙い英語(しかし簡潔に表現しようとして短くして情報が不足している)で表現しているのだからタチが悪い。
このブログもまあ長いことやってるんだけど、これだけダラダラやっててもちょいちょい「○○さんが『△△』をブックマークしました」って通知が来たりする。でも、正直古い記事だと「それもう賞味期限切れてるよなー」って思うことがあって苦い気持ちになったりする。
自分の感覚としては1年経ったら大体も主張も色あせてきて、振り返ると「何々はなんとかすべきである」とか何を偉そうにのたまっとんじゃ〜という気持ちになる。いやもちろん正しいことも言ってるし、将来考えが変わることを予想して予防線を張っていたりはするんだけど。何かを主張している系の記事はどんどん気が変わってしまう。
「CSS はもう全部インラインでいいだろ」 「いや、なんやかんや言って普通に CSS ファイル使うのがやりやすいわ」 「CSS ファイル膨れ上がりすぎるし書く場所を移動するのめんどい。やっぱインラインで書けばいいんじゃないか」 「CSS フレームワーク要らね」 「要らないとは言ったけど、結局は代わりに自前で似たような仕組み作らざるを得ないわ」 「View は Model の関数だよね、状態持つとか以ての外だよね」 「あーバケツリレーめんどい、状態持たせろ」 「人間は DOM から離れては生きていけないのじゃ」 「関数型最高、イミュータブル最高、手続きなんて捨ててしまえばいいね!」 「どーでもいいわ。こんなの手続きでちょろっと書けば終わりなのに何でわざわざ難しいことしてるの」 「class 普通に使うでしょ、わざわざ用意されてる便利な道具を使わない理由なんてある?」 「ボイラープレートは必要悪だ、同じことを何度も書いてもシンプルで見通しが良けりゃいいんだ」 「そこ共通化できないの最早プログラミングじゃないだろ、マクロ使わせろ」 「これが世間のベストプラクティス、みんな従うべき!」 「世間のベスプラが正解とは限らないし、守る必要なんてないでしょ」
こういうのを何度も何度も繰り返していくうちに、段々めんどくさくなってきて「適当にみんな好きなようにやればいいんじゃないの、知らんけど」になったり、「要はバランス」とでも言いたくなったりする。その場で思いついた適当なことを周囲に撒き散らしながら、自分だけは「こういう場合はこうだよね」という言語化されないバランス感覚が身につくので、まあ迷惑なやつだ。
適当なことを言ってるので適当に聞いてもらえれば良いんだけど、中には目を輝かせて聞いてくれる人が一定の割合でいて、「いやこんなの信用しなくて良いし1年後には全然違うこと言ってるよ?」という気分になってしまう。自分も web 界隈の強そうな人がなんか言うと「そうなんだ〜」ってなるけど、逆に「うるせー好きにやらせろ」って思うことも多々ある。そもそも web 界隈の平均的なプロジェクトの事情と自分の目の前にあるプロジェクトの抱える事情は違うわけで、「参考にはするけど内心は疑ってますよ」というスタンスで聞いてたりする。でも、自分がそうだからといって読者もそうとは限らないなと。ここのところはそういう主張モノを書くのはやめてしまって、なんか試しにやってみたっていう日記か、ただただ技術的に正しい事実を淡々と書いていく感じ。「あのブログにこれが良いって書いてあった」と言われると面倒だし。
じゃあ、そういう主張モノが害しかないかというと、そうは思っていなくて、みんなが色んな意見を言ってるのを聞いて上のように行ったり来たりしてるうちによりアイデアが洗練されてくるということはあるだろうし、言ってることも大体「一理ある」とは思う。誰も何も言わなくなったら、少なくともそういう考えを持った人がいるということを知る機会もなくなるし、それはそれで面白くない。
プロジェクトに採用するライブラリやアーキテクチャにしても「あの時はそれが良いと思ってたけど、試した結果イロイロ問題があることが分かったわ」みたいのがあって、「なんでこんな風になってるの?」と言っても本人も「なんでだろね、当時はそれがベストな方法と信じていたんだ」となったりする。
このアイデアの賞味期限というのは人によって違って、中には自ら 10 年前の主張をピックアップする人もいる。それだけ長い期間ブレない考察が得られるのは素直に尊敬する。真似できない。経験なのか知識なのか性格なのか、それは分からない。自分はコーディングも1分前にしていたと全く違う方法で書いてしまったりする。「さっきは関数を下にまとめるのがみやすいと思った。今は上かなと思ったから上に書いてる。」気分屋にもほどがある。でもブレないスタイルでずーっと書き続けられる人もいる。多分あれは「考えずに書くだけ」モードに入ってる。すごい。
世の中にはすごい人もいるけど自分には無理なので諦める。読者も諦めて欲しい。 ここに書くアイデアの賞味期限は1年です。
前回、 Faust という言語を使って音を鳴らしてみたわけですが、AudioParam を使ったオートメーションに対応していないのでちょっと微妙だなと思いました。まあ Web が唯一のターゲットではないので仕方ない気はしますが。多分これができないと楽譜データからミックスダウンとかもできないはず。
じゃあということで次に試したのが Emscripten で C++ から出力するやつで、まあ動いたんですが、サイズがでかいとか、余計な JS コードがついてくるとか、バインディングを書くのが面倒くさいとか細かいところが色々気になったりしました。他にも Rust とか色々選択肢はあるんですが、どれにしても結局 AudioWorkletProcessor との接続部分は書かないといけないのが面倒で、ああ本当に面倒なのでそれ用の言語が作りたくなりました。
AssemblyScript を調べてたら binaryen.js という Binaryen のラッパーを使ってたので採用。この時点で LLVM が消えてターゲットが wasm のみに。脳みそが狐さん以下なので仕方ないですね。
名前はまだない。 github.com
こういう感じのコードを書くと、wasm モジュールとそれをラップした AudioWorkletProcessor が生成されます。デモはこちら。
言語を作ったりするのは初心者もいいところなので、コスト削減のために大幅な割り切りをしています。
逆に、あるもの
エディタに色がつくと急にそれっぽい!
wasm の特性なのかは不明ですが、 C っぽい言語が圧倒的に作りやすいというか、普通に普通の文法を動かすのが普通に大変で、それ以上の気の利いた文法とかはもう「動くんだから愚直に書けばいいでしょ」という気分になってきますね。 あと binaryen のレイヤーが高いのか、全然アセンブリ書いてる気がしないというか、結局アセンブリのこと何も分かってないかもしれない。うーん。
反省点としては、自作のパーサーコンビネーターがトークナイズしてないせいでホワイトスペースが至る所に出現してノイズが多いのと、VSCode 拡張(というか Language Server Protocol)で補間とか出そうとするとマメにコンテクストを管理しなきゃいけないなーっていうのに後で気づきました。直すの辛いけどやった方が良さそう。
まあとりあえず動いたので満足です。メンテナンスしたくないので広く公開することは多分ないけど。 早く FM 音源とか作りたい〜!
音声信号処理に特化した言語というか DSL です。次のような短い記述でサクッとシンセ が作れます。
import("stdfaust.lib"); freq = hslider("freq",200,50,1000,0.01); gain = hslider("gain",0.5,0,0.5,0.01); gate = button("gate"); process = os.sawtooth(freq) * gain * gate;
簡潔、素晴らしい。 UI も定義できますが十分に抽象的なので見た目は完全にカスタマイズできます。
Faust については次の記事が詳しいです。 qiita.com
コンパイルターゲットの一つに wasm があり Web Audio API の AudioNode を作ってくれます。
例えば Hello.dsp
というファイルを作って faust2wasm
というコマンド(実体は shell )を叩くと、次の3点セットが出てきます。
Hello.js
: AudioWokletNode ( main thread 側)Hello-processor.js
: AudioWorkletProcessor ( audio thread 側)Hello-processor.wasm
: 信号処理部分言語そのものに関しては公式サイトがかなり詳しく解説しているので、上から順にやっていくと一通り作れるようになります。 が、個別のターゲットに関する情報は少ないので、 wasm 向けにすぐに音を出せるサンプルを作りました。
動作確認は2020/3/4 時点( FAUST Version 2.23.1
)
AudioWorklet の想定としては「ノード毎に processor を実装して組み合わせてね」ということなんですが、 Faust でなんでも出来てしまうので、ぶっちゃけ1ノードで済みます。 1ノードになんでもやらせるか、多数のノードを個別に作って組み合わせるのは Web Audio API でやるかは悩みどころ。
ちなみに現時点で吐き出されるコードは AudioParam のオートメーションが実装されていないので setTargetAt()
などが効かないはず。ということは OfflineContext を使ったミックスダウンも上手く動かないんじゃないかと予想。気軽にコントリビュートするには重い課題な気がしますが、どうなんでしょうね。
VSCode 拡張を自作して使いたいけど、全世界に公開するほど意識が高まらない時とか、特定のプロジェクトに特化したものを作りたい時。ググったら公開前提の解説が多いけど、その必要はない。
ここに大体書いてある。 code.visualstudio.com
最短の手順はおよそ次の通り。
npm install -g yo generator-code # yeoman と vscode 用のジェネレータ yo code # 雛形生成(色々聞かれるので答える) cd helloworld # 生成したプロジェクトへ # README を書き換える # package.json に "publisher" と "repository" を入れる npm i -D vsce # VS Code Extension Manager を入れる npx vsce package # パッケージを作る(prepublish のチェックが走るが publish はしない) code --install-extension helloworld-0.0.1.vsix # パッケージをインストール
途中で README とか package.json を書き換えているのは、vsce package
時の怒られを回避するため。
これで捗る。あとは進捗するのみ。
React みたいなコンポーネント作る系フレームワークだと思って Custom Elements を使おうとすると、たちまち死んでしまう。まだ色々試している最中なのでアウトプットはないんだけど、とりあえず今考えてることを書いておく。役立たないし刺されたら困るからポエム宣言しとこうか。ポエムです。
Custom Elements の良さは特定のフレームワークに依存しないところだと思う。例えば React とか Vue とかだとそれぞれのフレームワークの世界にどっぷり浸かってしまい互換性がないが、 Custom Elements ならば普通の要素の延長線上でどこに持って行っても使える。 npm とか使わなくても script タグで CDN とかから持ってくればすぐに動く。夢のよう。もちろん、データフローはアプリケーション固有のものになるだろうから Custom Elements が力を発揮するのはせいぜい末端要素だろうとは思う。それでもよく使いそうな末端要素を埋めてくれるのは便利。
だけど、実装を始めると途端に難易度が高いことに気づく。もちろん、チュートリアル通りにやればすぐに動くものはできるのだが、「ちゃんと」動くものを作るのはめちゃくちゃハードルが高い。「ちゃんと」というのは言い換えると「普通の DOM らしく動作する」という意味。別に使い方は自由だと思うけど、変な動きをしたら驚くし、せっかくだからちゃんとしたものを作りたいという気持ちがある。
色々難しい点はあるが、その中でも特に、難易度を劇的に高めているのは「属性とプロパティの二重管理」だと思う。
まず、属性に関しては静的に検査されている訳でもなんでもないので、適当な文字列がどんどん入ってくる。省略される可能性があるので、デフォルト値の定義も必須だ。おかしな値が入っていても例外を投げることもできない。例えば <input type="number" min="10" max="0" value="5">
などと書くことも可能だ。この場合どうなるかというと、最初は 5 が入っているがインクリメントすると値が 10 に飛び、デクリメントすると値が 0 に飛ぶ。0 のまま送信しようとすると「値は 10 以上にする必要があります」と怒られ、10 で送信しようとすると「値は 0 以下にする必要があります」と怒られる。おかしいのだが無難な動きにはなっている。
また、min="hoge"
のような明らかに意味のない値を入れると無視される。無視されるが属性が消える訳ではなく、"hoge"
という属性はセットされたままになる。
対してプロパティは JS から操作されるためにあり、文字列以外に数値やブール値を許容している。とは言っても、max
なら数値かと思えば文字列が返ってくる。MDN によると date-time が入りうるからということらしい(たとえ type="number"
でも)。対して maxLength
はどうかというと、こちらは数値が返ってくる。属性を指定しない場合は、それぞれ ""
と -1
が返る。 null
ではない。値をセットした時の挙動も属性とは微妙に違って、例えば minLength = 10
の時に maxLength = 0
という値を入れようとすると「 10 より小さくするな」と例外を投げてくる。文字列で "100"
を入れると 100
と解釈される。"hoge"
を入れると 0
、true
は 1
。というわけで、型に関しては指定されているものの違っても怒られずなんらかの解釈をするようだ。
次に属性とプロパティの関係について。最初「属性を変更したらプロパティも変更される」という一方通行だと思っていたが、どうも双方向らしい。つまり、どちらかを変更した時にもう片方を呼ぶような実装にしていると無限ループになる。共通部分を切り出す必要がある。だいたい思うように動くが、変な値を入れると妙な動きをする。属性 maxlength
を "hoge"
とすると、属性値は "hoge"
に、プロパティは -1
になる。今度はプロパティを "hoge"
にすると、属性値は "0"
に、プロパティは 0
になる。別にこの通りの動きにする必要はないと思うが。
また、ややこしいことに、value
のような属性は双方向ではなく属性には反映されない。頻繁に変更されるものに関しては逐一属性値を書き換えたくないようだ。その辺の細かい作法みたいなのが、以下の "Custom Element Best Practices" に書いてある。
正直「ベストプラクティス」と書いてはいるが、「お前らちゃんと正しく実装しろよ」ということだと思う。いや、できることならそうしたいけど難しくないっすか。他には例えば「プロパティに値をセットした時にイベントを発火するのは避けるべし」とある。理由は「プログラムから値を変更したんだからどんな値が入ってるかは知ってるでしょ」とのこと。うーん...そう言われればそうな気もするし、そうじゃないような気もする。
とりあえず、「 Custom Elements を使えばアプリがスイスイ作れるぜ」みたいな生易しいものじゃないので万人に勧められないし、開発チームに丸投げしたらコンポーネントがホイホイ上がってくるようなものでもなさそう。いや、上がってくるかもしれないが。本当に一部の実力者みたいな人がちまちま作るか、上のベストプラクティスをうまいことまとめたツールキットなりテストなり、なんらかの仕組みが必要そう。 Custom Elements のチュートリアルに「ユーザーはどんな使い方をするか分からないからね!」とあるんだけど、ほとんどの開発現場では世界中の人に使ってもらうわけではなく、限られた使い方だけを網羅すればいいはずなので。そして限られた使い方しかしないのであれば、まあ React とかでいいよねという話に戻ってしまう。その辺はなんか立場の違いというか、今まで実現できなかったことをボトムアップに構築していく集団と、今までできてたことをいかに高効率でやってくかっていう集団があって、それは両方あっていいと思う。ただ、効率厨が軽い気持ちで触ると難しさに音を上げるという話。覚悟を持って挑もう。覚悟と気合い。
よいお年を。