最近の趣味は「Haskellはいいぞ」と呟くかTwitter Search: Haskellを巡回して を押して回ることです
毎日巡回しているとHaskellに入門しようとするも細かいところに引っかかって前に進めないでいる人をちらほら見かけます。今回はそんな見回りの知見を活かしてHaskell初心者が躓きやすいポイントをまとめてみたいと思います。
##1. 入門書は何がいいの?
それはもう_すごいH本_一択でしょう!…と言いたいところですが時々不満の声を聞くこともあります。確かに_すごいH本_こと『すごいHaskellたのしく学ぼう!』は__世界一わかりやすいHaskell入門書__であることは間違いないと思いますが、逆に内容が平易すぎるため記述が冗長だと感じたり読み終わっても何か自分で作れるようになった気がしなかったりするかもしれません。なので僕は「プログラミングも初心者でHaskellから入門してみたい」という人には__すごいH本__をおすすめしています が、そうではなく「普段書いてるプログラミング言語はあるけど最近関数型言語とかよく聞くしHaskellにも入門してみたい」というような人には『関数プログラミング実践入門』をおすすめしています。関数プログラミングと広く銘打ってありますが中身はHaskellを中心に書かれているので安心して下さい 。(ここに書いてるのはあくまで個人的な見解なので実際に本を購入するときは自己責任でお願いします )
追記 2018/07/08
『Haskell入門 関数型プログラミング言語の基礎と実践』という本も発売されました。こちらも文法から丁寧に解説されており入門書として非常におすすめです。
本なんて買うのめんどくさいからWebの記事で手っ取り早く入門したい
という人のためにいくつかオススメの入門記事を列挙したいと思います。
- 英語だけどわかりやすい記事
- Try Haskell! - 5分あったらすぐ始められる!
- Learning Haskell - 図が豊富でわかりやすい!
- A community maintained course for learning Haskell - エッセンスがギュッと詰まってる!
- 日本語で書かれた入門記事
- ウォークスルー Haskell - サクッと基本をおさえられる!
- こわくないHaskell入門(初級) - Haskellこわくないよ!
- Haskell 超入門 - とても丁寧に書かれてる上にボリューム満点!
- What I Wish I Knew When Learning Haskell - 初心者から上級者まで必携のHaskell辞典!
##2. 開発環境って何を入れたらいい?
Haskellの代表的なコンパイラはGHCです。広く使われているパッケージマネージャはCabalです。そしてGHCのバージョン管理と各バージョンで動作するパッケージだけを集めて管理してくれる大変優秀なツールがStackです なので一番最初はStackを入れましょう。大事なことなのでもう一度いいます、Stackを入れましょう。時々GHCとCabalを直接インストールしていたりHaskell Platformの方をインストールしている人を見かけますが理由は色々あってやっぱりオススメなのはStackです。それなのになんでStackを入れるように書いてある本や記事が少ないのかというとStackは最近(確か2015年6月頃)出てきた新しいツールだからです。新しいツールですがあまりの便利さのために今やStackを使わないHaskellライフが考えられないほどです。
Mac OS Xでbrewを使ってる場合、Stackのインストールは
$ brew install haskell-stack
で終わりです。無事インストールが終了したら
$ stack setup
を実行しましょう。これは最初の一回だけで十分です。このコマンドでGHCやCabalのインストールが行われます。めでたくHaskellの開発環境の完成です 。その他のOSの場合はこちらを参照してください。
実はStackでGHCを入れた場合ghc
コマンドは直接は入らずstack
のサブコマンドとして実行する必要があります。
$ stack ghc
そのためghc
やghci
を気軽に利用するため~/.bashrc
などに以下のエイリアスを追加しておくのがオススメです
alias ghc='stack ghc --'
alias ghci='stack ghci --'
alias runhaskell='stack runhaskell --'
- Stack参考文献
- Haskellのビルドツール"stack"の紹介
- Stackでやる最速Haskell Hello world! (GHCのインストール付き!)
- StackでHaskellプロジェクトを作ってみる
- Haskell環境構築_20151226_最新版_4_投稿済み_stack-1.0.0対応版.md
- Stackを使って楽しくHaskellスクリプティング
##3. 変数に再代入できなくてもプログラムが書けるの?
書けます
##4. for文なくてもプログラムが書けるの?
書けます
もちろん書けますよ!!!
変数が全て不変(イミュータブル(再代入できない))であっても for文もwhile文も無くても不自由なくプログラムを書くことが出来ます このような印象を持もたれているのはHaskell製のソフトウェアをそんなに知らないことが原因だと思うのでいくつか紹介していきたいと思います。
-
lukasmartinelli/hwk - Haskellで書かれた
awk
のようなツールです。Haskellの文法が使えるので慣れていればこっちの方が記述量が少なくなることも多く重宝しています! - ShellCheck - その名の通りシェルスクリプトが正しいかどうかチェックしてくれるツールです!文法的に間違ってるところやもっとこうした方がいいというところを指摘してくれます。手書きのシェルスクリプトも事前にこういたツールにかけると安心して動かせますね!ブラウザ上ですぐに試すことが出来るのでぜひ遊んでみてください。
- Pandoc - Pandocは様々な形式のドキュメントを別の形式のドキュメントに変換してくれるツールです。どれだけの種類のドキュメントに対応しているかは公式ページを見てみて下さい。トップページの下部にある図は圧巻です!
- Monadius - グラディウスというゲームをHaskellで実装したものです!状態とループの塊であるゲームだってHaskellで書けちゃいます。公式サイトは日本語で書かれておりHaskellでゲームを作る時の知見も書かれているのでぜひ一読をオススメします!
- facebook/Haxl - Facebookが開発しているリモートにあるデータへのアクセスを抽象化するライブラリです。N+1問題を解決してくれたり別のリソースには平行してアクセスしてくたりと便利なライブラリです
- BlockApps STRATO - Haskellで書かれたEthereumベースのブロックチェーンです。Microsoft Azureで提供されていてすぐにクラウド上で試すことができます。世界の42の銀行が参加するR3Cevのブロックチェーン・コンソーシアムにも採用されていてこれからの金融市場を支える基盤技術になっていくでしょう
だから最初は戸惑っても挫けずに勉強してみてくださいね
##5. 複数種類の値を返したい
ある時は文字列をまたある時は数値を返す関数を作りたい。でもHaskellは型が決まっちゃってるから書けない…
書けないなんてことはない!そういう時は Either String Int
が使えます
safeDiv :: Int -> Int -> Either String Int
safeDiv _ 0 = Left "integer division by zero"
safeDiv n m = Right (n `div` m)
この様に文字列も数値も返すことが出来ます。さらに使うときは
main :: IO ()
main = do
case safeDiv n m of
Left error -> putStrLn error
Right q -> putStrLn $ "Result: " ++ show q
この様に型を意識して取り出せるので型安全なプログラムを書くことが出来ます。例の通りEither
は例外処理に用いられることが多いです。
今度は三種類以上の値を返したい場合を考えましょう。ここまで来たらそもそも設計がおかしいんじゃないかと疑いたくなりますがとにかくやってみましょう。Either
を組み合わせてEither String (Either Bool Int)
のようにすることも出来ますが、この場合は自分でデータ型を定義するのが得策でしょう。
data MyData = VBool Bool
| VInt Int
| VStr String
こんな感じ。関数は
myFunc :: String -> MyData
myFunc "true" = VBool True
myFunc "false" = VBool False
myFunc "100" = VInt 100
myFunc value = VString value
こんな感じで定義します。使うときは
main :: IO ()
main = do
case myFunc string of
VBool b -> putStrLn $ "Bool: " ++ show b
VInt i -> putStrLn $ "Int: " ++ show i
VStr s -> putStrLn $ "String: " ++ s
こんな感じです。このような関数を書くことはあまりないと思うのですが、どうしても書きたくなった時はいずれ成長してから書き直しに戻ってくるところと覚えておいて一旦はここにあるような方法で書いておきましょう
##6. 乱数の作り方がわからない
Haskellの乱数について調べるとStdGen
とか出てきてナンダカヨクワカラナイってなると思います。ですが単純に乱数を生成するだけならIOモナドを使うのが手っ取り早いです!
import System.Random
main :: IO ()
main = do
r <- randomIO :: IO Double
print r
-- $ runhaskell Main.hs
-- 5.571578485008155e-2
簡単でしょう?ランダムな整数がほしい時は
import System.Random
main :: IO ()
main = do
r <- randomIO :: IO Int
print r
-- $ runhaskell Main.hs
-- -7817900208843160045
この様に型注釈を変えるだけでOK
範囲を限定して生成したい時は
import System.Random
main :: IO ()
main = do
r <- randomRIO (1,100) :: IO Int
print r
-- $ runhaskell Main.hs
-- 46
randomRIO
を使えば大丈夫です。この例では1から100までの間のランダムな整数を生成しています。
試しにモンテカルロ法で円周率を求めてみましょう!
import System.Random
-- 点を生成して四半円の中に入っている個数を数える関数
monte :: Int -> Double -> IO Double
monte 0 inside = pure inside
monte limit inside = do
x <- randomRIO (0,1) :: IO Double
y <- randomRIO (0,1) :: IO Double
-- 四半円の中に入っているか?
if x^2 + y^2 < 1
then monte (limit-1) (inside+1)
else monte (limit-1) inside
main :: IO ()
main = do
inside <- monte 10000 0
print $ 4 * inside / 10000
-- $ runhaskell Main.hs
-- 3.1384
最初のうちは乱数が欲しくなったらここにあるコードをコピペして使って下さい
import System.Random
main :: IO ()
main = do
-- 基本的な乱数生成
r0 <- randomIO :: IO Int
r1 <- randomIO :: IO Double
r2 <- randomIO :: IO Bool
print (r0, r1, r2)
-- (6129061023494953312,0.35663084737564565,True)
-- 範囲の決められた乱数生成
r3 <- randomRIO (1,100) :: IO Int
r4 <- randomRIO (0,1) :: IO Double
print (r3, r4)
-- (88,0.40205531778476833)
-- ランダムな文字列生成
let randomSamples :: [a] -> Int -> IO [a]
randomSamples xs n = map (xs!!) <$> (sequence . replicate n $ randomRIO (0, length xs - 1))
r5 <- randomSamples ['a'..'z'] 10
r6 <- randomSamples (['a'..'z']++['A'..'Z']) 10
r7 <- randomSamples (['a'..'z']++['A'..'Z']++['0'..'9']) 10
print (r5, r6, r7)
-- ("zpnmprtjoh","AVZIebceLK","cjFym4gmjR")
##7. モナドは何だ
オブジェクト指向のプログラミング言語を初めて学んだ時、クラスとかオブジェクトといった概念に最初は慣れなかった記憶はありませんか?Haskellの場合はモナドで同じ現象が起こるのだと思います。モナドは新しい概念でとっつきにくいとは思いますが使えるようになるととても便利なものですよ!
一度別のパラダイムに慣れてしまった人にはどうもこのモナドが鬼門になるようで世の中には大量のモナドチュートリアルが出回っています。ここでは日本語で書かれていてわかりやすいモナドに関する記事を厳選していくつか紹介します。
- 初心者のモナド
- 函数プログラミングの集い2011チュートリアル「モナドについて」
- 難しいのは見た目だけ!?Haskellのモナドの「たった2つのルール」を簡単に理解する!
- モナモナ言わないモナド入門
- 箱で考えるFunctor、ApplicativeそしてMonad
- モナドは象だ
Haskellでは入出力を伴うような"時と場合によって結果が変わる関数"はIOモナドを使って実現されています。なので"Hello World"を表示するプログラムですらモナドが現れるのでモナドから逃れることは出来ません。ですが一度使えるようになってしまえばモナドは非常に強力で便利な道具です。モナドを身につける一番効率のいい方法はモナドを使ってプログラムを書くことです。
main :: IO ()
main = do
putStr "あなたの年齢を3で割った余りを教えて下さい: "
m3 <- read <$> getLine :: IO Int
putStr "あなたの年齢を5で割った余りを教えて下さい: "
m5 <- read <$> getLine :: IO Int
putStr "あなたの年齢を7で割った余りを教えて下さい: "
m7 <- read <$> getLine :: IO Int
let age = (m3 * 70 + m5 * 21 + m7 * 15) `mod` 105
putStrLn $ "あなたの年齢は" ++ show age ++ "です!"
これは百五減算で年齢を当てるゲームです。最初のうちはこのような簡単なプログラムを書きまくってモナドやdo構文に慣れることが肝心です!
ところで「モナドは単なる自己関手の圏におけるモノイド対象だよ。何か問題でも?」という有名なフレーズがあります。実はモナドはHaskellだけの概念ではなく数学の一分野である圏論に出てくる概念です。Haskellのモナドを調べていたらいつの間にか数学の話が出てきてわけがわからなくなるというようなことはよくあります。しかしHaskellのプログラムを書くだけであればモナドの数学的な背景を知る必要はありません。使い方さえわかってしまえば何も問題ありません。ただモナドは圏論の中でちゃんとした定義が与えられているということは重要です。もしモナドに対して勝手な解釈から議論が始まったとしても定義に戻って考えることで何が正しいのかを見失わずに済むでしょう。
##8. 何か作りたいけどネタがない…
新しい言語を学ぶ時はとりあえず自分でなにか作ってみるのは大事ですね
まずは自分が作りたいと思えるものに出会うことが大切だと思うのでHaskellの素敵なライブラリを紹介していくことにします
簡単なWebサービスのアプリケーションサーバーをHaskellで書いてみるというのはどうでしょう?HaskellのWAFとして有名なのはYesodですが最初からこれに取り組むと泥沼にハマるので僕はオススメしません
HaskellにはWAIというWebアプリのインターフェースがあります。PythonにおけるWSGIやRubyにおけるRackのようなものです。WAIに適合する標準的なサーバーであるwarpと組み合わせれば簡単なWebアプリを書くことが出来るので最初はここからはじめてみるといいと思います
stackを使ってhaskellで最小のWeb Applicationしてみる
WAI/Warpだけだとシンプル過ぎるという場合はSpockというWAFを使うのがオススメです。Spockはルーティングやセッション管理などWebアプリケーションを作る上で必要な機能を提供してくれます。
出典: Spock Tutorial
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Monoid
import Web.Spock.Safe
main :: IO ()
main =
runSpock 8080 $ spockT id $
do get root $
text "Hello World!"
get ("hello" <//> var) $ \name ->
text ("Hello " <> name <> "!")
BitTorrentのクライアントを一から実装してみるのはどうでしょう?以下の記事ではBencodeをパースするところから実装するのですが易しく書かれているのでとても勉強になると思います!
Creating a BitTorrent client in Haskell #1
Creating a BitTorrent client in Haskell #2
Creating a BitTorrent client in Haskell #3
glossを使うと図形の表示やユーザーからの入力を簡単に扱うことが出来ます。例えばglossでマンデルブロ集合を描画してみたものがあります
出典: Mandelbrot shift
Chartを使えば簡単にグラフを書くことが出来ます。バックエンドを入れ替えるだけで同じコードを使って画像ファイルに吐き出したりウィンドウに表示したり出来るのが特徴です!
出典: timbod7/haskell-chart wiki
SpreadsheetのUIでインタラクティブなGUIアプリを作ることが出来るtyped-spreadsheetというライブラリがあります。
Haskell-native spreadsheets
diagramsと連携してパラメータをいじるとすぐに図が変化するような環境も作れるのでかなり遊べると思います!
出典: Interactive and composable charts
OpenGLを使ってゲームを作ってみるというのはどうでしょう?
OpenGL Tutorial 1
OpenGL Tutorial 2
文献は多くはないですがかなりいい練習になると思いますよ!
##まとめ
正直に言うとHaskellはもう10年以上もあるにかかわらずネット上の文献も少なく独学で習得するのは決して簡単ではない言語だと思います。特に日本語の記事となると極端に少ないので初心者の人にはかなり敷居が高く感じられるのではないでしょうか。しかし並行並列プログラミングが当たり前になり大規模な開発に耐えうる言語が求められるにつれHaskellの重要性はますます増してくると思います。そんな中Haskellを学ぼうとする人たちの転ばぬ先の杖となるようこの記事が少しでもお役に立てればいいなと思います。
この記事は第30回Haskellもくもく会@朝日ネットのもくもく時間を利用して書かれました。この勉強会は普段仕事でHaskellを書いている人からHaskellを始めようと思ってる初心者まで誰でも参加できるアットホームで明るい勉強会です。懇親会も大盛り上がりなので興味のある型はぜひ参加してみてくださいー
##追記: 20160326
紹介したrandom
は簡単でわかりやすいのですが現在はあまりオススメされてないようです。(参考: Haskellの乱数事情)
なので慣れてきたら別のライブラリを使ったほうが良いでしょう。
本文中に挙げた例をmwc-random
で書きなおしたものを載せておきます。
import System.Random.MWC
main :: IO ()
main = withSystemRandom . asGenIO $ \gen -> do
-- 基本的な乱数生成
r0 <- uniform gen :: IO Int
r1 <- uniform gen :: IO Double
r2 <- uniform gen :: IO Bool
print (r0, r1, r2)
-- (6129061023494953312,0.35663084737564565,True)
-- 範囲の決められた乱数生成
r3 <- uniformR (1,100) gen :: IO Int
r4 <- uniformR (0,1) gen :: IO Double
print (r3, r4)
-- (88,0.40205531778476833)
-- ランダムな文字列生成
let randomSamples :: [a] -> Int -> IO [a]
randomSamples xs n = map (xs!!) <$> (sequence . replicate n $ uniformR (0, length xs - 1) gen)
r5 <- randomSamples ['a'..'z'] 10
r6 <- randomSamples (['a'..'z']++['A'..'Z']) 10
r7 <- randomSamples (['a'..'z']++['A'..'Z']++['0'..'9']) 10
print (r5, r6, r7)
-- ("zpnmprtjoh","AVZIebceLK","cjFym4gmjR")