ジンジャー研究室

長めのつぶやき。難しいことは書きません。

ミスマッチ

仕事で使う技術 X がある。

技術 X を習得したいが、そこまで興味もなくプライベートで学習するのが辛い。

とはいえ実際に手を動かしてみないと習得するには至らないだろう。

そうだ、モチベーションを高めるために技術 X を使ってアプリ A を作ろう。

もちろんお題となるアプリ A はプライベートで作れる程度の簡単なものだ。

ところが、アプリ A を作るのに最適な手段は技術 X よりももっと簡単な技術 Y だ。

ああ、技術 Y を使ってアプリ A が作りたい。

すごいコード

OSS とかのコードを巡っていると、時々「すごいコード」に出会うことがある。 もちろん「すごい」と言っても色々な凄さがあって、「読みやすくメンテしやすいコード」とか「技術的に凄いことをしている」とか「最新のライブラリを上手く使っているコード」はもちろんそうなのだが、それ以上に「圧倒される」コードがある。

凡人の発想と違うことがあり、一見して読みにくく取っつきにくいこともあるが、そういう感想はすぐに吹き飛んでしまう。

その特徴を思いつくままに列挙してみる。

  • 癖があるが終始一貫した書き方
  • 世に出回っているベストプラクティスに則っていない
  • フォーマッターを使っていない
  • 細かいユーティリティ関数がない
  • 初手で完璧に近い
  • 静的型でないコードも多い
  • 可読性のためにパフォーマンスを犠牲にしない
  • とにかく無駄がない
  • テストがないこともある

別に型やテストがないのは見習うべき点ではない。一方で、これは絶対に真似できないなーと思う。

もし「独りよがりな書き方してんな!俺がマトモにしてやる!」とリファクタリングを始めたとしたら、しばらく経ってたちまち鬱になるのが目に見える。 3 行のコードが 20 行にフォーマットされ、共通化はしたものの良く分からない名前の変数や関数が増え、たまに共通化したコードを使い忘れている箇所があり、やたら型が増え、宣言的なのに簡潔でなく、無駄にメモリを食い、後になって「ここは共通化すべきじゃなかった」と言って戻す。

じゃあなんで彼らはそういうコードが書けるのかという仮説、あくまで仮説を立ててみる。

  • 圧倒的な経験量。こういう時はこう書くというパターンが予め頭に入っているために迷いがない。迷いがないからコードの書き方に関して雑念がなく、本質的な部分に集中できる。昔から書いていたのでスタイルは古いが、メモリを食いにくい書き方が常に意識されている。細かいアルゴリズムは頭に入っているのでユーティリティ関数を使わなくてもすぐに書けるし、逆に書かれたパターンからアルゴリズムを瞬時に読み取ることができる。
  • 圧倒的な知識量。技術的に十分な知識を持っていれば、予め何が出来て何が出来ないかが把握できるので適切な設計ができ、行き当たりばったりにならない。セキュリティ的に考慮すべき点は最初から網羅できる。仕様書を読めば実際に試さなくても何が起こるか把握できる。
  • 上記2つを効率良く高速で摂取する頭の良さ。

優れたプログラマは頭の中で全てを組み立て、手を動かし始めたら一気に頭の中にあるものを吐き出すだけ、と良く言われる。 翻って凡人プログラマである自分は逆に何をしているのかというと、とにかく常に迷い、試行錯誤している。

まず知識量の不足から仕様書を確信を持って読むのがとにかく難しい。簡潔に書かれているそれらは、おそらく背景知識があれば自明なのだろうが、一部が抜け落ちていると理解が困難になる。そして「分からん!とにかくサンプルをくれ!」となる。サンプルを通してやっと「あれはこういうことが言いたかったんだろうな」と理解した気になる。試行錯誤しなければ分からないとなると、大きな時間のロスになる。一つ確かめるのに20分かけてデプロイしたら、3回で1時間。なかなか進まない。また、サンプルにないパターンを自分で組み立てるのは不可能なので、「とにかく自分のやりたいことと似たことをやっているコードがないかググって探す」ことに時間を費やすことになる。まるで何か未解明の自然現象に触れるかのように「あれをしたらこんなことが起きた」を繰り返すのだが、実際にはブラックボックスでもなんでもなく仕様はちゃんと書かれている。

試しながら書いているととにかく書いている時間が長いので、書き始めた時と書き終わる頃で気分が変わって一貫性がなくなっていく。ある時は関数を上に書き、ある時は下に書く。その時々で「これで行こう」と判断した形跡が、ミクロにもマクロにもある。タイピングも遅いので同じコードを何度も書いているとだるくなってきてユーティリティ関数を作ったりする。が、関数を一つ作るということは名前を一つ考えるということでもあり、名前こそその時々の気分が反映される。行き当たりばったりで生み出した謎概念を、拙い英語(しかし簡潔に表現しようとして短くして情報が不足している)で表現しているのだからタチが悪い。

すごいコードはそうした凡人プログラマの苦悩を微塵も感じさせないので、すごい。

賞味期限が1年

このブログもまあ長いことやってるんだけど、これだけダラダラやっててもちょいちょい「○○さんが『△△』をブックマークしました」って通知が来たりする。でも、正直古い記事だと「それもう賞味期限切れてるよなー」って思うことがあって苦い気持ちになったりする。

自分の感覚としては1年経ったら大体も主張も色あせてきて、振り返ると「何々はなんとかすべきである」とか何を偉そうにのたまっとんじゃ〜という気持ちになる。いやもちろん正しいことも言ってるし、将来考えが変わることを予想して予防線を張っていたりはするんだけど。何かを主張している系の記事はどんどん気が変わってしまう。

CSS はもう全部インラインでいいだろ」 「いや、なんやかんや言って普通に CSS ファイル使うのがやりやすいわ」 「CSS ファイル膨れ上がりすぎるし書く場所を移動するのめんどい。やっぱインラインで書けばいいんじゃないか」 「CSS フレームワーク要らね」 「要らないとは言ったけど、結局は代わりに自前で似たような仕組み作らざるを得ないわ」 「View は Model の関数だよね、状態持つとか以ての外だよね」 「あーバケツリレーめんどい、状態持たせろ」 「人間は DOM から離れては生きていけないのじゃ」 「関数型最高、イミュータブル最高、手続きなんて捨ててしまえばいいね!」 「どーでもいいわ。こんなの手続きでちょろっと書けば終わりなのに何でわざわざ難しいことしてるの」 「class 普通に使うでしょ、わざわざ用意されてる便利な道具を使わない理由なんてある?」 「ボイラープレートは必要悪だ、同じことを何度も書いてもシンプルで見通しが良けりゃいいんだ」 「そこ共通化できないの最早プログラミングじゃないだろ、マクロ使わせろ」 「これが世間のベストプラクティス、みんな従うべき!」 「世間のベスプラが正解とは限らないし、守る必要なんてないでしょ」

こういうのを何度も何度も繰り返していくうちに、段々めんどくさくなってきて「適当にみんな好きなようにやればいいんじゃないの、知らんけど」になったり、「要はバランス」とでも言いたくなったりする。その場で思いついた適当なことを周囲に撒き散らしながら、自分だけは「こういう場合はこうだよね」という言語化されないバランス感覚が身につくので、まあ迷惑なやつだ。

適当なことを言ってるので適当に聞いてもらえれば良いんだけど、中には目を輝かせて聞いてくれる人が一定の割合でいて、「いやこんなの信用しなくて良いし1年後には全然違うこと言ってるよ?」という気分になってしまう。自分も web 界隈の強そうな人がなんか言うと「そうなんだ〜」ってなるけど、逆に「うるせー好きにやらせろ」って思うことも多々ある。そもそも web 界隈の平均的なプロジェクトの事情と自分の目の前にあるプロジェクトの抱える事情は違うわけで、「参考にはするけど内心は疑ってますよ」というスタンスで聞いてたりする。でも、自分がそうだからといって読者もそうとは限らないなと。ここのところはそういう主張モノを書くのはやめてしまって、なんか試しにやってみたっていう日記か、ただただ技術的に正しい事実を淡々と書いていく感じ。「あのブログにこれが良いって書いてあった」と言われると面倒だし。

じゃあ、そういう主張モノが害しかないかというと、そうは思っていなくて、みんなが色んな意見を言ってるのを聞いて上のように行ったり来たりしてるうちによりアイデアが洗練されてくるということはあるだろうし、言ってることも大体「一理ある」とは思う。誰も何も言わなくなったら、少なくともそういう考えを持った人がいるということを知る機会もなくなるし、それはそれで面白くない。

プロジェクトに採用するライブラリやアーキテクチャにしても「あの時はそれが良いと思ってたけど、試した結果イロイロ問題があることが分かったわ」みたいのがあって、「なんでこんな風になってるの?」と言っても本人も「なんでだろね、当時はそれがベストな方法と信じていたんだ」となったりする。

このアイデアの賞味期限というのは人によって違って、中には自ら 10 年前の主張をピックアップする人もいる。それだけ長い期間ブレない考察が得られるのは素直に尊敬する。真似できない。経験なのか知識なのか性格なのか、それは分からない。自分はコーディングも1分前にしていたと全く違う方法で書いてしまったりする。「さっきは関数を下にまとめるのがみやすいと思った。今は上かなと思ったから上に書いてる。」気分屋にもほどがある。でもブレないスタイルでずーっと書き続けられる人もいる。多分あれは「考えずに書くだけ」モードに入ってる。すごい。

世の中にはすごい人もいるけど自分には無理なので諦める。読者も諦めて欲しい。 ここに書くアイデアの賞味期限は1年です。

wasm ターゲットな言語を作って音を鳴らしてみた

前回、 Faust という言語を使って音を鳴らしてみたわけですが、AudioParam を使ったオートメーションに対応していないのでちょっと微妙だなと思いました。まあ Web が唯一のターゲットではないので仕方ない気はしますが。多分これができないと楽譜データからミックスダウンとかもできないはず。

じゃあということで次に試したのが EmscriptenC++ から出力するやつで、まあ動いたんですが、サイズがでかいとか、余計な JS コードがついてくるとか、バインディングを書くのが面倒くさいとか細かいところが色々気になったりしました。他にも Rust とか色々選択肢はあるんですが、どれにしても結局 AudioWorkletProcessor との接続部分は書かないといけないのが面倒で、ああ本当に面倒なのでそれ用の言語が作りたくなりました。

AssemblyScript を調べてたら binaryen.js という Binaryen のラッパーを使ってたので採用。この時点で LLVM が消えてターゲットが wasm のみに。脳みそが狐さん以下なので仕方ないですね。

作ったもの

名前はまだない。 github.com

こういう感じのコードを書くと、wasm モジュールとそれをラップした AudioWorkletProcessor が生成されます。デモはこちら

言語を作ったりするのは初心者もいいところなので、コスト削減のために大幅な割り切りをしています。

  • 動的なメモリの確保はできない
  • 型推論のような贅沢なものはもちろんない
  • 配列とループの長さは 128 決め打ち
  • 新しい型も作れない
  • if もねえ

逆に、あるもの

  • AudioParam 定義用の構文
  • VSCode 拡張

f:id:jinjor:20200415023948p:plain

エディタに色がつくと急にそれっぽい!

感想

wasm の特性なのかは不明ですが、 C っぽい言語が圧倒的に作りやすいというか、普通に普通の文法を動かすのが普通に大変で、それ以上の気の利いた文法とかはもう「動くんだから愚直に書けばいいでしょ」という気分になってきますね。 あと binaryen のレイヤーが高いのか、全然アセンブリ書いてる気がしないというか、結局アセンブリのこと何も分かってないかもしれない。うーん。

反省点としては、自作のパーサーコンビネーターがトークナイズしてないせいでホワイトスペースが至る所に出現してノイズが多いのと、VSCode 拡張(というか Language Server Protocol)で補間とか出そうとするとマメにコンテクストを管理しなきゃいけないなーっていうのに後で気づきました。直すの辛いけどやった方が良さそう。

まあとりあえず動いたので満足です。メンテナンスしたくないので広く公開することは多分ないけど。 早く FM 音源とか作りたい〜!

関数型言語 Faust で wasm な AudioWorkletNode を一瞬で作るサンプル

Faust って何

faust.grame.fr

音声信号処理に特化した言語というか 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

AudioNode を吐き出す

コンパイルターゲットの一つに 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

github.com

Web Audio API との相性

AudioWorklet の想定としては「ノード毎に processor を実装して組み合わせてね」ということなんですが、 Faust でなんでも出来てしまうので、ぶっちゃけ1ノードで済みます。 1ノードになんでもやらせるか、多数のノードを個別に作って組み合わせるのは Web Audio API でやるかは悩みどころ。

ちなみに現時点で吐き出されるコードは AudioParam のオートメーションが実装されていないので setTargetAt() などが効かないはず。ということは OfflineContext を使ったミックスダウンも上手く動かないんじゃないかと予想。気軽にコントリビュートするには重い課題な気がしますが、どうなんでしょうね。

github.com

VSCode 拡張を作って公開せずに使う

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 時の怒られを回避するため。

これで捗る。あとは進捗するのみ。