はい。
みなさんはそれぞれ「頭が良い」ということについて一言持っているはずです。インターネッツでも定期的に言及されてあーでもないこーでもないと喧々諤々無益な自転車置き場的議論が発生しているのが観測できます。
というわけで今日ぼくも石を投げます。
結論から言うと頭の良さとは「認識の解像度の高さ」です。
例えば今流行のCOVID-19へどう対策するか、となったとき、解像度が高ければ
続きを読むはい。
みなさんはそれぞれ「頭が良い」ということについて一言持っているはずです。インターネッツでも定期的に言及されてあーでもないこーでもないと喧々諤々無益な自転車置き場的議論が発生しているのが観測できます。
というわけで今日ぼくも石を投げます。
結論から言うと頭の良さとは「認識の解像度の高さ」です。
例えば今流行のCOVID-19へどう対策するか、となったとき、解像度が高ければ
続きを読むproviderとかfreezedの作者が作ってる state_notifier が当エントリとほぼほぼ同じことをやっているので依存が増えることを気にしない人はそっち使ってもいいんじゃないかとおもいます。
みんなの心はひとつでした。
先のエントリ BLoCにおけるリモートデータの状態遷移のパターンをくくりだす方法 - タオルケット体操 の書き方からもわかるように、そもそも僕はBLoCが嫌いです。
というか10年前にC#がRXをはじめたときからわかっていたはずですが、ObservableStreamは超かっこいいけど使い道の少ない技術です。フレームワークの裏側で使う分には便利ですが、表に出てくるべきではないでしょう。普通のGUIアプリケーションであれば99%のユースケースはただのコールバックで満たせます。
しかもBLoCはViewModelのパターン*1です。ViewModelとViewの接続にStreamが必須になるようなケースをどれだけおもいつけますか? コナミコマンド? せめてFlutterのTextFieldやControllerなんかがStreamをストレートにサポートしていればいいのでしょうが、もちろんそんなものはありません。
さらにもうちょっと高度なことをやろうにも、Dartの貧弱な型システムでは安全性の担保が面倒です。
というわけで、 例によって界隈はBLoCだるくてかなわんしシンプルなChangeNotifier(要はEventEmitterです)つかおーぜww という空気になっているようです。
ただし、ChangeNotifierはある意味でBLoCよりも退化しているパターンです。公式を鵜呑みにすると消耗します。
*1:なのでいわゆるApplication Stateを管理する方法について、Flutter界隈にデファクトは存在しません
今回のエントリは特定の言語に向けて書いているわけではありませんが、関数をサポートしていない言語では必然的にクラスをベースに実装していくことになるのである程度は対象となる言語は絞られます*1。
また特に説明がなければサンプルはTypeScriptで書きます。
さて、あくまで傾向としてではありますが関数を作れる言語の経験が短い人(例えばJavaやRubyですと、関数ではなくクラスに対するメソッドという形で実装することになります)は、単機能の振る舞いを実装するためだけであっても以下のようなコードを実装しがちです
class DoSomethinger { constructor(private something: Domething) {} public doSomething() { return dooo(something); // do something } }
これは
const doSomething = (something: Domething) => { return dooo(something); // do something };
と書きましょう。
今回は以上です。 :+1:
で終わらせるのはあんまりなので理由について述べます。
これにはいくつかの理由があります。代表的なものを以下で説明します。
単純に、関数で済むものは関数で書いた方があらゆる意味で簡潔です。
例に挙げた DoSomethinger
のようなクラスは、すぐに setAnything
のようなメソッドを生やしはじめて管理不能に陥ります。
引数と結果のみが存在する関数と比べて、クラスのコンストラクターとメソッドという中間状態にmutabilityが残る手法は不確定要素が増えてしまい、その分保守性や利便性が下がります。
またこのようなクラスは往々にして責務が曖昧なため、1ヶ月後の自分や他のメンバーが無計画に新しいメソッドを増やしがちです。そしてそれに伴って新たな状態setterが追加されてしまい、初期化や特定のメソッド呼び出しに暗黙の順序が増えていってしまいます。
それを避けるための方法としてはBuilderPatternなどが存在しますが、全てのオブジェクトをそのパターンにのっとって実装してしまうとプロジェクトはenterprise fizzbuzz化してしまいます。
色々と書こうとしたのですが、本稿に関してはとにかく「意味もなくクラスを使うのをやめろ」という以外にいうことがないことに気がつきました。
現代のプログラミング、特にGUIの中でも宣言的UIを採用しているもので最も重要な要素はimmutabilityです。クラス(そしてデータに振る舞いを持たせること)はあらゆる意味でそこと相性が悪いです。
上の理由では頑張ってクラスを「使わない理由」という形で文章をひり出しましたが、本来は逆で「クラスを使う理由」があるときに限ってクラスを使うべきです。
しかしほとんどのケースは関数の機能(クロージャーとか合成)でまかなえます。せいぜいメソッドチェーンでいい感じに書くインターフェース作りてぇなぁとかその程度のユースケースじゃないでしょうか。
特にReact Hooksがリリースされた今となっては、フロントエンドの実装でクラスが必要になることはほぼ皆無といっていいでしょう。悔い改めよ。
以前に書いた記事: https://hachibeechan.hateblo.jp/entry/pipeline-operator-will-be-good-friend-of-typescript ではまさにメソッドチェーンしたいという気持ちからクラスを使っています。
Dartのように、関数をサポートしていてもクラスとメソッドで実装したほうがベターということになりがちな言語も存在しています。その辺は大人の判断力でどうかひとつ、よろしくお願いします。
クラス使わないならどう書くんや的な疑問を持つ方への補足です。
こう書きます。
type User = Readonly<{ name: string; email: string; }>; const setName = (u: User, name: string) => ({...u, name}); const setEmail = (u: User, email: string) => ({...u, email}); >>> const userA = {name: 'user a', email: 'user-a@hoge.com'}; >>> userA2 = setEmail(setName(userA, 'user a2'), 'user-a2@hoge.com');
無限に括弧がネストされて辛い、メソッドチェインみたいに括弧よく書きたいという場合は先に紹介した記事を参考にすると上のコードをリファクタリングできます。
察しの良いかたはお気づきかもしれませんが、上の書き方は古のコーディングスタイルを踏襲しています。
そう、C言語です。
C言語ではstructで宣言したデータに対して、それをレシーバにとる関数群を用意して振る舞いを表現していました。
本稿で僕が推奨してる書き方はほぼそのまんま、その書き方を踏襲する形をとっています。古き良きじゃん〜。
このスタイルはStoreをPOJOに保つことを強く推奨しているReduxと特に相性が良いです。
また、やってみるとわかりますが構造的部分型による型検査を行い、クラス宣言されたものをのぞいてはランタイムに型情報が消滅してしまうTypeScriptと非常に親和性が高いです。あと継承とかいう主に自分の両足を縛り付けてうまく歩けないようにするための機能が付属してきません。
ちなみにPythonもメソッドの第一引数は必ずthis(self)を宣言する必要があるということになっていますが、これも実質的に僕が推奨しているようなスタイルと同じことをやっているわけですね(強引な結論)。
実際、Pythonでメタプログラミングをしていると関数を動的にクラスに紐づけていくようなことをしょっちゅうするわけですが、そういうことしているとメソッド呼び出しはselfを第一引数にする関数呼び出しの糖衣構文なんだ*2なぁというのを強く感じたりします。
ちなみにモダンな言語でもこのようなスタイルを取り入れている言語がありますね、みんな大好きGo言語とRustとか。それぞれ実際の振る舞いや指向は微妙に違っている気がしますが?
そういえばDiscordがGo言語で書いてたマイクロサービスの一つをRustで書き換えたら最高にハッピーだった、みたいな記事でまた界隈がザワついていましたが、ざっくりとだけ記事を読んだ自分でもその焦点がGCであって、つまりGCがボトルネックになっちゃったからGCがない言語(C++とか)で書き直したらすごく速くなったよ、また光もらっちゃったね。
そういう今までよくあるストーリーだったんだけどその乗り換えた先がC++じゃなくてRustだった、つまりあの記事でなんらかのX vs Y要素を見出すならC++ vs Rustなんだよね。
そういえばPOJOってなんかジョジョっぽいですよね。
おしまい。
Dart否定して終わりだとあもりにあわれ。
おれにこれしかなんだ! だから、これがいちばんいいんだ!というわけで現実と戦う方法を模索。
元ネタ: https://elmprogramming.com/remote-data.html
ちなみにBLoCはすでにオワコン化しつつあるようなので新規でコード書く場合はよっぽどのモチベーションがない限りは避けるべきでしょう。ちょろっとコンセプト読んだだけで微妙なのわかってしかるべきなのに今さら「やっぱダメで〜すww」とかほんともうね、そういうのはフロントエンドだけにしておいてくれ
ざっくり99%くらいのFlutterアプリはなんらかのリモートデータを取得して画面に表示する処理を持っているはずです。
その場合、データには
いまある"フレームワーク"の中でReduxほど覚えるのが簡単なものはありません。
しかし(僕を含め)多くの人が学習の最初に躓きと強い苦痛を覚えるようです。何故でしょうか。
私見ではありますが、これにはReduxがどういう性格をもったツールであるかということ、それによって現在マジョリティを占めるフレームワークとは異なる学習スタイルを求められることが影響していると考えております。
理由は追々述べていきますが、一番効率の悪いReduxの学習方法は「覚えよう」とすることです。
(異論はあるかもしれませんが)Railsなどのフルスタック系フレームワークを習得するときには実装例を写経するなどして暗記から学習していくスタイルはかなり有効だとおもいます。しかしReduxを同じように学習していくのは遠回りどころか、最悪で何一つ得られるものはなくただ壊れたアプリケーションが手元に残って終了します。