trifle

技術メモ

Haskellの型クラス/インスタンスはIQ150を超える

に触発されて, これを Haskell でもやってみようと思った.

f:id:HelloRusk:20180930223544j:plain:w450

+アドホック多相させようという話だ.
Python には特殊メソッドを定義する関数があり, + であれば __add__() が対応している, という感じのようだ.
Haskell には型クラスとインスタンスというものがあり, 大まかに言って, 型クラスは雛形を作るもの, インスタンスは雛形を実現させるものだと思う(雑だろうか?)
+Num 型クラスのメソッドで

class Num a where
  (+) :: a -> a -> a
  (-) :: a -> a -> a
  (*) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
  {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}

と定義されている. というわけでコードを書くとこんな感じ.

module Main(main) where

data IQTest = IQTest String

instance Num IQTest where
    (+) (IQTest m) (IQTest n) = do
        let a = read m :: Int
        let b = read n :: Int
        IQTest $ show (a - b) ++ show (a + b)

ans :: IQTest -> String
ans (IQTest n) = n
    
main :: IO ()
main = do
    line <- getLine
    let m = takeWhile (\n -> n /= '+') line
    let n = tail $ dropWhile (\n -> n /= '+') line
    let a = IQTest m
    let b = IQTest n
    let c = a + b -- これがやりたかっただけ
    putStrLn $ "=" ++ ans c
    main
$ stack runghc test.hs

test.hs:5:10: warning: [-Wmissing-methods]
    • No explicit implementation for
        ‘*’, ‘abs’, ‘signum’, ‘fromInteger’, and (either ‘negate’ or ‘-’)
    • In the instance declaration for ‘Num IQTest’
  |
5 | instance Num IQTest where
  |          ^^^^^^^^^^
6+4
=210
9+2
=711
8+5
=313
5+2
=37
7+6
=113

他の演算子の定義をしていないので warning が吐かれるが動作には問題ない.
Haskell には split("+") に相当するものが標準モジュールにはなく(Data.List.Splitにはあるようだ), 頑張って標準モジュールを使おうとすると takeWhile とか dropWhile を使うことになります.
Python と比べた場合の明らかな弱点としては, Num 型クラスの定義の中で (+) :: a -> a -> a というように, 足し算の左辺と右辺と答えの型は全て同じであるように制約がかかっているので, ans :: IQTest -> String みたいなものを作って中身を取り出さないといけないことです. もっと良い方法があるかもしれない.