isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

Rust / Wasm でオセロ盤を作った。

Othello

 

オセロAIはまだ作れていない。

ただの石が自動でひっくり返る板。

 

Rustの使い心地

C++を書いている時に脳内で考えていたことが簡単な記号で言語化できるから嬉しい

GCのある言語しか使ったことない人にはちょっと敷居が高いかも

 

楽しかったところ

オセロの盤面を配列ではなくてintで表すようにしたこと。

オセロの盤面は64ますで、空・白・黒の3通りだから全部で 3の64乗通りである。これはRustのu128で表現可能である。

僕はu128の上に3進数を構築した。

これでスタックとかヒープとか気にしなくてよくなる。

 

ありがたかったこと

wasm-bindgenは最高である。

buildすると自動でd.tsを吐き出してくれるため、TypeScriptからasync import一発でライブラリを読み込める。型補完もバッチリ。

また、生WasmだとちょっとめんどくさいはずのArrayBufferのやりとりも抽象化されている。多分、wasm-bindgenなしなら自力でLenとOffsetをとってポインタを渡してあれこれしないといけないはずだ。

しかし、この通りの簡単な関数呼び出しで配列をやりとりできる。

github.com

新しいWebフレームワークを作る。

話の前提

ここだけの定義

  • クライアントとはWeb/Android/iOS/CLI/管理画面を指す
  • この話の中でGAEのDataStoreとMemcacheを同一のものとみなす。なぜなら多くの場合その2つを透過的に扱うライブラリを使うから
  • ローカルキャッシュを全てIndexedDBとひとくくりにする。AndroidSQLiteなどは適宜読み替えてほしい。また、サーバーから取ってきたデータは全て一度IndexedDBに入れることを想定している。
  • とりあえずReduxの話をする。適宜読み替えてほしい。
  • RESTFul APIの全体をGETした時の戻り値を、とりあえずRESTFul API全体と呼ぶ。
  • DBは今の所特定の実装の話をしていない。ただ、GAEのDataStoreを意識している。
  • ReduxのStateに入れるデータにはサーバー起源データとユーザー起原データがあると思う。掲示板アプリなら「ユーザー一覧」「書き込み一覧」などがサーバー起源データだ。「キーボードから入力中のテキスト」「現在開いている板」などがユーザー起源データだ。

作ろうと思っているフレームワーク

作ろうと思っているフレームワークはこういうものだ。


ユーザーがすること

  • DBの全データをRESTFul APIの全体に変換する関数を書く
  • RESTFul APIの全体をIndexedDBの全データに変換する関数を書く
  • IndexedDBの全データをReduxのStateに変換する関数を書く
  • 通常のmapStateToProps関数を書く
  • RESTFul APIのPUT・PATCH・DELETE・POSTがDBのどの部分を更新するのか関数で定義する

フレームワークがすること

  • ビルド時に上記のコードを読みキワッキワに最適化した処理を自動生成する

思い立った背景


今、サーバーサイドをGAE/Go + Gin、フロントエンドをReactで開発している。
現代的なプロダクトは、サーバーとクライアントの双方が大きなモデル層を持つようになった。どちらかがもう一方に依存しなくなったということだ。昔のフロントエンドはサーバーサイドプログラムの携帯ストラップのような扱いだったが、スマホの時代になったら変わった。今ではサーバーとクライアントは完全に粗結合になっている。
また、GinとReactの共通点として、両方とも薄いライブラリであることを強調している。流れの早い業界の中で、「その気になればいつでも捨てられる」ということがアピールポイントになっている。これは現代的なフレームワーク全体に共通していると思う。
このように「各レイヤーが自立して動くようになったことで粗結合になったこと」「薄いライブラリが流行っていること」がプロダクトの健全性に大きく貢献している。しかし、その分、コード量という観点ではプログラマの負担が増えている。トラック一台で運びきれないのでワンボックスを30台用意しましたと言われた気分だ。
今こそ、がっぷり四つの分厚いフレームワークを作るときだ。自分ならできるという自信がある。
自分はずっとサーバーとクライアントの両方を担当してきた。また、サーバーとクライアントの接合部も設計してきた。一人で両方を担当しているが、しっかりと粗結合にしてきた。だから、どのようにサーバーとクライアントを接合するべきかわかっているつもりである。
悪魔のように粗結合に。天使のように密結合に。

根底にある思想


サーバと全てのクライアントはRESTFul APIの型のみに依存するべきだ。その向こう側にどんな処理があるか一切知っていてはいけない。
RDB用語で「関数従属性」という言葉がある。例えばA+B=Cは3つの内2つ値が定まればもう1個の値も定まる。このように全体の内一部がわかればほかも割り出せることを関数従属性があるという。ソースコードにも当てはまる。フロントエンドにこういうコードがあるからサーバーサイドにこういうコードがあるはずだと特定できることがある。これはソースコード間に関数従属性があると言えると思う。その場合、プログラマがコードを書くよりコンピューターにコードを生成させたほうが安全なはずだ。

支柱になる考え


DBの全データからRESTFul API全体に変換する関数のASTを取れば、RESTFul APIの一部をGETするリクエストを処理するためにDBのどのデータを取り出さなければいけないか算出できるはずだ。
ReduxのStateのサーバー起源の部分とIndexedDBは虫食いコピーの関係になるということにしておくといいと思う。IndexedDBに保存されているデータの内必要なものだけを随時Reduxにコピーして使う。
RESTFul API全体とIndexedDB全体も虫食いコピーの関係になる。
キャッシュ戦略はコンピューターが立てたほうが精度が高いはずだ。最も素朴な実装をプログラマが書き、それをもとにコンピューターにキャッシュ戦略を立案・実行させるといいと思う。
コードの自動生成という言葉を使っているが、

 を現時点では区別していない。一番合目的なものを適宜選ぶ。ただし、IDE補完との相性を重視する。また、ソースコードを自動生成する場合は生成の冪等性を重視する。つまり、一度生成したソースコードを人間が修正したら不磨の大典と化すことは避ける。

 

避けること

これはプログラミングが苦手な人のための簡単なフレームワークではない。自力で正確に型定義し、仕様をシンプルな純粋関数で表現でき、ちゃんとテストコードが書ける人しか使えないと思う。
jQueryWordPressプラグインにありがちな「環境上のどの変数を参照しているのかわからない、どんな副作用をもたらすのかわからない、でもインストールすれば勝手にいい感じにやってくれる」ようなものではない
少なくとも、

  • Cookie
  • DBのSchema
  • RESTFul APIの型
  • 公開するRESTFul のPOST・PUT・DELETE・PATCH
  • ReduxのStateの型
  • ReactのPropsの型
  • IndexedDBの型

は各プログラマが責任を持って完全に支配するべきだと考える。

このフレームワークは何をしてくれるのか


ユーザーがどこかの画面に来たときに、その画面を表示するのに必要なデータをRESTFul API、IndexedDBから自動で取り出してReduxのStateに読み出しておいてくれる処理を生成する。すると、プログラマはStateに必要な全てのデータが入っている前提で処理を書くことが可能になる。
また、各GET APIも自動生成してくれる。DBの全データをAPIの全データに変換する関数があるからプログラマの意図した通りに生成されるはずである。
次に、キャッシュレイヤーを自動生成する。静的ファイルを使うもの、DBに非正規化したデータを保存しておくもの、クライアントのファイルにキャッシュさせておくものなどがあると思う。
さらに、DBの一部分を更新した時に、クライアントのIndexedDBをPushで変えたい。それも自動化できる。

クリーンアーキテクチャを俺なりに解釈する

クリーンアーキテクチャの意図の話をする。

具体的な実装の話はしない。

 

変数の純度

まず、変数の純度という概念を提唱したい。

ある値がどれだけ整理されているかの指標である。

エントロピー、ないし抽象度ということも可能だと思う。

 

Stringの"1,800"よりintの1800のほうが純度が高い。

JSTUTCならUTCのほうが純度が高い。

誕生日・現在時刻・年齢や定価・税率・税込価格のように3値の内2値が定まれば第3値が導出できるものは、3つ目を省いた方が純度が高い。

不正値が混じっているかもしれないデータと不正値が混じっていないことを確認したデータなら確認したデータの方が純度が高い。

おおよそRDBの正規化と同じ考え方だ。

 

純度の高低の見極め方

より汎用的に使いまわせるほうが純度が高い。

A→Bに変換する手間とB→Aに変換する手間を比較して、簡単な方が純度を下げる操作。

より不整合が起こりにくいデータ構造のほうが純度が高い。

よりその言語の文法や型システムを活かしたコードになる方が純度が高い。

純度を上げれば上げるほど、その値の表現の仕方は1通りに収斂する。

クリーンアーキテクチャの皮とは

純度の上下である。内側に行くほど純度が上がる。外側に行くほど純度が下がる。

 

この純度の山で各機能を表現するのがクリーンアーキテクチャだ。

実は数十行のコードでも、純度の上下を意識すると綺麗なコードがかける。

そこでclass分けやパッケージ分けにこの考えを適用しようという意図だ。

 

コードの読みやすさ、編集しやすさ

ここで読みやすく変えやすいコードの特徴を考えたい。

これは数十行のコードの読みやすさの話ではない。

その段階ではケツカンマの有無やタブ・スペースなどが議題に上がる。

そうではなく、数十万行のコードの読みやすさのことだ。

アーキテクチャ全体で保守性を高める特徴である。

 

第一に、処理が一方向に進む必要がある。良いコードはパッケージの分け方と処理の流れが対応する。どのサブパッケージの関数がどのサブパッケージの関数を呼び出すか明快に図示できる。それは一方向の矢印になる。大きな円になる場合もある。

 

第二に、同じことをするコードが複数あってはいけない。これはよく言われることだ。残念なことにプログラマーの心構えの問題とされがちである。そうではなく、これはアーキテクチャの問題なのだ。前の処理で潰しておくべき問題を解決せずに次の処理に入るから似たような関数を乱立させる羽目になる。

 

第三に、何かを判定するコードとその判定をもとに作業をするコードは厳格に別れなければいけない。サーバーサイドにおける作業とはHTTPレスポンス、DBに書き込み、キャッシュの破棄や作成、Push通知などだ。フロントエンドにおける作業とはファイルの書き込み、ダイアログの表示、画面の切り替えなどだ。厳格に分けれるほど使い回しが効くし、わかりやすいログが出せるし、修正しやすくなる。時々複雑なFor文の中の複雑なIf文の中で複雑なDB書き込みをしている人がいる。そういうコードはわかりづらいし、間違いに気づいたときにブレークポイントを貼ってもどの変数を見ればいいのかわからない。

 

第四に、入力が基準を満たしているか判定する処理が散らばっていてはいけない。本来それは処理の冒頭で行われるべきだ。そして、後続の処理は正当な入力がなされた前提で書かれるべきだ。

 

第五に、Util関数がたくさんあってはいけない。Util関数がなくなることはないと思うが、正しく責務が分割できないためUtil関数が増えすぎるのは設計が間違っているのだろう。

 

クリーンアーキテクチャとは

この五つの特徴を純度の観点で考えると単純な構造が見えてくる。純度が一つの大きな山の形になればいいのだ。処理の冒頭で入力値の純度を上げる変換をする。純度が上がり切ったら純度を下げる変換をする。

どうすればそれを実現できるかを考えると、クリーンアーキテクチャが導き出せる。

 クリーンアーキテクチャはこう書く

まず、最も純度の高い値の型を書く。これがクリーンアーキテクチャのEntitiesだ。ショッピングカートシステムならUser、Shop、Itemなどの型が該当する。

 

次に、Entitiesをインスタンス化するコードを書く。Entitiesの境界の外側からやってきたデータはすべてこの部分を通りEntitiesに変換される。例えばサーバーサイドなら

  • HTTP Request
  • Database
  • CSVファイル

などが該当する。フロントエンドなら

  • Http Response
  • Indexeddb
  • Mouse Event

などだ。この部分には

  • parseInt
  • json.Unmarshal
  • throw文

連発されるだろう。Optional型を持っていない資源からの入力の場合、ここでOptional型に変換するといい。注意してほしいのは、この段階で一切の判断や具体的な処理をしてはいけない。ただ単に純度を上げるためのコードだ。

 

次に、Entitiesと任意の引数から別の表現形式に変換するコードを書く。これがクリーンアーキテクチャのUseCaseに該当する。任意の引数とはその機能が実行されるトリガになったイベントの付加情報のことだ。ユーザ情報取得APIであれば、URLのクエリパラメータに指定されたUserIDなどが該当する。別の表現形式とは、サーバーサイドなら

  • HTTP ResponseのBody
  • DB操作コマンド
  • Push通知のコマンド

などだ。フロントエンドなら

  • ViewModel
  • ローカルファイル書き込みコマンド
  • HTTP Requestのパラメータ

などだ。ここでUseCaseを3層に分ける。

  • Entitiesの関数たち
  • 表現形式の型
  • 各機能の仕様だ。

Entitiesの関数たちとは、例えば

  • 商品の価格と税率から税込価格を計算し三桁ごとのカンマを入れる関数
  • ユーザーの誕生日から年齢を計算する関数
  • 購買履歴から通期の売上高を計算する関数
  • ユーザー一覧から20歳以上のユーザーのみを抽出したユーザー一覧を返す関数
  • AさんがBさんをフォローしているか返す関数

などだ。

特定の処理に使うことを想定しておらず、副作用がない。Entitiesのインスタンスメソッドとしてもたせるといいだろう。Entityのリストはリストを継承した専用クラスを作っておくとそこにメソッドをもたせれて良い。

表現形式の型とは

  • 画面に何を表示するか
  • 何をHTTPで返すか
  • DBに何を書き込むか
  • だれかにPush通知を送るのか

などを型で表現したものだ。

各機能の仕様とは、ビジネスロジックそのものだ。システムの各機能に相当する。サーバーサイドならAPI一個一個のことだ。これはEntitiesを引数に取りEntitiesの関数たちを活用しながら表現形式の型に変換する関数だ。

 

次に、コマンドをもとに外部APIを操作する処理を書く。ここがInterfaceAdapter層だ。どうせこの層はろくにテストできないので、極力簡単なプログラムになるようにコマンドの型を見直そう。

 

最後に各層をつなぎ合わせるコードを書く。

 

これがクリーンアーキテクチャだ。

様々な入力を最も純度が高い形に変換してから純度を下げるようにビジネスロジックを書いていくことがわかると思う。

 

注意点

いくつか絶対に守ってほしいことがある。

山頂を通過しないコードを書かない。

文字の”1,800”に消費税をかけて”1,944”を出力する関数などを書いてはいけない。

そのほうが全体のコード量が減る。通りの数問題の話だ。不必要な密結合も避けられる。Entitiesをハブにして疎結合にするべきだ。

 

ビジネスロジックの戻り値をもとに別の判断をするコードを書いてはいけない

一旦Entitiesから別の表現形式に変換したら、それをもとに別の処理をしては行けない。それらは劣化した情報であって、それに依存したコードを書くと保守性が落ちる。そもそも同じ処理ならEntitiesを引数に取った関数を書くほうが綺麗にかけるはずだ。また、クリーンアーキテクチャ中心部に集約したいコードが散逸するという問題もある。例えば、「このユーザー一覧をHTTP Responseでブラウザに返せ」というUserListResponseコマンドがあったとしよう。ある日、一度に返されるユーザーの数が多すぎるから登録日時降順にソートし上位20件を返すように変更することにした。一見、既存のUseCase層が返すUserListResponseコマンドの中をソートして配列をスライスすれば事足りるかもしれない。しかし、

  • ユーザー数を登録日順にソートする
  • Sliceする

というコードがビジネスロジックの外側に漏れ出している。その関数はUserEntitiesを引数にするように(あるいはUserEntitiesのインスタンスメソッドに)定義されるべきであり、ビジネスロジックはその関数を使うべきである。

 

以上がクリーンアーキテクチャの考え方だと思う。

おさらいしよう。

まず、もっとも純度が高い値の型を定義する。

入力値をその型に変換する。

その型を引数に具体的な処理を書く。



Alt SQLを作ることにした。

取りかかれるのはだいぶ先だと思うけど。



現行のSQLは重大な問題をいくつも抱えている。

だから、新しい文法のSQLが必要だ。

ちょうどDartが新しい文法でJSの世界を救ったように、SQLにも救世主が必要だ。



今の所SQLRDBを操作する唯一の言語だ。

RDBの背景には集合論を土台にした素晴らしいセマンティクスがある。

しかし、SQLの文法が悪いせいでRDBが扱いにくいものだと思われている。

よくあるイメージとして、これは間違った考えだが、KVSはRDBよりかんたんに扱えると思っている人がいる。

この状況を放置すれば人類は安直にKVSに流されてしまう。

 

また、RDBは速度に限界があると言われている。

いくつか話題になりやすい理由を上げる

  1. SQLのパースが遅い
  2. JOINが遅い
  3. ロックが遅い
  4. 挿入時の整合性確認が遅い

1はバイナリフォーマットを定義するとことで回避するべきだ。

2〜4はシステムの安全性とトレードオフだ。

RDBが正規化・トランザクション・整合性確認(参照整合性+チェック制約)という機能を持っているから安全なアプリケーションが作れる。

しかし、速度のためこの機能をあえて使わない選択がされてしまうことがある。

もっと速度を出しやすい文法にするべきだ。

 

速度を出しやすい文法とはどういうことだろうか。

RDBの低レベルのAPIを露出させてプログラマが最適化すれば速くなる。

しかし、それはやめたほうがいい。

逆に、プログラマはより抽象度の高いクエリを書くべきだ。

RDBMSによりたくさんの意図を伝えることで、RDBMS側が自動で最適化できるようにするべきだ。

 

実現方法として、トランスパイルすると標準のSQLに変換できる文法を新しく設計する。

 

解決されるべき課題

TABLEに名前空間がない

RDBではグローバル名前空間に全てのテーブル名がぶら下がっている。

しかし、別のテーブルとJOINせずにSELECTすることはほぼないと言い切れるテーブルもある。

実際のシステムでは半数近くがこのような二級市民テーブルだろう。

僕はMySQL WorkbenchやIntelliJSQL編集機能といったSQL専用エディタで開発をする。

専用エディタは入力中にコード補完をしてくれる。

FROM句やJOINキーワードを書こうとすると候補を上げてくれるのだ。

ところが、大きなシステムでは候補が大量に出てきてあまり意味がないことがある。

「このTABLEとJOINする可能性があるのはこのTABLEに決まっているのではないか」「トップレベルのFROM句にこのTABLEが出てくることはないのではないか」と思うことがある。

そこで、大量のテーブルを分類して名前空間を分けることができるべきだ。

そしてprivate句とpublic句を定義できるようにしたい。

 

フィールドに名前空間がない

UserテーブルやProductテーブルに、ものすごくたくさん列が存在するシステムに覚えはないだろうか。

 

フィールドを分類して垂直分割をすることも考える。

しかし、それはパフォーマンスの問題につながる。

そもそも垂直分割はアンチパターンということになっていると思う。

もっと簡単に列をネストできるべきだ。

 

例えばGo言語で

 

type User struct {

fieldA int

fieldB int

fieldC int

fieldD int

...

}

 

 

と長くなってきてしまったら

 

type User struct{

 subTypeA SubTypeA

 subTypeB SubTypeB

}



type SubTypeA struct {

 fieldA int

 fieldB int

}

 

type SubTypeB struct {

 fieldC int

 fieldD int

}

 

 

 

のように分割する。 同じようなことができてほしい。

これをRDBで扱う時はのような意味に変換するか?

もちろん、「構造体を保存できるようになりました」という話ではない。

実際は並列にカラムが並んでいるが、その中で名前空間を表現できるシンタックスシュガー、という意味に解釈する。

各フィールドは木構造を直列に変換して表現する。

オブジェクトの階層の区切り文字「.」を別の文字に置き換える。

 

// 保存したい値

USER.BODY.HEIGHT = 170

USER.BODY.WEIGHT = 60

USER.MENTAL.COURAGE = 5

USER.MENTAL.CALMNESS = 10

 

// 新SQL

CREATE TABLE USER(

 BODY (

   HEIGHT INT,

   WEIGHT INT,

 ),

 MENTAL (

   COURAGE INT,

   CALMNESS INT,

 ),

);



// トランスパイルされたSQL

CREATE TABLE USER (

 BODY__HEIGHT INT,

 BODY__WEIGHT INT,

 MENTAL__COURAGE INT,

 MENTAL__CALMNESS INT,

);



// 取り出し

SELECT BODY.* FROM USERS;

 

 



親子関係を直接表現する方法がない

一般にTABLEの親子関係を作るにはCREATE TABLE文内でFOREIGN KEYを使い、クエリ時にJOINする。

これをもっと簡易に書きたい。

それができないせいで配列を保存できるようなRDBが出てきてしまうと思う。

 

親TABLEが一つの場合

一旦複数の親を持つTABLEのことは置いておこう。複雑だ。

自分が考えたのはこのような文法だ。

 

CREATE TABLE USERS (

 ID INT,

 NAME VARCHAR

);

 

CREATE TABLE USERS.FAVORITE_FOODS (USERS.ID AS USER_ID) (

 USER_ID,

 FOOD_NAME VARCHAR

);

 

 

まずCREATE TABLE USERS.FAVORITE_FOODSで、FAVORITE_FOODSはUSERSにぶら下がっているということを明示している。

さらに(USERS.ID AS USER_ID)でUSERS.IDはFAVORITE_FOODSのUSER_IDと同じだと宣言している。

これによりFOREIGN KEY文よりわかりやすく親子関係を表現している。

 

クエリはこのようになる。

カレーが好きな人一覧なら

SELECT * FROM USERS WHERE USERS.FAVORITE_FOODS IN "カレー";

のような感じになるだろう。

このあたりは、相関クエリのためにサブクエリを省略できる文法として設計するか、

集計関数のためにFROM・WHERE句を省略できる文法として設計するかちょっと悩んでいる。

 

親TABLEが2つある場合

親TABLEが2つある場合、上記の文法では対応できない。

この場合、そもそも2つのテーブルをJOINした結果のTABLEを定義できるようにし、

そのTABLEに上の文法で子供を作れるようにする。

ユーザー一覧のUSERSと食べ物一覧のFOODSがあり、各ユーザーの好きな食べ物をFAVORITE_FOODSに保存する。

 

CREATE JOINED TABLE USERS, FOODS AS USERS_FOODS;

 

CREATE TABLE USERS_FOODS.FAVORITE_FOODS (USERS_FOODS.USER.ID as USER_ID , USERS_FOODS.FOODS.ID as FOOD_ID) (

 USER_ID,

 FOOD_ID,

);

 

 

一見ただのCREATE VIEW文と変わらないが、あえてCREATE JOINED文を定義することに意味がある。

CREATE VIEW文はあらゆることに使えるのでなんのために作ったViewかわからない。

それに対してわざわざCREATE JOINED TABLE文を使っていたら、

この組み合わせに対して子TABLEをぶら下げるためにこの中間テーブルを作ったというメッセージが伝わりやすい。

 

ダイヤモンドになると最悪

親子関係がダイヤモンド型になる時に、外部キーを正確に書くのは非常に難しい。

 

ライトテーブルに夕食のメニューテーブルと乗客テーブルがぶら下がり、

その両方にぶら下がる乗客の食事メニューテーブルのことを考えよう。

 

CREATE TABLE PASSENGERS_FOODS (

 PASSENGER_ID int,

 FOOD_ID int,

 FOREIGN KEY PASSENGER_ID REFERENCES PASSENGERS (PASSENGER_ID),

 FOREIGN KEY FOODS_ID REFERENCES FOODS (FOODS_ID)

);

 

 

これではだめだ。

このようにうっかりするとフライトAに乗る乗客にフライトBのメニューを設定できるテーブルが出来上がる。

 

正確にはこうだ。

CREATE TABLE PASSENGERS_FOODS (

 FLIGHT_ID int,

 PASSENGER_ID int,

 FOOD_ID int,

 FOREIGN KEY (FLIGHT_ID, PASSENGER_ID) REFERENCES PASSENGERS (FLIGHT_ID, PASSENGER_ID),

 FOREIGN KEY (FLIGHT_ID, FOODS_ID) REFERENCES FOODS (FLIGHT_ID, FOODS_ID),

);

 

この場合CREATE JOIND TABLE文をカスタマイズすればいい。

 

CREATE JOINED TABLE PASSENGERS, FOODS AS PASSENGERS_FOODS

 REPRESENTATIVE KEY (PASSENGERS.FLIGHT_ID, FOODS.FLIGHT_ID) as FLIGHT_ID;

 

 

このようにPASSENGERSとFOODSのデカルト積からFLIGHT_IDが一致しない行を取り除いている。

おかげで、このJOINED TABLEに子テーブルをぶら下げれば、必ずFLIGHT_IDの掛け違いが起こらないようになった。

 

CREATE TABLE PASSENGERS_FOODS.PASSENGERS_FOODS

 (PASSENGERS_FOODS.PASSENGERS.PASSENGER_ID as PASSENGER_ID,

  PASSENGERS_FOODS.FOODS.FOODS_ID as FOOD_ID) (

 PASSENGER_ID,

 FOOD_ID,

);

 

 

似たようなテーブルを複数作るのがめんどくさい

よく、RDBには継承がないと言われる。

しかし、そのような大掛かりな機能を導入するのは難しい。

欲しいのは他のテーブルのCREATE TABLE文をコピペする構文だ。

JSのスプレッド演算子のようなものがあればいいと思う。

 

CREATE ABSTRACT TABLE PERSON (

 NAME VARCHAR,

 AGE INT,

);

 

CREATE TABLE CUSTOMER (

 ID int,

 ...PERSON,

 PHONE VARCHAR,

);

 

CREATE TABLE EMPLOYEE (

ID int,

...PERSON,

JOB VARCHAR

);

 

 

こんな感じだ。

 

3値論理が複雑

やはりOptional型は必要だと思う。

よく「NULLとFALSEを比較したらどうなるか」などのまとめ表がSQLの教科書に載っている。

ありがたいと思うが、そもそもNULLABLEとそれ以外の値を演算できるのがおかしい。

NULLABLEをunwrapせずに演算しているところをすべてコンパイルエラーにするべきだ。

JavaPHP等のRDBMSドライバもNULLABLEのintはintではなくOptional<Integer>で返すべきだ。

新しく追加されたcase文でこれまでに比べれば便利になったが、しかし依然として文字の量が多いので、

もっと便利な構文がほしい。

RDBにもOptional型を追加し、get(), orelse(), is_empty(), is_present()等の関数を追加するべきだ。

 

ユーザー定義型

また、ユーザー定義型も欲しい。

しかし、RDBにおいてスカラ値ではなくベクトル値を保存できてしまうのは良くない。

必要なのは、TABLEでは複数の列を使って値を保存するが、SQL上は一つの構造体として扱えるものだろう。

似たようなものを挙げるとC++だ。

C++のクラスはオブジェクトをメモリ上にどのように表現するかプログラマが責任を持って決めないといけない。

同じようにRDBも構造体を保存できる機能を追加するのではなく、SQLでは構造体として扱われる物をTABLE上でどのように表現するかプログラマが責任を持って決めるべきだ。

例えば何型の列が何個いるか、どんなチェック制約が必要かについて決める必要がある。

 

ついでに代数的データ型もほしい。

 

WITH句にクエリではなく関数を書きたい

自分はJavaScriptなどで高階関数を書くとき、その式が長くなりすぎるならローカル関数を定義し高階関数の中を簡略化することがある。

例えば

function filterUserByGender(users, gender) {

 

 function genderEqual(user) {

    // これならここの式がどれだけ長くなってもストレスがない。

    return user.gender == gender;

 }

 

 return users.filter( u => genderEqual(u));

}

 

SQLも同じように書きたい。

 

複雑な相関クエリをCREATE TEMPORARY TABLE文で簡単にするのはよくあるパターンだ。

クエリによっては大きな効果が得られる。

TwitterでWITH句を使うように教えていただきました)

しかし、相関の仕方が複雑なとき意味をなさないことがある。

これはある会社の各課で誰が一番若いかを集計するクエリだ。

 

SELECT

ID , NAME

FROM

EMPLOYEES

WHERE AGE = (SELECT MIN(AGE) FROM EMPLOYEES as E2 WHERE DIVISION = E2.DIVISION);

 

 

これを一時テーブルで簡略化してみよう。

 

CREATE TEMPORARY TABLE DIVISION_MIN_AGE AS

(SELECT DIVISION, MIN(AGE) AS AGE FROM EMPLOYEES GROUP BY DIVISION);

 

SELECT

ID , NAME

FROM

EMPLOYEES

WHERE AGE = (SELECT AGE FROM DIVISION_MIN_AGE WHERE DIVISION_MIN_AGE.DIVISION = EMPLOYEES.DIVISION);

 

 

このように全く意味がない。

なぜ意味がないのか。

このクエリで一番冗長な箇所はサブクエリのWHERE句だ。

「DIVISION_MIN_AGE.DIVISION = EMPLOYEES.DIVISION」

この部分を外に出す方法がないから意味がないのだ。

 

そこで、WITH句に関数を宣言できるようにしたい。

 

WITH FUNCTION IS_MIN_AGE(VARCHAR(10) DIVISION, INT AGE) AS (

 (SELECT MIN(AGE) FROM EMPLOYEES WHERE EMPLOYEE.DIVISION = DIVISION) == AGE

)

SELECT

ID , NAME

FROM

EMPLOYEES

WHERE AGE = IS_MIN_AGE(ID, AGE);

 

大変綺麗になったと思う。

 

木構造でResultが取りたい

これはSQLというよりドライバの問題かもしれない。

あるクエリの結果の行をもとに別のクエリを投げる、ということがある。

アプリケーション側が一回目のクエリの結果を使わないなら、WHERE INを使えば解決する。

しかし、アプリケーション側が一回目のクエリの結果を使うならば、少なくとも二回に分けてクエリを投げなければいけない。

下の表を見てほしい。

 

 

f:id:isyumi-net:20180810190449p:plain

 



この表を出力するにはアプリケーション側で下のような2重For文を書かなければいけない。

先に都道府県一覧を取得し、その結果をもとに市町村一覧を取得する。

疑似コードだがこんな感じであろう。

 

foreach($pref_row as $connection->fetchAll("SELECT * FROM PREFECTURES")) {

 foreach($city_row as $connection->fetchAll("SELECT * FROM CITIES WHERE PREFECTURE_ID = ${pref_row->id}"){

   print $pref_row->name, $city_row->name, $city_row->population;

 }

}

 

 

少し悔しい。

SELECT文が木構造に対応してくれたらいいのだ。

 

SELECT

PREFECTURE_ID ,

PREFECTURE_NAME,

VISITOR (SELECT * FROM CITIES WHERE PREFECTURES.PREFECTURE_ID = CITIES.PREFECTURE_ID) AS CITIES

FROM PREFECUTRES;



foreach($pref_row as $connection->fetchAll($sql)) {

 foreach($city_row as $pref_row->cities){

   print $pref_row->name, $city_row->name, $city_row->population;

 }

}

 

 

綺麗に書けた。

冒頭でAlt SQLSQLに変換して使うと書いたが、もしRDBMSがこの構文に対応してくれるならパフォーマンスが向上するであろう。

上のコードの場合RDBMSは1回目のクエリ結果をもとに二回目のクエリがやってくるということを知らない。

しかし下のコードであれば、HDDからRAMに読み込んだデータやカーソルを意図的に使いまわすことが可能になる。




コード書く工程は末端作業か?

お断り
  1. 今起きている何らかの喧嘩は関係ありません。未来に向けて書き残しておこうと思ったから書いています。
  2. 「そうやって現場を大事にしないから日本はだめになったんだ」的な話も、今は関係ありません。それはそれ、これはこれ。あくまでソフトウェア産業の話をします。

 

まず、僕の現状認識を説明する。これは自分が体験したことではない。また、統計など信用のおける証拠があるわけでもない。あくまで、伝聞で得たイメージである。

ソフトウェア産業では10年以上前からプログラミングが儲からない仕事になっていくといわれていた。商品開発とアフターサービスの付加価値が高まり、逆にプログラミングの付加価値が下がるであろう。コーディングはどんどん発展途上国に外注されていく。だから日本人は生き残るために別の道を選ぶべきだ。いまプログラマーとして働いている人は設計や管理職などのキャリアへ進むべきだ。というのが定説だった。

しかし、その変化は起こらなかった。プログラミングの価値はますます増大し、プログラマーは稼げる仕事になっていった。多くの先進的な企業が差別化のために能力の高いプログラマーを高報酬で雇い始めた。

ところが、日本企業の多くがその変化に気づいていない。個人の感覚としてもプログラミングを軽視する感覚が根強い。また、企業の採用・人事制度でもプログラマーは立場が低く扱われている。

というのが僕の認識だ。確かめたわけではないがそこそこ正しいだろう。

 

なぜ、このようなことが起こったのか。この先は完全にただの憶測だ。

まず、プログラミングが儲からなくなっていくと思われていたのはなぜか。それは製造業の影響だろう。製造業にはスマイルカーブという概念がある。企画→設計→製造→流通→マーケティング→アフターサービスという各工程の付加価値はお盆のような形をしており、両端の企画とアフターサービスが一番高く、次いで設計とマーケティングが高く、製造と流通は最も低い。この考え方をソフトウェアに当てはめるとプログラミングは製造工程に当たる気がしてしまうのだろう。ここから得られる教訓は製造業とソフトウェア産業は全然別物であるということだ。少しフォローするなら、これも伝聞だが、プログラム言語がアセンブラCOBOLなどしかなかった時代のソフトウェア産業人海戦術の域を出ておらず、製造業と大差なかったのだろう。そもそもソフトウェア産業などという概念自体がなく、それは製造業の一種と考えられていたのなら、そういう勘違いが発生するのも無理はない。

また、なぜプログラミングの価値が落ちなかったのか。これは大変興味深い謎である。僕がTLで見たのはこんな意見だ。

  1. 優秀な人とそうでない人の差が大きすぎるから
  2. 設計とコーディングが別工程だと思われていたが、実は不可分だったから
  3. ソフトウェア開発が高度化しているから
  4. プログラミング可能なものが増えたから
  5. プログラマー文化の本流はヒッピー文化でもあり、反権威的なプログラマーたちがそういう流れを作ったから
  6. シリコンバレーを中心とする経済圏が過熱しすぎていて相場を押し上げているから
  7. プログラミングは人間の限界を拡張する手段であり(エンハンスメントとかオーギュメントとか難しい言葉を使ってた)プログラミングを覚えた人間は一段上のステージに行くから

どれもあり得そうな話だ。僕は4を推しておく。

最後に、なぜ日本人はその変化に気づけなかったのか。この疑問を考えるには、そもそもアメリカでも気づかれていない可能性を考えなければいけない。今をときめく映画評論家町山智浩さんによればアメリカ人の半分はニューヨークの場所を知らないらしいので、Googleプログラマーが高給であることを7割くらいの人が知らない可能性は高い。であれば、日本だけが遅れているという前提すら危うい。まあしかし、いったんそこは置いておいて考える。まず企業の中では、多くの日本企業の昇進制度上、会社を動かしているのは10年以上前に活躍していた人が多いはずだ。つまり、そういう考え方でうまく回っていた時代の人だ。そうであればプログラミングの重要さを前提とした経営判断がし辛いのは想像できる。個人の問題としては、各国の経済成長の動向が大きいと思う。日本はこの数十年の間に途上国に次々と追い越された。日本が追いかける側の国であればアメリカでプログラミングがもうかっているらしいという情報が入った瞬間にみんながまねしたであろう。しかし、一度製造業で世界を制した成功体験があるだけになかなかそこから抜け出せない考えの人も多いと思う。

 

では、今僕たちはどうするべきか。お断りにも書いたが「現場を大事にするべきだ」みたいなわかりやすいスローガンに回収しないほうがいいと思う。もちろん、それはそうなのだが、残念ながら付加価値の多寡というのは必ずある。職業に貴賎はないが給料は違う。個人戦略としてどこが儲かるかを判断する必要はあるし、企業としてもどこで差別化するかを見極めて、誰がやっても同じな業務は安く抑え、勝因になりえる業務に強い人を雇うのは当然のことだと思う。少なくとも僕らはプログラマーとして能力を伸ばすべきだ。管理職やマーケ職の方が付加価値が高くなる可能性よりもプログラマーの付加価値が伸び続けるほうに張りつつ、しかし、それが逆転する兆候があればすぐに身を翻す機敏さも持ち合わせなければいけない。

ポリテックと型システム

金融とテクノロジーフィンテックで行政とテクノロジーがゴブテックなら、政治とテクノロジーの融合はポリテックだそうだ。

政治をよくできそうな技術として、どうも政治家さんにはAIがモテモテのようだが、型システムの方が味がいい気がする。

例えばこの意見を見てどう思うか?

最近少年犯罪が増えているので、若者は全員自衛隊に入って根性を鍛えるべきだ。

そういう考え方の是非はともかく、前提知識として少年犯罪自体は減っている。だから、最初からこういう意見は取り合わなくていい。

また、これはどうだろうか?

 自民党に投票しなかったということは民主党を支持するってことだな?

 これは必要条件と十分条件の区別がついていない人だ。

黙秘するということは犯行を認めるということですね?

これは三値論理と言う。三値論理的にこの主張は間違っている。

 

型システムを使えばこういう無駄な議論に時間を使わなくてよくなる。「わかりきった事実誤認に基づいた意見」「真偽や善悪以前に理屈が通っていない理屈」をあらかじめ排除できる。

どこか一箇所に型定義リポジトリを作りそこに自明の知識をプールしておけばいい。

「少年犯罪は減っている」「東京は出生率が低い」「女子高生が産婦人科に行く理由」「日大と日本体育大は別」など、間違えそうなことは事前に登録しておいてあげれば無知を晒して恥ずかしい思いをする人も減るだろう。

思い切って

  • 政治家は国会で型チェックを通らない話をしてはならない
  • 国民は政治家に型チェックを通らない要望をあげてはならない

というルールにしておけばいい。

 

逆に誰でも簡単に意見が言えてよくなるという効果もあると思う。

例えば、映画は犯罪率を低下させるのではないかと思っている人がいたとしよう。しかし、実際にそうなのか調査結果がどこにもなかったとする。こう意見を言ったとする。

映画には犯罪率を低下させる効果がある。経済発展のために映画を見るべきだ。

映画を見るべきだというのはその通りだと思うが、話の前後に繋がりがない。これでは相手にしてもらえない。

そこで

映画には犯罪率を低下させる効果がある。犯罪率の低下のために映画を見るべきだ。

とか

映画には犯罪率を低下させる効果がある。犯罪率の低下は経済発展に効果がある。経済発展のために映画を見るべきだ。

などであれば事実関係はともかく理屈は通っている。「じゃあ映画に本当に犯罪率低下の効果があるか、犯罪率低下に経済発展の効果があるか調べるから予算くれ」と話を繋げられる。このように事前に理屈を整頓する機会を与えられることによって、意見を言いやすくする効果があるのではないか。

 

 

 

ブラウザに自動ドネーションAPI機能がほしい

以前からコンテンツ提供者にもっとお金が支払われるべきだと思っていた。

こういう仕組みがほしい。

  • 僕がChromeに「毎月5,000円をコンテンツ提供者に支払う」と設定する
  • Chromeは俺の行動履歴からどのサイトにどのくらい滞在しているかを集計する
  • 月締めで滞在時間に応じて自動的に5,000円を各サイトに分配する

この方法が必要な理由はこうだ。

  1. たとえサイト運営者がPaypalなどでドネーションの申し込み口を設けていたとしても、いちいちそれをクリックするのがめんどくさい
  2. ある程度お金を払っていることを忘れられるUXでないともったいないと感じてしまう(i-Modeの論理)