😺

型ポエム1

2024/02/07に公開

概要

型についてのポエムです。型とは何かを改めて考え、疑い、味わうものです。Haskellにてintやfloatなどの一般的な型以上に厳密に型を扱える例も示します。
記事作成:bunnyhopper_isolated

はじめに

私はC言語からプログラミングに触れ、これが無いと何も出来ないからとりあえず使えと言われ型を扱ってきました。整数はint、浮動少数はfloatといえばなんとなく分かりますが、何かコンピュータの都合を押し付けられている感が否めませんでした。それまでの人生で型的な概念が必要となったことがなかったからです。駄菓子屋で買い物する時、家庭科の授業で醤油を図る時、型を意識したでしょうか?その違和感を放置してこれまで仕事してきて、比較的型を意識しないで良いpythonで仕事をすることになったのですが、今度は型が無いことで型に関するエラーに苦しめられることになっており、結局pythonでも型を書いています。

引数に型を指定しない例。普通に動きます。

def do_task_1(config, debug):
    if debug:
        print("debug mode")
    print(config["key1"])

型を書いた例。上と同じ挙動となります。

def do_task_2(config: Dict[str,str], debug: bool): # do_task_1 と同じ定義
    if debug:
        print("debug mode")
    print(config["key1"])

この苦しさは多分今後も消えないことでしょう。しかし苦しむ理由はあるんだと納得はしたい、、、

現実世界に置き換えてみる

まず型とは何でしょうか。計算機工学ではデータの種類と定義されています。
つまり関数の引数の型を設定することはデータの種類に制限を設けるということです。
現実の問題で考えるためにデータは変数であるとしましょう。
変数の種類に制限を設けている例はどれくらいあるでしょうか。まず思い浮かぶのは単位だと思います。

型は単位?

電気回路におけるI[Ampare]=V[Voltage]/R[Ohm]という式があります。

トランジスタを使用する回路設計などで多用されます、この式が機能しているのはVにキチンと単位通りの数値をVとRに代入している場合のみです。
即ちこの式は利用者に単位を指定することで変数の種類を強いています。
これはプログラミングの型と同等のことを行っていると考えられます。

型は食材?

では単位を指定すればよいのでしょうか。料理において砂糖と塩を間違えるということがあると思います。これらは一般にグラム(g)で表記されていますが、料理のレシピに置いては砂糖100gなどのように、食材を指定することにより特定の料理を作るためのレシピとしての性質を満たせるようになっています。これも型と同等のことを行っているとみなせます。

型は数以外の要素そのもの?

上記の例から考察するに、現実における型らしきものは数値に対して性質を付加するものであり、単純な数値に種類を負荷するものであるとみなせます。
2.1ボルトは2.1オームとは、牛乳100mlは醤油100mlとは厳密に違うことを型らしきものは伝えています。

実はプログラミングの型は弱い?

型と現実の型らしきものとのアナロジーを見たところで、型に対しある程度存在意義が見えてきたかと思います。現実世界も実は型らしきものを沢山使って居るのだからプログラミング言語もそれを使ってなにか悪いのかということです。
さて、一度プログラミングの世界に戻ってみましょう。現実の型らしきものが提供する強制力が型にあるでしょうか。そう考えるとintやfloatやstringが非常に弱々しく思えてくるかと思います。ボルトとオーム、牛乳と醤油の量を引数とする関数を考えた場合にfloatはそれらの種類の混同を防ぐことができるでしょうか?
そうならないようにpoints_voltageなどと命名の工夫をすることで少なくとも私はバグを防いできました。しかしこれは人間の脳に頼り過ぎています。別な方法は無いでしょうか。

強い型付け

Haskellにおいてはnewtypeという基礎となる型から新しい型を作る記述があります。これを用いるとなんと電圧型、抵抗型、牛乳型、醤油型などが作れてしまいます。
以下ではドル型と円型を作成し、円型のみ処理対象とする関数を書いています。この関数にドル型を渡すと、コンパイル時にエラーとなります。

newtype Yen = Yen Int
newtype Doller = Doller Int

showYen :: Yen -> String
showYen (Yen v) = (show v) ++ " yen"

main = do
  print "hello"
  let ay = Yen 100
  let bd = Doller 100
  print $ showYen ay  -- "100 yen"
  print $ showYen bd  -- error

もし単純にfloat型で円を意図とする変数とドルを意図とする変数を使った場合、この間違いはランタイムエラーとなり苦しむことになるでしょう。

やり過ぎでは?

プログラミングにおける多くの課題は上記の強い型が求められる場面は少ないでしょう。強い型に求められることが自明である場合が多いからです。しかし何を持って自明とするかです。ゲームのミドルウェアを作成するときにRadianで統一していたものが途中からDegreeを使うライブラリの参照が必要となった時は有効かも知れません。
私個人のエピソードとしては、誰かが作ったDB検索する関数がミスヒット時はからのリストを返す仕様だったのに別バージョンでNullを返すようになってランタイムエラーとなったことがあります。
これは上記の様に型に頼れる言語と構造であれば動かす前に察知出来たわけです。
HaskellにはListMaybe Listの違いの指定により察知できます。

まとめ

型を現実問題と比較し納得し、プログラミングの型を省みることをやってきました。明日からのプログラミングライフに幸あらんことをと言いたいですが、この知識が生きる場面は少ないでしょうね、、、

Discussion