Open6

データ指向プログラミングが気になる

kecykecy

(後日追記:↑の翔泳社の本が扱っているデータ指向プログラミングとは別の話だった)

https://developers-jp.googleblog.com/2015/08/blog-post.html?m=1

ゲームプログラミングに寄せた話。

CPUのキャッシュメモリに載せられる量はごくわずかであること(64KBとか)を前提に、一度に処理するデータはまとめておいたほうがパフォーマンスが上がる。

例えばゲーム内のオブジェクトの座標をオブジェクトごとに管理するのではなく、全オブジェクトの座標を一つの配列にまとめておいたほうが高速になる。

kecykecy

翔泳社の本について調べる。

書評によると、

データ指向プログラミングは以下の4つの原則からなると書かれている。

  1. コード(動作)をデータから切り離す。
  2. データを汎用的なデータ構造で表す。
  3. データをイミュータブル(不変)として扱う。
  4. データスキーマをデータ表現から切り離す。

https://booklive.jp/review/list/title_id/1342731/vol_no/001

kecykecy

ここに書いている話も参考になる。

https://zenn.dev/foxtail88/scraps/07a2b835f07642

データはmap[string]T(同種マップ)、配列、map[string]any の三種類のみ。

あくまでプリミティブとコレクション、インデックスで全てを表現する。このパラダイムの根底にあるのは前述したように安全性と変更容易性のトレードオフ。

DOPでのポリモーフィズムにマルチメソッドを使うところは面白く見えた。dispatch!
javascriptの場合はarrows/multimethod

へ〜、こういうのがあるんだ

https://caderek.github.io/arrows/packages/multimethod/

kecykecy

翔泳社のサイトの立ち読みで二章のはじめまで読めた。

https://www.shoeisha.co.jp/book/preview/9784798179797

1章ではOOPでよくある変更容易性の低さについて具体例を用いて示されていた。

一貫して書かれていたのは、データとコードがカプセル化されていると、概念同士の関係性(合成、関連、使用、継承)が絡み合ってしまうこと。

本の中ではOOPで構築したソフトウェアにあとから要件が足されていくと、コードの共通化のために特定の役割を持った基底クラスがどんどん増えていき、「死のダイヤモンド継承」もはらんだ複雑な関係図になりがちって趣旨の話があった。

https://ja.m.wikipedia.org/wiki/菱形継承問題

(また、データをクラスにして扱うことを前提とした場合、シリアライズ/デシリアライズを繰り返さなくてはならない問題も示されていた。Web APIでデータをやり取りしようとすると、この問題は避けられない)

一方。DOPの考え方でデータ(データエンティティ)とコード(コードモジュール)の2つに分離すると、データ同士の関係性は合成か関係のどちらかになり、コード同士の関係は使用だけに単純化される(DOPでは継承は扱わない)。

コードモジュールはデータを第一引数にとるステートレスな関数。

データエンティティは(おそらく)イミュータブルで容易にシリアライズ可能なデータ構造。

という感じかな。

OOPのクラス図をデータとコードに分離して整理すると、上下左右に線が伸びて絡み合った図が、上下だけのツリー構造に整理されていて、常にこの通りいくのかは分からないけど、理解しやすさが格段に上がることは見て取れた。

いま自分のプロジェクトで実践しようとしていたことと同じことをしているので、かなり参考になりそう。

ということでAmazonでポチった。明日届くのでそしたら再開しよう。

kecykecy

第4章まで読み終わって、続きの章もざっと拾い読みしつつ、付録のAを読んだ。

ここまでの感想をメモ。

1. コード(動作)をデータから切り離す。

既存コードを整理する上でも汎用的に使える考え方だと思う。

最近だとクラスにいろいろデータとメソッドを詰め込んでカプセル化するやり方は推奨されなくなってきていて、「じゃあどうすればいいの?」に答えをくれる

https://zenn.dev/mizchi/articles/oop-think-modern

2. データを汎用的なデータ構造で表す。

メリットは大きいが、ここは言語特性によってはデメリットも大きく、いつでも採用すべきかは難しい。

最近のコード補完やコンパイルタイムでの型チェックが重視される風潮だと、Go風に言う map[string]T /
map[string]any / 配列だけでデータ構造を表すことになるとそういった恩恵を得られなくなってしまう。これが大きなネックになりそう。

その点、動的型付けだとこのデメリットは発生しない。PythonやRubyでアプリケーションを構築する分にはメリットが大きくなりそう。

3. データをイミュータブル(不変)として扱う

これは特に違和感がない。

が、データベースにデータを永続化するウェブアプリケーションではメモリ内でデータを変換することはあれで更新することは意外と多くない。

フロントエンド側の実装だと活きることが比較的多いかも?

少なくともデータベースに近いバックエンド処理ではImmutable.jsやImmerみたいなライブラリを使うほどではなそうに思う。

4. データスキーマをデータ表現から切り離す

「データを汎用的なデータ構造で表す」に従順に従うと、データ構造の柔軟性と引き換えにデータの整合性を保つのが難しくなる。

この問題に対するアンサーとして、JSON Schemaを使って、境界部(Web API)のインターフェイスを明示しよう & 検証しようというのが本書で言われていること。

これは確かに。JSON Schemaを使おうという発想はあまりなかった。