はじめに

この記事は「Haskell (その4) Advent Calendar 2017」の13日目の記事になりました。

書籍「すごいHaskellたのしく学ぼう」は2012年に発売され、現在も販売されているHaskell入門にぴったりの良書です。しかし、内容が2012年当時のままで止まってしまっています。

そこでこの記事では(もう年末ですが)2017年現在「すごいHaskellたのしく学ぼう」を読むにあたって注意すべき点を章ごとにまとめて紹介します。

イントロダクション

イントロダクションの「Haskellの世界に飛び込むのに必要なもの」ではHaskell Platformをインストールするように紹介されています。しかし、現在ではStackを利用するのが一般的です。

Linux系OSではリポジトリにある「stack」や「haskell-stack」などのパッケージを導入することで使えるようになりますし、もしリポジトリになくても以下のコマンドでインストールすることができます。ただし、Arch Linuxでは一部パッケージのビルドができない不具合があるため、AURにあるstack-staticパッケージをyaourtやpacaurなどで導入してください。

curl -sSL https://get.haskellstack.org/ | sh

macOSでは、Homebrewにhaskel-stackパッケージがあるのでインストールしましょう。Windowsでは専用のインストーラーが公式サイトからダウンロードできるので活用してください。

Stackをインストールしたら、GHCの準備をします。結構時間がかかるので、以下のコマンドを入力して終わるまで放置しましょう。自動的にStackageのLTSで指定されているGHCをインストールしてくれます。

$ stack setup

終わったら起動GHCiをしてみましょう。Stack経由でインストールしたので、先頭にstackをつけます。ghcコマンドやrunghcコマンドも同様です。面倒であればお使いのシェルの設定ファイルにエイリアスを加えてください。その際にはオプションの解釈を後ろ側のコマンドに寄せるために、--を追加しておくと良いでしょう。

$ stack ghci
.bashrc
alias ghci='stack ghci'
alias ghc='stack ghc --'
alias runghc='stack runghc --'

第1章

リスト入門

「1.3: リスト入門」の脚注で「GHCiの中で名前を定義するときはletキーワードを使ってください」とありますが、執筆時点のGHCiバージョン(8.0.2)では不要になっています。従って、該当のサンプルコードは以下のように書くことができます。以下変数の束縛にletを用いる場面は全て同様に省略できます。

ghci> lostNumbers = [4,8,15,16,23,42]
ghci> lostNumbers
[4,8,15,16,23,42]

第2章

とくになし

第3章

とくになし

第4章

とくになし

第5章

とくになし

第6章

モジュール

訳注に以下の記述があります。

Haskell Platformをインストールしてあるなら、cabalコマンドが使えるはず。「cabal install パッケージ名」というコマンド一発で、様々なパッケージをインストールできますよ!

しかし今回はHaskell Platformではなくstackを利用しています。従って、パッケージをグローバルにインストールする際には以下のコマンドを利用します。

$ stack install パッケージ名

第7章

とくになし

第8章

Hello, World!

イントロダクションでも紹介しましたが、GHCでコードをコンパイルする際には先頭にstackをつけるか、事前にエイリアスを設定しておいてください。--makeオプションがstackのオプションと解釈されないように--を間に挟んでおくのがミソです。

$ stack ghc -- --make helloworld # aliasなしの場合
$ ghc --make helloworld          # aliasありの場合

第9章

ランダム性

System.Randomパッケージはデフォルトでインストールされなくなりました。利用するためには、以下のコマンドで個別に導入する必要があります。パッケージ導入後ghciを再起動して、System.Randomを読み込めば手順を再現できます。

$ stack install random

第10章

とくになし

第11章

ファンクターとしての関数

関数がファンクターとして定義されているモジュールがControl.Monad.Instancesであると紹介されていますが、すでにGHC.Baseに移されています。Control.Monad.Instancesは将来的に削除予定なので気をつけましょう。

また、NoMonomorphismRestrictionオプションを有効化するように指示されていますが、GHC 7.8.1からデフォルトで有効化1されるようになったのであえて付け加える必要はありません。

アプリカティブスタイル

Control.Applicativeはfmapと等価な中値演算子<$>をエクスポートしています。

とありますが、<$>は特に何もインポートしなくても使えるようになっています。<*>pureも同様です。

第12章

モノイドで畳み込む

Preludeに定義されているfoldlなどのFoldableな関数について、かつてはFoldable型クラス制約が付いていませんでした。しかしGHC 7.10からはつくようになったので、わざわざData.Foldableを読み込む必要はなくなりました。例えば以下のコードはそのまま通るようになります。

Prelude> :t foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
Prelude> foldr (*) 1 [1,2,3]
6

第13章

Monad型クラス

Monad型クラスはApplicative型クラス制約を付与して定義されるように変更されています。具体的には、以下の実装になっています。

class Applicative m => Monad m where
    (>>=)       :: forall a b. m a -> (a -> m b) -> m b
    (>>)        :: forall a b. m a -> m b -> m b
    m >> k = m >>= \_ -> k

    return      :: a -> m a
    return      = pure

    fail        :: String -> m a
    fail s      = errorWithoutStackTrace s

Monadのインスタンスは必ずApplicativeのインスタンスでもあるよう、クラス宣言の前に、class (Applicative m) => Monad m whereという型クラス制約があるべきではないでしょうか

という記述がありますが、当時とは異なり現在ではそのような実装に変更されています。

第14章

もうちょっとだけモナド

パッケージの一覧取得にghc-pkg listコマンドを使うように紹介されていますが、例によってstack環境なので下記コマンドで代用します。

$ stack exec ghc-pkg -- list

また、mtlパッケージもデフォルトでは導入されていないため、インストールしておきましょう。

$ stack install mtl

モナドとしての関数

例によって(->) rのモナドとしての定義はControl.Monad.InstancesではなくGHC.Baseにあります。また、MonadがApplicativeになったおかげで、returnpureが等価な関数であると明確に定義されたため、Monadとしての定義にreturnを定義する必要がなくなりました。

instance Applicative ((->) a) where
    pure = const
    (<*>) f g x = f x (g x)
    liftA2 q f g x = q (f x) (g x)

instance Monad ((->) r) where
    f >>= k = \ r -> k (f r) r

Readerモナド

addStuff関数を定義しているサンプルコードではControl.Monad.Instancesを読み込んでいますが、上記理由により不要になっています。

liftMと愉快な仲間たち

MonadにもApplicativeに対して同じような型クラス制約が付いているべきだった

とありますが、先ほど説明したとおりすでに付与されています。

モナドを作る

MonadがApplicativeになった影響で、Monadだけ実装してもコンパイルが通らなくなってしまいました。とりあえずコンパイルを通すために、Applicativeの実装も付け加えましょう。実装は簡単で、returnpureが等しいことを利用して、returnの内容をpureに写し取るだけです。

import Data.Ratio

newtype Prob a = Prob
    { getProb :: [(a, Rational)]
    } deriving Show

instance Functor Prob where
    fmap f (Prob xs) = Prob $ map (\(x, p) -> (f x, p)) xs

flatten :: Prob (Prob a) -> Prob a
flatten (Prob xs) = Prob $ concat $ map multAll xs
    where multAll (Prob innerxs, p) = map (\(x, r) -> (x, p * r)) innerxs

instance Applicative Prob where
    pure x = Prob [(x, 1 % 1)]

instance Monad Prob where
    m >>= f = flatten (fmap f m)
    fail _ = Prob []

第15章

とくになし (Zipperに挑むもよろしく!)

おわりに

色々見てきましたが、環境構築方法の変更や、MonadがApplicativeになった影響が一番大きかったかなと思います。ただ、5年経ってもこの本で紹介されているHaskellのコアな部分は変わりませんので、安心して学んでください!


  1. 正確にはMonomorphism Restrictionがデフォルトで無効化されました