概要
最近、関数型プログラミングの勉強にと、すごい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はバンドルされなくなったらしい.
という事で記事に書いてあったことに沿おうとしましたが、割とザックリ気味の説明だったためこのままじゃ分からん、と思いもう少し調べてみたら、記事の内容より手っ取り早そうな方法を見つけました。
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." -- メッセージとスタックトレースが返却される