Haskell本で勉強しようとしたら関係ない所で詰まった話


概要

最近、関数型プログラミングの勉強にと、すごいH本こと『すごいHaskell たのしく学ぼう!』を読みながら勉強していたら、本通りに進めているつもりでも詰まる場所が数ヶ所出てきたので備忘録がてらまとめてみます。
まだ読み途中なので更新するかも。

追記(2020/2/1)
2017年に「すごいHaskellたのしく学ぼう」を読む2019年に「すごいHaskellたのしく学ぼう」を { -# OPTIONS -Wall -Werror #- } を付けて読むの両記事をコメントで教えていただきました。
System.Randomについては内容が重複しているほか、有益な内容が詰まっている記事なのでぜひそちらもご参照ください。
また、Monoidインスタンスの定義方法を追加しました。

追記(2020/2/2)
do記法内でのパターンマッチングを追加しました。

学習環境

OS: Windows 10 Pro
Haskell: 8.6.5
stack: 1.9.3
エディタ: Visual Studio Code

System.Randomがない

第9章に入ってランダム性の話題に入ったところで、サンプルソースをファイルに記入して実行しようとしたところ、下記のエラーが返ってきました。

System.Randomが見つからない、とのエラーです。
検索してみたところ、System.Randomがバンドルされなくなってたという記事を見つけました。

ググってみると,どうやらghcの7.2.1からSystem.Randomはバンドルされなくなったらしい.

という事で記事に書いてあったことに沿おうとしましたが、割とザックリ気味の説明だったためこのままじゃ分からん、と思いもう少し調べてみたら、記事の内容より手っ取り早そうな方法を見つけました。

System.Random is missing

stack install random

上記のコマンドを実行した上で、ghciを直接実行せずにstack経由でghciを起動するために、stack ghci --ghci-optionsで起動すると、

System.Randomの読み込みに成功しました。やったぜ。

ファイルの中身をプログラムに渡す記法が違う

学習環境に書いてある通り、Windowsのローカル環境にHaskellをインストールしてHaskellを実行しています。
本の記載は基本的にbash準拠なので、ファイルの中身を読み込ませてHaskellのプログラムを実行するコマンドは以下のように書かれています。

runhaskell program.hs < data.txt

プログラムの実行は基本的にVisual Studio Code上のターミナルで開いているPowershell経由だったので、このコマンドではもちろん動きません。

(基本的には拡張機能のHaskellyをインストールして、そのコマンドからターミナル上でghciを起動しています。
今回のように引数がある場合のみ、ターミナルで直接コマンドを叩いています)

なので、学習中は下記のようにコマンドを書き換えて実行しています。

cat data.txt | stack runhaskell program.hs

Monoidインスタンスの定義方法

14章「もうちょっとだけモナド」にて、差分リストというデータ構造の紹介があります。
差分リストの定義は、本では下記の通りになっています。

newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }
toDiffList :: [a] -> DiffList a
toDiffList xs = DiffList (xs++)

fromDiffList :: DiffList a -> [a]
fromDiffList (DiffList f) = f []

instance Monoid (DiffList a) where
    mempty = DiffList (\xs -> [] ++ xs)
    (DiffList f) `mappend` (DiffList g) = DiffList (\xs -> f (g xs))

この書き方でコンパイルに通すと、下記のエラーが出てきます。

• No instance for (Semigroup (DiffList a))
    arising from the superclasses of an instance declaration
• In the instance declaration for ‘Monoid (DiffList a)’b

GHCの8.4、baseパッケージの4.11以降では、それまで別パッケージだったData.Monoid、Data.Semigroupがbaseパッケージに移行されて、SemigroupがMonoidのスーパークラスになりました。
その都合で、下記の書き方でSemigroup側で中値演算子<>を定義しないとエラーが出るようになっています。
※参考:https://kazu-yamamoto.hatenablog.jp/entry/20180306/1520314185

instance Semigroup (DiffList a) where
    (DiffList f) <> (DiffList g) = DiffList (\xs -> f (g x))

instance Monoid (DiffList a) where
    mempty = DiffList (\xs -> [] ++ xs)
    -- mappend = (<>) ...Semigroupの演算子(<>)と規定で同じ定義になるため、省略可能

do記法内でのパターンマッチング

14章「もうちょっとだけモナド」にて、Stackデータ構造の実装方法が記載されています。
Stateモナドを使用した定義方法は下記のようになっています。

type Stack = [Int]

pop :: State Stack Int
pop = do
    (x:xs) <- get
    put xs
    return x

push :: Int -> State Stack ()
push a = do
    xs <- get
    put (a:xs)

上記のコードでは、pop関数の定義で下記のエラーが発生します。

• No instance for (Control.Monad.Fail.MonadFail
                     Data.Functor.Identity.Identity)
    arising from a do statement
    with the failable pattern ‘(x : xs)’
• In a stmt of a 'do' block: (x : xs) <- get
  In the expression:
    do (x : xs) <- get
       put xs
       return x
  In an equation for ‘pop’:
      pop
        = do (x : xs) <- get
             put xs
             return xbios

Haskell WikiのMigration Guideで紹介されていますが、GHCの8.6.x以降ではdo記法内でエラーが発生しうるパターンマッチを使用する場合は、case記法を使ってモナドからの値取得後にパターンマッチングを行うよう指示されています。

pop :: State Stack Int
pop = do
    xl <- get
    case xl of
        (x:xs) -> do
            put xs
            return x
        _      -> fail "Pattern matching error." -- 例外発生時にメッセージが返却される
--         _  -> error "Pattern matching error." -- メッセージとスタックトレースが返却される