リテラルが多相的であること、もしくは値コンストラクタの要らないデータ構造

本を適当に開いて読んでみるということをたまにする。


Real World Haskell(RWH)のデータ構造の章。

ghci> 3 * 2 + 4
10
ghci> prettyShow $ 3 * 2 + 4
"3 * 2 + 4"

こんな感じのものが書いてあった訳だ。
僕には何が起こっているか全然分からなくて、一体どんな言語拡張使ってるんだこの野郎と思いつつその章をざっと見たけど何も説明見つからなかったりして混乱。
結論を言えば2,3,4はよく知っている数値リテラルで、(+)も(*)もよく知ったNumのメソッドだった。僕が思っていたより色々出来たというだけで。
僕が数値リテラル見たときだけ何故かCやPHPといったあちらの言語を想定してしまっていたことが原因だった。
加えてHaskellでデータ構造を作るには、値コンストラクタ(もしくはそれ相当の関数)が必要だと思っていたためだった。
中値出来る値コンストラク演算子は知っていたけど、あれはコロンから始める必要があったはず。


大抵の言語では(3 * 2 + 4)は10に潰されてしまって、prettyShowに渡されるときにはそれ以前のデータ構造なんて保持されない。
prettyShowに10を渡して"3 * 2 + 4"が表示される訳がない。そりゃそうだ。prettyShowには何らかの構造体が渡されているはずだ。
Haskell以外の言語でそれを達成したいなら、built-inの型であるInt等をopen classなどして、演算子(+)や(*)を変更する必要があるかもしれない。そして変更してしまった後は普段の計算が出来なくなる。



Haskellの数値リテラルは多相的だ。

ghci> :type 34
34 :: Num a => a
ghci :type 2.6
2.6 :: Fractional a => a

Numは基本Integer, Int, Float, Doubleをインスタンスとしているが、
他にも追加することが出来る。そして数値リテラルを直接扱うことが可能だ。

ghci> 3 * 2 + 4
10
ghci> prettyShow $ 3 * 2 + 4
"3 * 2 + 4"

これが出来るのは、prettyShowが適用されているからだ。prettyShowの存在が、続く3, (*), 2, (+), 4の機能を変更していたのだ。


RWH手元にないからうろ覚えで似たような挙動でっち上げる。

data Op = Add | Sub | Mult
    deriving (Eq, Show)

data Math a = Atom a | Rel Op (Math a) (Math a)
    deriving (Show, Eq)

instance (Num a) => Num (Math a) where
    n + m = Rel Add n m
    n - m = Rel Sub n m
    n * m = Rel Mult n m
    fromInteger = Atom . fromInteger -- 再帰じゃないですよ
    abs = undefined -- めんどい
    signum = undefined -- めんどい

prettyShow :: (Show a) => (Math a) -> String
prettyShow (Atom a) = show a
prettyShow (Rel Add n m) = prettyShow n ++ " + " ++ prettyShow m
prettyShow (Rel Sub n m) = prettyShow n ++ " - " ++ prettyShow m
prettyShow (Rel Mult n m) = prettyShow n ++ " * " ++ prettyShow m

main = do
    print $ 3 * 2 + 4 -- => 10
    print $ prettyShow $ 3 * 2 + 4 -- => "3 * 2 + 4"

面白いですね。


しかしIsStringのエントリの後にこのエントリかくのは結構阿呆っぽいですね。
まあ気づかなかったのだからしようがない。