エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

はてなブログで LaTexをレンダリングする方法の覚書

[$ ... $] でブロックを作る(2026年7月以降)。

例1

[$ \sum_{x = 1}^n f(x) $]

 \sum_{x = 1}^n f(x)

[$ \displaystyle \sum_{x = 1}^n f(x) $]

 \displaystyle \sum_{x = 1}^n f(x)

例2

[$
\begin{array}{rcl}

\boldsymbol{A} \cdot \boldsymbol{B}

& = &

\begin{bmatrix}
| & | & | \\
\boldsymbol{a_{1}} & \boldsymbol{a_{2}} & \boldsymbol{a_{3}} \\
| & | & |
\end{bmatrix}

\cdot

\begin{bmatrix}
- & \boldsymbol{b^*_{1}} & - \\
- & \boldsymbol{b^*_{2}} & - \\
- & \boldsymbol{b^*_{3}} & -
\end{bmatrix}

\\

& = &

\boldsymbol{a_1} \boldsymbol{b^*_1} +
\boldsymbol{a_2} \boldsymbol{b^*_2} +
\boldsymbol{a_3} \boldsymbol{b^*_3}

\end{array}
$]


\begin{array}{rcl}

\boldsymbol{A} \cdot \boldsymbol{B}

& = &

\begin{bmatrix}
| & | & | \\
\boldsymbol{a_{1}} & \boldsymbol{a_{2}} & \boldsymbol{a_{3}} \\
| & | & |
\end{bmatrix}

\cdot

\begin{bmatrix}
- & \boldsymbol{b^*_{1}} & - \\
- & \boldsymbol{b^*_{2}} & - \\
- & \boldsymbol{b^*_{3}} & -
\end{bmatrix}

\\

& = &

\boldsymbol{a_1} \boldsymbol{b^*_1} +
\boldsymbol{a_2} \boldsymbol{b^*_2} +
\boldsymbol{a_3} \boldsymbol{b^*_3}

\end{array}

出典

help.hatenablog.com

staff.hatenablog.com

図形で理解するということ〜線形代数と数的霊薬〜

行列の積

 3 \times 3 の行列同士の積を考えます。


\begin{bmatrix}
a_{11} & a_{12} & a_{13} \\
a_{21} & a_{22} & a_{23} \\
a_{31} & a_{32} & a_{33}
\end{bmatrix}

\cdot

\begin{bmatrix}
b_{11} & b_{12} & b_{13} \\
b_{21} & b_{22} & b_{23} \\
b_{31} & b_{32} & b_{33}
\end{bmatrix}

定義に従い計算すると、1行1列の要素は左の項の1行目と右の項の1列目の各々の要素の積の和、1行2列の要素は左の項の1行目と右の項の2列目の各々の要素の積の和、…、3行3列の要素は左の項の3行目と右の項の3列目の各々の要素の積の和、となります。


\begin{bmatrix}
a_{11} b_{11} + a_{12} b_{21} + a_{13} b_{31} & a_{11} b_{12} + a_{12} b_{22} + a_{13} b_{32} & a_{11} b_{13} + a_{12} b_{23} + a_{13} b_{33} \\
a_{21} b_{11} + a_{22} b_{21} + a_{23} b_{31} & a_{21} b_{12} + a_{22} b_{22} + a_{23} b_{32} & a_{21} b_{13} + a_{22} b_{23} + a_{23} b_{33} \\
a_{31} b_{11} + a_{32} b_{21} + a_{33} b_{31} & a_{31} b_{12} + a_{32} b_{22} + a_{33} b_{32} & a_{31} b_{13} + a_{32} b_{23} + a_{33} b_{33}
\end{bmatrix}

この行列の和の部分を行列の外に出すと、次のように表現することができます。


\begin{bmatrix}
a_{11} b_{11} & a_{11} b_{12} & a_{11} b_{13} \\
a_{21} b_{11} & a_{21} b_{12} & a_{21} b_{13} \\
a_{31} b_{11} & a_{31} b_{12} & a_{31} b_{13} 
\end{bmatrix}

+

\begin{bmatrix}
a_{12} b_{21} & a_{12} b_{22} & a_{12} b_{23} \\
a_{22} b_{21} & a_{22} b_{22} & a_{22} b_{23} \\
a_{32} b_{21} & a_{32} b_{22} & a_{32} b_{23}
\end{bmatrix}

+

\begin{bmatrix}
a_{13} b_{31} & a_{13} b_{32} & a_{13} b_{33} \\
a_{23} b_{31} & a_{23} b_{32} & a_{23} b_{33} \\
a_{33} b_{31} & a_{33} b_{32} & a_{33} b_{33}
\end{bmatrix}

これは、左の行列を列ベクトルに、右の行列を行ベクトルに分けたとき、1 番目の列ベクトルと行ベクトル、 2 番目の列ベクトルと行ベクトル、3 番目の列ベクトルと行ベクトルのそれぞれの積を足し合わせたものと同じになります。


\begin{array}{rcl}

\begin{bmatrix}
| & | & | \\
\boldsymbol{a_{1}} & \boldsymbol{a_{2}} & \boldsymbol{a_{3}} \\
| & | & |
\end{bmatrix}

\cdot

\begin{bmatrix}
- & \boldsymbol{b^*_{1}} & - \\
- & \boldsymbol{b^*_{2}} & - \\
- & \boldsymbol{b^*_{3}} & -
\end{bmatrix}

& = &
\boldsymbol{a_1} \boldsymbol{b^*_1} +
\boldsymbol{a_2} \boldsymbol{b^*_2} +
\boldsymbol{a_3} \boldsymbol{b^*_3}

\end{array}

これを計算で確かめます。

Nx - Numerical Elixir

Elixir には Numerical Elixir - Nx というプロジェクト / ライブラリがあります。

github.com

nx.hexdocs.pm

機械学習などで利用されることを想定されていますが、ここでは純粋に行列計算に利用します。

まず IEx を起動します。

$ iex
Erlang/OTP 29 [erts-17.0.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Interactive Elixir (1.20.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

IEx が起動したら、環境に Nx をインストールします。

iex> Mix.install([:nx])

以下、プロンプトの iex> は省略します。

ここでは Nx モジュールの関数をモジュール名の装飾なしに利用したいので、すべて import します。

import Nx

行列を定義します。

a = ~MAT[
1 2 3
4 5 6
7 8 9
]
#Nx.Tensor<
  s32[3][3]
  [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
  ]
>

ここで ~MATNx.Tensor の値を作成するための構文糖衣です。 あとで出てくる ~VEC も同様です。

行列をもう一つ定義します。

b = ~MAT[
1 4 7
2 5 8
3 6 9
]
#Nx.Tensor<
  s32[3][3]
  [
    [1, 4, 7],
    [2, 5, 8],
    [3, 6, 9]
  ]
>

行列から列ベクトルを取り出します。

take(a, ~VEC[0], axis: 1)
#Nx.Tensor<
  s32[3][1]
  [
    [1],
    [4],
    [7]
  ]
>

axis には軸を指定します。 表示された Nx.Tensor の値からわかる通り、値は [[1,2,3],[4,5,6],[7,8,9]] という並びで格納されています。 Nx では添え字は 0 から始まるので、縦方向に値を拾いたい場合は axis: 1 を指定します。

同様に行ベクトルを取り出します。 axis オプションのデフォルト値は 0 のため指定を省略できますが、ここでは表記を合わせることにします。

take(b, ~VEC[0], axis: 0)
#Nx.Tensor<
  s32[1][3]
  [
    [1, 4, 7]
  ]
>

なお第 2 引数にはスカラも利用できますが、ここでスカラを与えると期待する形の値が得られないため、それぞれベクトル( ~VEC[0] )で指定しています。

take(b, 0, axis: 0)
#Nx.Tensor<
  s32[3]
  [1, 4, 7]
>

行列の積の値を確認します。

dot(a, b)
#Nx.Tensor<
  s32[3][3]
  [
    [14, 32, 50],
    [32, 77, 122],
    [50, 122, 194]
  ]
>

次に、各々の列ベクトルと行ベクトルの積の和の値を確認します。

a1b1 = dot(take(a, ~VEC[0], axis: 1), take(b, ~VEC[0], axis: 0))
a2b2 = dot(take(a, ~VEC[1], axis: 1), take(b, ~VEC[1], axis: 0))
a3b3 = dot(take(a, ~VEC[2], axis: 1), take(b, ~VEC[2], axis: 0))

a1b1 |> add(a2b2) |> add(a3b3)
#Nx.Tensor<
  s32[3][3]
  [
    [14, 32, 50],
    [32, 77, 122],
    [50, 122, 194]
  ]
>

一致することが確認できました。

Julia - The Julia Programming Language

同じ内容を Julia でも計算させてみます。

julialang.org

$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.7.3 (2022-05-06)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

以下、プロンプトの julia> 以降が入力した式で、その下に表示される値は Julia の出力です。

julia> A = [1 2 3; 4 5 6; 7 8 9]
3×3 Matrix{Int64}:
 1  2  3
 4  5  6
 7  8  9

julia> B = [1 4 7; 2 5 8; 3 6 9]
3×3 Matrix{Int64}:
 1  4  7
 2  5  8
 3  6  9
julia> A * B
3×3 Matrix{Int64}:
 14   32   50
 32   77  122
 50  122  194

Julia では、ベクトルの後に [行番号,列番号] と列番号と列番号を指定することで、指定した位置の値を取得できます。 値の代わりに : を指定すると列全体または行全体を取得できます。

なお Julia では添え字は 1 始まりなので、1 列目や 1 行目には 1 を指定します。

ただし、単純に列番号を指定すると扱いたい形式にならないため、

julia> A[1,:]
3-element Vector{Int64}:
 1
 2
 3

[[1],:] という形で指定します。

julia> A[[1],:]
1×3 Matrix{Int64}:
 1  2  3
julia> A[:,1]
3-element Vector{Int64}:
 1
 4
 7

取り出した列と行は、同じように積をとることができます。

julia> A[:,1] * B[[1],:]
3×3 Matrix{Int64}:
 1   4   7
 4  16  28
 7  28  49

各々の列ベクトルと行ベクトルの積の和の値を確認します。

julia> A[:,1] * B[[1],:] + A[:,2] * B[[2],:] + A[:,3] * B[[3],:]
3×3 Matrix{Int64}:
 14   32   50
 32   77  122
 50  122  194

一致することが確認できました。

図解線形代数

なお、ちゃんとした話はこちらにありますので、こちらを読んでいただけたらと。

anagileway.com

オブジェクトの手続きと手続きのオブジェクト

構造体のようなオブジェクトを扱う

オブジェクト指向言語使っていても、ただの値の塊としか利用されないケースに遭遇します。

class Foo
  def initialize(attr_a, attr_b, attr_c)
    @attr_a = attr_a
    @attr_b = attr_b
    @attr_c = attr_c
  end

  def do_something(info)
    info.attr_1 = info.attr_1 * @attr_a
    info.attr_2 = info.attr_2 * @attr_b
    info.attr_3 = info.attr_3 * @attr_c
  end
end

class Info
  attr_accessor :attr_1, :attr_2, :attr_3

  def initialize(attr_1, attr_2, attr_3)
    @attr_1 = attr_1
    @attr_2 = attr_2
    @attr_3 = attr_3
  end
end

foo = Foo.new(2, 3, 5)
info = Info.new(7, 11, 13)

foo.do_something(info)
pp info
#=> #<Info:0x0000000120c09c90 @attr_1=14, @attr_2=33, @attr_3=65>

ベタ書きがよくないからと手続きを分離しても、関数に分離するところまで止まってしまうことがあります。

class Foo
  # ...

  def do_something(info)
    do_something1(info)
    do_something2(info)
    do_something3(info)
  end

  private

  def do_something1(info)
    info.attr_1 = info.attr_1 * @attr_a
  end

  def do_something2(info)
    info.attr_2 = info.attr_2 * @attr_b
  end

  def do_something3(info)
    info.attr_3 = info.attr_3 * @attr_c
  end
end

構造体のようなオブジェクトを能動的なオブジェクトで包む

構造体のようなオブジェクトを定義しているのが自分たちであれば #do_something1Info へ移動して、info.do_something1(@attr_a) としたいところですが、Info を直接変更することができないならば、一層包んでやる必要があります。

たとえば Info を包む Attributes を用意して、Attributes#do_something1 を移動してみます。

class Foo
  def initialize(attr_a, attr_b, attr_c)
    @attr_a = attr_a
    @attr_b = attr_b
    @attr_c = attr_c
  end

  def do_something(attributes)
    attributes.do_something1(@attr_a)
    attributes.do_something2(@attr_b)
    attributes.do_something3(@attr_c)
  end
end

class Info
  attr_accessor :attr_1, :attr_2, :attr_3

  def initialize(attr_1, attr_2, attr_3)
    @attr_1 = attr_1
    @attr_2 = attr_2
    @attr_3 = attr_3
  end
end

class Attributes
  def initialize(info)
    @info = info
  end

  def do_something1(attr)
    @info.attr_1 = @info.attr_1 * attr
  end

  def do_something2(attr)
    @info.attr_2 = @info.attr_2 * attr
  end

  def do_something3(attr)
    @info.attr_3 = @info.attr_3 * attr
  end
end

foo = Foo.new(2, 3, 5)
info = Info.new(7, 11, 13)
attributes = Attributes.new(info)

foo.do_something(attributes)

欲しい手続きを持ったオブジェクトで包む方法はよくやるのですが、今回のように操作する間だけ包み、操作後は元のオブジェクトを利用するという使い方をするばあいは、いつオブジェクトが変更されたのか隠されてしまうという難点があります。

手続きをオブジェクトにして分離する

こういったときには手続きを分離してしまうのがよいのかも知れません。 手続きを担うオブジェクトを導入します。

class Foo
  attr_reader :attr_a, :attr_b, :attr_c

  def initialize(attr_a, attr_b, attr_c)
    @attr_a = attr_a
    @attr_b = attr_b
    @attr_c = attr_c
  end

  def do_something(processor, data)
    processor.do_something1(self, data)
    processor.do_something2(self, data)
    processor.do_something3(self, data)
  end
end

class Info
  attr_accessor :attr_1, :attr_2, :attr_3

  def initialize(attr_1, attr_2, attr_3)
    @attr_1 = attr_1
    @attr_2 = attr_2
    @attr_3 = attr_3
  end
end

class Processor
  def do_something1(foo, info)
    info.attr_1 = info.attr_1 * foo.attr_a
  end

  def do_something2(foo, info)
    info.attr_2 = info.attr_2 * foo.attr_b
  end

  def do_something3(foo, info)
    info.attr_3 = info.attr_3 * foo.attr_c
  end
end

foo = Foo.new(2, 3, 5)
info = Info.new(7, 11, 13)
processor = Processor.new

foo.do_something(processor, info)

Foo#do_something は必要な操作の並びを指揮し、個々の操作の詳細は専用のオブジェクトに任せる。

手続きの集まりのオブジェクト

ここで、それぞれの手続きは固定の操作だからとハードコードしてしまうと、二つ目に書いた「関数に分離」しただけの硬直したコードに戻ってしまいます。

結局のところこのオブジェクトは二つのオブジェクトの関係を表現しているのではないかと思います。 手続きしか持たないために対応する実態がないようにも感じられますが、同じオブジェクトの間にも複数の関係があると考えれば、それらが異なるオブジェクトで表現されることも自然に感じられるようになると思います。

foo = Foo.new(2, 3, 5)
info = Info.new(7, 11, 13)
processor = AnotherProcessor.new

foo.do_something(processor, info)

状態を持たない ≠ スタティックな存在

「状態を持たない」と説明されたときに、状態を持たない = インスタンス変数を持たない = インスタンスは不要 と捉えて、すべてのメソッドをクラスメソッド、つまり関数として定義するパタンを何回か目にしました。

おそらくこれは誤解で、上で示したとおり状態がなくてもオブジェクトとして捉えることは可能です。 そもそも、状態はなくても個々のオブジェクトに固有のパラメータを持たせることで、操作の際にそれぞれ個性の違うオブジェクトとして振る舞えるようにすることも考えられるわけで、スタティックな存在とイメージするのはいささか短絡的かなという印象です。

オブジェクト指向は、十分に普及した。 そう思っていましたが、意外とそうでなかったのか。 あるいは、オブジェクト指向を踏襲する必要性がなくなり、このような技法も不要になりつつあるのか。 もう少し観察してみたいところです。

いつか読むはずっと読まない:石垣の中の県庁

福井県の県庁は、福井城址に建てられています。

福井城とは? (PDF)

つまり、石垣の中に県庁があります。

「石垣の中の県庁」と「黒曜石の中の不死鳥」とは似てそうと思ったけれど、そんなに似てなかった。

プログラミングをプログラミングする

コードを出力するコードを書く

一発で目的のコードを出力するためのコードを書く、というのが昔のジョークにあった気がしますが。

最近は。 仕事では。 「『目的のプログラミングを AI に生成させるプロンプト』を AI に生成させるプロンプト」を AI で生成しています。

「自分」の入る余地はどこ? という気持ちにならなくもないですが、見ているとやはりプロンプトを書く人によって個性が出るようです。

思いつく解法がひとつのとき

長く仕事でプログラミングをしていると、開発する内容に対して、それを解決する方法がそれほど迷わず思い浮かぶようになります。 完成形で出てくるわけではないですが、どの種類の解法を組み合わせればどうにかなりそうか、当たりがつくようになります。

そんなときでも、ひとつしか方法が思いつかないときは、自分を疑ってかかっています。

よほど単純な問題でもない限り、解法がひとつなどということはありません。 自分がそんなに簡単に最適な解法ひとつを選べるはずがない、という自分に対する疑念がいつもつきまとっています。

ただ一方で、最初に思いついた方法に対しても、これで間違いないだろうという感覚があり、我ながらめんどくさい精神構造だと呆れたりもしています。

クリティカルシンキング

この精神構造は今の仕事でも発揮されていて。

自分が指示を出して出力させた分析結果を信じきれず、与えた情報に偏りがないか、指示にバイアスがかかっていないか、いつも気にしています。

なので生成させるものの仕上がりが近づくと、必ず「偏ってないか」「バイアスはないか」「指示していないことは何か」「出力結果に反する情報を見落としていないか」と、問いただすのは否定的なことばかり。

無限に続けられるわけでないので、及第点と判断したところで仕上がりとするのですが、「完成には程遠いな」という感覚はずっとついて回っています。

プログラミング・プログラミング

今のような状況になってしばらく経ちますが、間接的に仕事をしているようなこの感覚は、当面消えそうにありません。

AI を使わずに仕事をするという選択はすでになく、使うのが当然という理解でいるわけですが、それとこれとはまた別の話。

自分でプログラミングするかどうかという問いも、輪郭がぼんやりしていてあまり意味をなさなくなっているように思います。 が、とはいえ。 出力される何かは自分が出力できるもの以上のものでないと、納得できない気持ちでもいます。

Markdown のテキストに、自分の思考を写し取りたいのかも。

ライフボックス

自分を写し取るというと、昔々に読んだ本の中にあった「ライフボックス」を今でも思い出します。

邦訳は現在も入手できますが、原著であれば著者のサイトで読むことができます。

www.rudyrucker.com

その中の一節。

I would hazard a guess that, in the future, recording one’s own life fractal will be a very popular activity among retired people. Already it is not uncommon for an older person to write down the “story of my life,” but how much easier and how much more complete it would be if one could purchase a small computer, called, let us say, a life box. You could tell random reminiscences to your life box and it would store them all and set up a system of cross-referencing. Occasionally the life box might ask you a question to clear up certain confusing links. After a few months it would have you down in some detail, and you could pass it on to your children so that your grandchildren would be able to hear for themselves “what Grampa was like.” Who wouldn’t want to immortalize himself this way?

(Google 翻訳)

将来、退職後の人々の間で、自分自身の「人生のフラクタル」を記録することが非常に人気のある活動になるのではないかと、私は推測しています。高齢者が「私の人生の物語」を書き残すことは今でも珍しくありませんが、もし「ライフ・ボックス」とでも呼ぶべき小型のコンピュータを購入できれば、その作業はずっと簡単になり、内容もはるかに充実したものになるでしょう。思いつくままに昔の思い出をそのライフ・ボックスに語りかければ、装置はそれらすべてを記録し、相互に関連付けるシステムを構築してくれます。時には、曖昧なつながりを明確にするために、ライフ・ボックスの方から質問をしてくることもあるかもしれません。数ヶ月もすれば、あなたの人生が詳細に記録され、それを子供たちに引き継ぐことで、孫たちが「おじいちゃんはどんな人だったのか」を自分の耳で確かめられるようになるのです。このようにして自分自身を不滅のものにしたいと思わない人がいるでしょうか。

ふと、しばらくぶりに著者のサイトを開いてみると、二年半前にそのことを大きく取り上げたブログ記事がありました。

www.rudyrucker.com

(Google 翻訳)

ルーディは「ソフトウェアによる不老不死」を予言していた

コンピュータ上でプロセスを走らせることで人間をシミュレートすることに、私たちは着実に近づいています。世間では、「ソフトウェアによる不老不死」が実現するかもしれないという夢が語られています。そして私は、40年前にそれを予言していたのです。

ルーディ・ラッカーの「ソフトウェア」を読んで三十数年になりますが、いまだにこの著作の影響を受けている気がします。

いつか読むはずっとよまない:柔らかな死

もっと直接的にこのことを中心に据えた「柔らかな死(Soft Death)」という短編もあります。

このご時世、このことに触れた記事がないだろうか、と検索してみた結果…

blog.emattsan.org

…十五年前の自分の記事がヒットしました。

そのオブジェクトは何かと考えるということ

オブジェクト指向プログラミングでも、以前から存在している配列などのデータ構造を使います。 ただ、使われ方に違和感を感じることがあります。

その違和感について少し考えてみます。

オブジェクトとオブジェクトの集まり

あるオブジェクトに対して、その集まりを扱うことは多々あります。

例えば Ruby で次のようなオブジェクトがあったばあい、

class Item
  def initialize(foo:, bar:, baz:)
    @foo = foo
    @bar = bar
    @baz = baz
  end

  def foo = @foo
  def bar = @bar
  def baz = @baz
end

その複数のオブジェクトの各々属性の値の合計を取得するなら Array#sum を使って次のように書くことができます。

items = [
  Item.new(foo: 1, bar: 2, baz: 3),
  Item.new(foo: 4, bar: 5, baz: 6),
  Item.new(foo: 7, bar: 8, baz: 9),
]

sum_of_foos = items.sum(&:foo)
sum_of_bars = items.sum(&:bar)
sum_of_bazes = items.sum(&:baz)

合計であれば Array#sum 一つで解決するので、それほど違和感を感じることはないかもしれません。

次に平均値を計算することを考えてみます。

average_of_foos = items.sum(&:foo) / items.size
average_of_bars = items.sum(&:bar) / items.size
average_of_bazes = items.sum(&:baz) / items.size

ここでやりたいことは平均値を取得することです。 しかしこう書くと、平均値を算出する計算の詳細が見えてしまいます。 同じようなコードが繰り返されるのも気になります。

繰り返す部分を関数として分離してみます。

def average(items, key) = items.sum(&key) / items.size

average_of_foos = average(items, :foo)
average_of_bars = average(items, :bar)
average_of_bazes = average(items, :baz)

詳細は隠され、繰り返しは消えました。

そうではあるのですが。

気掛かり

関数に分離したところまでしか書き換えが進まない。 ここで止めてしまう。 これがここ最近の気掛かり。

この手のコードを目にすると「averageitems に対する操作なら、items. average としたほうが適切ではないだろうか?」と考えてしまいます。

例に挙げた合計や平均を取得する操作は、 「Item に対する操作」でなくて 「『Item の集まり』に対する操作」です。 個々の Item オブジェクトに注目しているわけでなく、それらを集めた「Item の集まり」オブジェクトに対する操作であるなら、コードもそのように書くのが妥当に感じられます。

集まりを扱うクラス

Item オブジェクトの集まりを扱う Item::Collection を考えてみます。

また誤用を防ぐため、扱う属性を引数で指定する代わりに専用のメソッドを用意することにします。

class Item
  class Collection
    def initialize(items)
      @items = items
    end

    def sum_of_foos = sum(:foo)
    def sum_of_bars = sum(:bar)
    def sum_of_bazes = sum(:baz)

    def average_of_foos = average(:foo)
    def average_of_bars = average(:bar)
    def average_of_bazes = average(:baz)

    private

    def sum(key) = @items.sum(&key)
    def average(key) = sum(key) / @items.size
  end
end

items は、「Item オブジェクトの配列」から、「Item の集まりオブジェクト」へ変更します。

items = Item::Collection.new([
  Item.new(foo: 1, bar: 2, baz: 3),
  Item.new(foo: 4, bar: 5, baz: 6),
  Item.new(foo: 7, bar: 8, baz: 9),
])

値の取得は items オブジェクトへ問い合わせることで実現します。

items.sum_of_foos
items.sum_of_bars
items.sum_of_bazes

items.average_of_foos
items.average_of_bars
items.average_of_bazes

基本データ型への執着

Martin Fowler のリファクタリング本にまとめられた「コードの不吉な臭い」の中に、「基本データ型への執着 (Primitive Obsession)」があります。

ここで「基本データ型」とは、プログラミング言語が提供する「整数、浮動小数点数、文字列など」です。

それに続けて「興味深いことに、多くのプログラマは、対象としているドメインに役立つ、貨幣、座標、範囲などの基本的な型を導入するのを嫌がる傾向があります」と綴られています。

今回のケースではどうでしょうか。

Ruby ではすべての型が基底を同じにするクラスで定義されているので、C/C++ や Java などを念頭に置いた「基本データ型」とは少し違うかもしれませんが、Array クラスは Ruby を構成する基本的な要素なので基本データ型と見てよさそうに思います。

なぜ Array クラスがそのまま使われるのかというと、一つは Ruby の Array クラスが多機能ということ。 一つか二つのメソッドを繋げて書けば大抵のことはできてしまうと思います。 簡単に実現できるので、分離したり隠したりする必要が感じられないのかもしれません。

別の視点として、オブジェクトの集まりを操作対象と認識していない、というのがあるのではと考えています。 「あるオブジェクトの集まり」を操作するばあい、「『あるオブジェクトの集まり』の操作」でなく「『あるオブジェクトの操作』の集まり」と認識されている可能性です。

「あるオブジェクト」がオブジェクトであるように、「あるオブジェクトの集まり」もまたオブジェクトであるわけですが、今回のケースではそれを見つけられていないために起こっていることなのかもしれません。

いつか読むはずっと読まない:リファクタリング

なんとなくわかったつもりになっていたのに原典をあたってみたらまるで違っていた、ということはよくあります。 悩んだり迷ったりしたときばかりでなく、自信のあるばあいでもときには原典に立ち返ってみるのがよいのかもしれません。

bliki-ja.github.io

bliki-ja.github.io

Koka を手探りする

新しいパラダイムの理解を開拓すべく、Koka に手を出しています。

koka-lang.github.io

Koka の本丸は、その名(Koka = "効果")が示す通り Effect system なのですが、基本の構文をいじるだけで、そこまでたどり着けない始末。

en.wikipedia.org

一方で、これまでの知識が手掛かり足掛かりとなって、少ない情報であっても前に進むことができます。

例えば、struct 、いわゆる構造体が Koka にもあります。

struct person
  age : int
  name : string

これは糖衣構文で実態は次のコードと同じです。

type person
  Person
    age : int
    name : string

こうなると、むしろ逆に「 Person がコンストラクタ」とわかり、勘が利くようになります。

このコードを person.kk というファイルに保存して、使ってみます。

まずは REPL を起動。

$ koka
 _         _ 
| |       | |
| | _ ___ | | _ __ _   welcome to the koka interactive compiler
| |/ / _ \| |/ / _' |  version 3.2.3, Mar 18 2026, libc arm64 (clang)
|   ( (_) |   ( (_| |  output dir: .koka/v3.2.3/clang-debug-69e713
|_|\_\___/|_|\_\__,_|  type :? for help, and :q to quit

:l コマンドでファイルを読み込みます。

> :l person
parse   : .../person.kk
check   : person

コンストラクタ Person で値を作り name で文字列の値を取り出します。

> Person(20, "Alice").name
linking : person/@main
created : .koka/v3.2.3/clang-debug-69e713/person__main

Alice

(実行するごとに linking, created が表示されますが、以降は省略します)

ここでちょっと不自然なことをしました。 実は person をコンソールへ出力する手段がないためこのままではエラーになってしまいます。

> Person(20, "Alice") 
  ^
/@virtual/person/@main.kk(1, 3): type error: types do not match
  context      :   (Person(20, "Alice") ).println
  term         :   (Person(20, "Alice") )
  inferred type: person/person
  expected type: string

エラーメッセージを見ると println が定義されていればよさそうな気配です。

リンクをたどってライブラリのドキュメントへ行くと、println には 2 種類あることがわかります。

koka-lang.github.io

一方は引数の型が string なのでおそらく使えません。

  • fun string/println( s : string ) : console ()

もう一方は引数の型がパラメータになっています。 おそらくこっちを多重定義すればいけるはず。

  • fun default/show/print( x : a, ?show : (a) -> <console|e> string ) : <console|e> ()

person.kk に次の定義を追加します。 文字列の連結がわかってないので、ただ順番に出力しました。

fun default/show/println(p : person)
  print(p.name)
  print("(")
  print(p.age)
  println(")")

ここで print(p.age)print を使って数値を出力しています。 REPL でも確認できますが、これもまた多重定義されているのだと思います。

> Person(20, "Alice").age

20

ファイルを読み込み直し、実行です。

> Person(20, "Alice") 

Alice(20)

明示的に関数を適用できることもできます。

> println(Person(20, "Alice"))

Alice(20)

よさそうです。

ところで。 エラーメッセージには context : (Person(20, "Alice") ).println と出力されていました。

これは dot selection と呼ばれる糖衣構文の模様。

s.encode(3).count.println は、すなわち println(count(encode(s,3)))) と同じ。

やってみます。

> Person(20, "Alice").println

Alice(20)

ドットをパイプに置き換えると、Elixir の構文との類似も見えてきます。

これもちょっとやってみます。

defmodule Person do
  defstruct [:age, :name]

  defimpl String.Chars do
    def to_string(person) do
      "#{person.name}(#{person.age})"
    end
  end
end

person.ex という名前でファイルに保存して iex を起動します。

iex(1)> c "person.ex"
[Person, String.Chars.Person]

iex(2)> %Person{age: 20, name: "Alice"} |> IO.puts()
Alice(20)

背景は異なるかもしれませんが、意図するものは近いので、これで覚えることを減らせます。

…思い返してみると、新しい言語を覚えるときはいつもこんなことをやっている気がします。 だからこそ、勘が利かなくなる領域に触れたときに、これはいったい何なのかと好奇心が立ち上がってくるのだと思います。

Koka に触れて数日、まだそこまで進めていないのが、もどかしくもあり楽しみでもあり。

ドメインモデルが貧血になる

貧血というと。 個人的には、持病の影響で慢性的に貧血気味なんですが、ソフトウェア的愛情的にはそのような貧血ではなく。

ドメイン駆動設計を標榜するわりには、やればやるほどモデルからドメインの知識がはがれて痩せていくのを目の当たりにして、何がそうさせるのか不思議な思いでいる今日この頃。

そのような状況は「ドメインモデル貧血症 (Anemic Domain Model)」という名前で呼ばれています。

bliki-ja.github.io martinfowler.com

名前がつくくらいなので頻発する症状なのでしょう。 「実践ドメイン駆動設計」にも繰り返し登場します。

しかし奇妙なことに、ドメインモデル貧血症をこの業界のあちこちで見かける。 問題は、ほとんどの開発者がそれを当然であるかのように見ているということだ。 実際にシステムで使ったときに深刻な状況になるということに、気づきさえしない。 大問題だ。

Vaughn Vernon 「実践ドメイン駆動設計」

サービス層が云々…といった類の話はあちこちでされていますし、そこまで語れるほどの技量もないので、それらは他に譲るとして。

近くで見ていて沸いてきたのが「オブジェクト指向って自分が思っているほど浸透していないの?」という疑問。 不思議なくらいに新しいクラスが定義されていないし定義されない。

オブジェクト指向にもとづいたフレームワークを利用しているので、フレームワークが提供するクラスのサブクラスは定義されているのですが、中の処理を定義したそのサブクラスに全部書いてしまう。 たとえば二つひと組で意味を持つ値があったとしても、クラスの中でいつも二つの値としてベタ書きされ、そのクラスが値の操作を受け持つ。

面倒なことを嫌う性格なので、二つの値が常に一緒に扱われることとか、その値を操作をその値自身でなくどこかに頼まなければならない状況は、いろいろと消耗させられます。

疑問というよりも不満なのかもしれません。

そんなこんなで最近はちょっと仕事疲れです。

いつか読むはずっと読まない:知能よりも

個人的には。 人工知能がどれだけ賢くなるのだろうということよりも、意識も生まれるのだろうか、人工知能の中に意識も再現できるんだろうか、そんなことばかりが気になります。