isyumi_netブログ

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

ポリテックと型システム

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

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

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

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

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

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

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

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

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

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

 

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

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

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

思い切って

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

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

 

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

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

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

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

そこで

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

とか

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

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

 

 

 

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

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

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

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

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

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

 

人生の5箇年計画

やりたいことがいっぱいありすぎるので一覧を作る。

5箇年計画と書いたが、実際には何年かかるか全く見当がつかない。

 

  1. 数学検定準一級
  2. TOEIC800点
  3. レッドコーダーになる
  4. Webフロントエンドのベストプラクティスホスティングサービスを作る
  5. Dartのサーバーサイドフレームワークを作る
  6. ビジネスロジックからDBのクエリを自動生成するツールを作る
  7. 数学検定一級
  8. 応用情報技術者をとる
  9. データベーススペシャリストをとる
  10. ネットワークスペシャリストをとる
  11. セキュリティスペシャリストをとる
  12. LPIC Level3になる
  13. この辺で年収1000万になる
  14. CPUを作る
  15. OSを作る
  16. 言語を作る
  17. RDBの理論面を勉強する
  18. 型システムの理論面を勉強する
  19. 素晴らしい数式処理システムを作る
  20. 暗号論を勉強する
  21. 新しいWebの仕様を策定しバーナーズリーの後継者になる

30才までに終わるかな。

外部からSSH接続を受け付けるプロダクトのSSH設定

今、デベロッパー向けにこういうサービスあったらいいなーと思っているものがある。

その中でSSH接続を受け付けたいなと思ったから実現方法を考えた。

WordPressホスティングしてvimで記事を編集できるようなものをイメージして欲しい。

 

ルール

・1人1Docker コンテナ

・コンテナを実行するEC2インスタンスを運営側がいつでも変更できるようにする

・サーバーの状態の保持は別問題

・HTTPも別問題

・ユーザー名は全ユーザーで1意、ホストは共通

 

サインアップしたユーザーにDockerコンテナを用意し、そこにSSH接続を受け付けるようなものを作りたい。

 

せっかくなので、1Dockerに複数のユーザーを用意できるようにしたかった。それができると「管理者」「編集者」「記者」等のロールを作れる。しかし、難易度が上がりそうなので今回は諦め。

 

コンテナを実行するEC2インスタンスを運営側がいつでも変更できるようにしたい。

特定のホスト名、ポート番号で待ち受け続けないといけないと運用がきつくなる。

例えばもっと安いEC2インスタンスが発表されたらこっそりコンテナを移動したい。

また、使われてないコンテナを勝手に落としておき、次にアクセスが来たときに空いているインスタンスで立ち上げる仕組みも入れられるようにしたい。

そのため、生EC2アドレスをユーザーに渡すのではなく、SSHプロキシのアドレスを渡してそこからEC2にブリッジする。

 

インスタンスを立ち上げなおしたときにユーザーが変更したファイルの保持をどうするかは、ネットワーク設計とは別問題なので今日は気にしない。それはアプリケーション設計の話だ。

大方 /var/hoge/fuga 以外を変更しても保存は保障しませんよといっておけばいいだろう。

 

HTTPでも受け入れられるようにしたいが、そっちはそっちで話がでかすぎるので別で考える。

 

ユーザー名は全ユーザーで一意にする。

さっきも書いたが本当はホストごとに名前空間を区切って

- admin@user1.some-service.com

- author@user1.some-service.com

- editor@user1.some-service.com

- junior-staff@user1.some-service.com

という風にロールわけをできるようにしたい。

しかし、後述のライブラリがまだHostに対応していないので諦めた。

 

とりあえず

- user1@some-service.com

- user2@some-service.com

というユーザー名体系にする。

 

実現方法

これはまだ試していなくて、こうすれば大丈夫だろうと僕が今想像していることである。

 

用意するもの

・ELB

・セキュリティゾーン

・EC2を3台以上

 ・内2台はSSHプロキシ

 ・もう一台はDockerのせるインスタンス(アプリケーションインスタンスと呼ぶ)

  ユーザーが増えてきたらこのインスタンスを増やしていけば良い

・DockerImage

 

まず、ELBから2台のSSHプロキシサーバーに大してTCPロードバランスを設定

外向きのホスト名はssh.some-service.comで統一する。

 

SSHプロキシサーバー内で下記SSHプロキシツールを起動

https://github.com/tg123/sshpiper

 

アプリケーションインスタンスでDockerを起動

このとき、内向きのSSHポートは22とする。

外向きは可変にする。

docker -p 3333:22 等

 

これで準備は完了。

新しくユーザーがサインアップしたらどこかのアプリケーションインスタンスでDockerを起動する。

鍵ペアを生成し、Docker内に渡すと同時にSSHプロキシにそのインスタンスのホスト名、ポート名、鍵を設置。

ユーザーに秘密鍵を渡す。

 

これでできるんじゃないかな?

ビジネスロジックのコードからDBのクエリを自動生成することは可能か?

最近取り組もうと思っていること

ビジネスロジックのコードを解析することでこのようなことができるのではないか?

  • このコードを実行するために必要なDB上のデータの範囲を特定する
  • そこからDBへのクエリを自動生成できるのではないか

という疑問だ。

 

前提とするコード

ビジネスロジックの前段階で必要なデータを落としてくる

僕はビジネスロジック内でDBにアクセスするのではなく、その前にデータを落としてきておいて、そのデータを引数に取る事にしている。

 // こうではない

func getUserSalesSum(req Request, db DB) Response {
sales := db.QuerySalesByUser(req.UserID)
return sum(sales)
}
// こう
func onRequest(req Request, db DB) Response {
sales := db.QuerySalesByUser(req.UserID)
return getUserSalesSum(req, sales)
}
func getUserSalesSum(req Request, sales []Sale) Response {
return sum(sales)
}

 これはgetUserSalesSumがスローテストにならないようにするためだ。

また、DBへのクエリが正しく書けたかということとこのビジネスロジックが正しく書けたかということは本来別問題なのに、同時にテストしなければいけなくなる。だからビジネスロジックに入る前に必要なデータは全部落としておくべきだ。

 

ビジネスロジックでも同じフィルターを書く

前述のこのコードは悪いコードの例である。よく「バッドコードの例」として紹介される。

func getUserSalesSum(req Request, sales []Sale) Response {
return sum(sales)
}

暗黙の前提が存在することについてだ。

このコードでsalesという値はあるユーザの購買履歴だけを含む配列ということになっている。しかし、このコードにそのメッセージ性がない。このコードを読んだ人は普通「このsalesはすべてのsaleが入った配列だな」と感じると思う。

そこで、いくつかの手を打つ。例えば引数名や関数名を工夫することがある。また、Assertを書くというのもただしい選択の一つだろう。しかし、僕はビジネスロジックにもフィルターを書くのがいいと思う。

func getUserSalesSum(req Request, sales []Sale) Response {
sales = filterUserSales(sales, req.UserID)
return sum(sales)
}

 

こうしたほうがビジネスロジックの1行1行が仕様と1:1で対応するようになるのでわかりやすい。

また、前者はこの配列が特定の条件でフィルターされていることに依存していたコードだが後者は必要なデータが入っていればそれ以外のデータが入っていても正常に動くコードになる。後者のほうがキャッシュなどとの相性が良くなる。

またクエリを書くことを手抜く余地が出る。例えば国コード一覧というのがあったとする。それは高々200個くらいの値しかないはずだ。そうであれば、毎回DBに取りに行くより手元のインメモリに持って置きたいかもしれない。そういう時に特別なコードを書かなくてよくなる。

DB上のすべてのテーブルを一つのオブジェクトツリーにまとめる

このコードの問題点の一つとして使うデータの種類が増えれば引数が多くなってしまう点がある。ユーザー情報や購買情報や位置データやあれこれ……

そこで、DBのテーブルを全部一つのオブジェクトツリーに含めてしまうといいと思う。

type Table struct {
Users []User
Products []Product
Sales []Sale
}

func getUserSalesSum(req Request, table Table) Response {
sales := filterUserSales(table.Sales, req.UserID)
return sum(sales)
}

さて、「ビジネスロジックの前段階で必要なデータを落としてくる」でビジネスロジックが参照透過性を手に入れた。そして、「ビジネスロジックでも同じフィルターを書く」でテーブルが特定の条件に依存するコードから。さらに一つのオブジェクトツリーにまとめることによって、テーブル全体の関数に変わった。

HTTPリクエストハンドラとは

ここで一つの結論に到達した。

HTTPハンドラとは、HTTPリクエストとDB上のすべてのデータに対してHTTPレスポンスを返す純粋関数である。

POSTメソッドなどのこともあるので、HTTPレスポンスとDB書き込みコマンドを返す関数であるということにしておく。

自動生成

 

おさらいだが、このビジネスロジックは引数でDB上のすべてのデータを受け取った前提で書いてある。しかし、実際に必要なのはその一部分である。その部分は必ず入っていなければいけないが、それ以外の部分にデータが入っていても入っていなくても動作は変わらない。逆に混ざっているとまずいデータというのはありえない。

たとえば上記のこのコードの場合だ。

type Table struct {
Users []User
Products []Product
Sales []Sale
}

func getUserSalesSum(req Request, table Table) Response {
sales := filterUserSales(table.Sales, req.UserID)
return sum(sales)
}

func filterUser(sales []Sale, id interface{}) []Sale {
var results []Sale
for _, s := range sales {
if s.UserID == id {
results = append(results, s)
}
}
return results
}

このコードを見れば誰でもUsersとProductsにデータが入っていても入っていなくても同じ動きをすることはわかる。

また、Salesにreq.UserIDのすべてのSalesが入っていなければならないが、UserBさんやUserCさんのデータはあってもなくても同じということもわかる。

僕の問題意識は、「人間がこのコードを読めば必要なデータと不要なデータを判定できるならコンピューターに計算させることができないか?」である。

UsersとProductsが不要であるということはASTを見ていけば結構簡単にできそうだ。しかし、req.UserIDのSalesだけが必要だ、ということはどうすれば判定できるのだろうか。

今のところ、すべての「値」に対して「その値を作るのに必要な値」を挙げていき、それをツリー上にすればある程度可能かと思ったが自信がない。

 

誰か教えてくれ。

 

 

Dartのサーバーサイドフレームワークを作ろうと思う

サーバーサイドもDartで書いてきた。

ワンパターン化してきたのでフレームワーク化する。

とりあえず構想を発表する。

認証を楽にするため、HTTPサーバーはなくFirebaseサーバーとして振舞うものにする。

 

サポートする機能3つだ。

  1. Firebaseの型定義
  2. View
  3. コード生成支援

 

Firebaseの型定義

FIrebaseJSONでデータをやりとりするため、エンコーダとデコーダが必要になる。

Firebase特有の機能があるので一般的なDartJSONエンコーダーライブラリでは機能不足だ。サーバー側とクライアント側で微妙にユースケースが違うこともネックになる。

 

だから、型定義ファイルからサーバー・クライアントのコードを自動生成する。

定義ファイルの書式はDartにする。型定義ファイルのフォーマットとして、JSONは書きたくない。yamlも辛い。yamlは序盤は快調だが行が増えてくると辛くなる。

結局、型定義にどのフォーマットが適しているか。プログラム言語が一番いい。だからDartにした。これはそのDartを実行したりリフレクションで値を入れたりするものではない。DartASTをとって解釈してそれを元にコードを生成する。Dartの書式で型を定義すれば読みやすいし各自のエディタのシンタックスハイライトや補完がきく。

 

将来はマイグレーション機能もつけたいと思っている。

Firebaseを使っているとDBのようにAlter Tableが打てないとめんどくさくなる局面がある。

型定義の変更履歴から既存のデータベースを変更できるコマンドを作れるはずだ。

 

2.view

FirebaseDBViewの機能が欲しい。もしかしたらこれは本家が対応するかもしれない。して欲しい。

例えばユーザーがいろんなグループに参加できるグループチャットアプリを作っているとする。ユーザーごとに取得したい書き込みはバラバラである。これを頑張ってクエリで表現するよりも

 A チャットの書き込みを全部一箇所にまとめたデータ

 B 誰がどのグループに所属しているかまとめたデータ

 C ユーザーごとに所属するグループの書き込みをまとめたデータ

を別々に保持したい。

 

だから、AとBが変更された時にリアクティブにCを再作成したい。

どうするかというと、AとBの全データを引数にCの全データを作成する純粋関数を一個定義すると、A、Bが変更された時に毎回その関数が実行され、結果をFirebaseの所定の場所に書き込むものが欲しい。

 

いくつか最適化の方法を考えている。

DBにならってAとBをテーブル、Cをビューと呼ぶ。

第一に、テーブルの全データを毎回Firebaseから読みだすのは悪いので、テーブルのコピーをローカルファイルにおいておきたい。

第二に、作り直すたびにビューの全データをFirebaseに書き込むのは申し訳ないので、前回作成したビューをローカルに持っておき、差分をチェックしパッチを送る形にしたい。

第三に、どのテーブルが更新された時にどのビューを更新するのか、どのビューを更新するためにどのテーブルを読み出さないといけないのかを自動で判断する。ソースコードを解析することで可能だ。

第四に、実現方法がわからないので夢物語だが、ビューの1行がテーブルのどの行に依存しているかを判断できたら、毎回テーブルの全データを読み込まなくてよくなる。

 

コード生成支援

以前設計の逆流問題の話をした。

 

blog.isyumi.net

 

この最後の部分にinterfaceを自動生成していってくれればいいということを書いた。

それを含めたい。