お布団宇宙ねこ

にゃーん

HaskellでMNISTを使えるようにする

Qiita に 雑な記事 を書いただけになっていたのでちょっとした解説記事を書きました。

今回やったこと

『ゼロから作るDeep Learning – Pythonで学ぶディープラーニングの理論と実装』 のMNISTを扱うための下記サンプルコードを、PythonからHaskellに実装し直しました。

deep-learning-from-scratch/mnist.py at master · oreilly-japan/deep-learning-from-scratch

MNISTについて

MNISTとは手書き数字画像のデータセットのことです。

MNISTには画像データ本体とそれに対応した数字のラベルがあり、それぞれに訓練用とテスト用のものが用意されています。

データの中身は例えば訓練画像は以下のような構成です。 16バイト以降が画像データで、28*28バイトのピクセルデータが60000枚入っています。

TRAINING SET IMAGE FILE (train-images-idx3-ubyte):

[offset] [type]          [value]          [description]
0000     32 bit integer  0x00000803(2051) magic number
0004     32 bit integer  60000            number of images
0008     32 bit integer  28               number of rows
0012     32 bit integer  28               number of columns
0016     unsigned byte   ??               pixel
0017     unsigned byte   ??               pixel
........
xxxx     unsigned byte   ??               pixel

Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

また、訓練ラベルは以下のような構成です。 8バイト以降がラベルデータで、1つ1バイトで10000枚入っています。

TRAINING SET LABEL FILE (train-labels-idx1-ubyte):

[offset] [type]          [value]          [description]
0000     32 bit integer  0x00000801(2049) magic number (MSB first)
0004     32 bit integer  60000            number of items
0008     unsigned byte   ??               label
0009     unsigned byte   ??               label
........
xxxx     unsigned byte   ??               label

The labels values are 0 to 9.

ref: MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

サンプルコードの処理の流れ

元のサンプルコードでは以下のような処理をやっています。

  • MNISTを準備する
    • MNISTをダウンロード
    • MNISTをNumPyで変換する
    • 変換したものをPickle化して保存する
  • Pickle化されたファイルを読み込んで復元する
  • オプションパラメータに応じて処理を行う
  • 画像とラベルのデータセットを返す

どう実装したのか

上記の処理フローの中でも重要な部分について解説します。

MNISTをNumPyで変換する→hmatrixで変換する

サンプルコードでは NumPy というライブラリを利用することでベクトルや行列を扱っています。

Haskellで実装する際には hmatrix というライブラリを利用しました。

例えば行列はこんな感じで定義できます。

// Python
import numpy as np
A = np.array([1,2],[3,4])

// Haskell
import Numeric.LinearAlgebra
let a = (2><2) [1,2,3,4] :: Matrix R

Haskellでは型を指定する必要があるので、画像データを Matrix R 、ラベルデータを Vector R とし、それをまとめたデータセットをタプルで定義しました。

type Image = Matrix R
type Label = Vector R
type DataSet = (Image, Label)

ダウンロードしたデータセットをhmatrixで変換するコードは下記のようになります。 画像データとラベルデータそれぞれの変換関数を作りました。

loadImg :: String -> IO Image
loadImg fn = do
    c <- fmap GZ.decompress (BL.readFile $ generatePath fn)
    return . matrix imgSize . toDoubleList $ BL.drop 16 c

loadLabel :: String -> IO Label
loadLabel fn = do
    c <- fmap GZ.decompress (BL.readFile $ generatePath fn)
    return . vector . toDoubleList $ BL.drop 8 c

BLData.ByteString.LazyGZCodec.Compression.GZip のことです。

fmap GZ.decompress (BL.readFile $ generatePath fn) の部分では、あるデータセットファイルを ByteString で読み込み decompress という関数を使って解凍しています。(ダウンロードするファイルはgz形式であるため)

はじめの方の項目で書いたようにMNISTのデータセットは、画像データが16バイト以降、ラベルデータが8バイト以降が必要となるデータなので、それより前は drop で切り捨てています。

hmatrixへの変換は、画像データの場合は matrix imgSize . toDoubleList 、ラベルデータの場合は vector . toDoubleList でやっています。共通する関数 toDoubleListVectorMatrix で扱う数値の型を Double にするための変換関数です。

toDoubleList :: BL.ByteString -> [Double]
toDoubleList = fmap (read . show . fromEnum) . BL.unpack

matrix には列数を引数として渡すことで行列を作ることができます。1行に画像データ1つが入ればよいので列数には画像サイズの 28*28=784 を指定します。一方でラベルデータは、1行にすべてのデータを入れるので vector を使っています。

最後に、loadImg , loadLabel を使ってhmatrixで変換した画像データとラベルデータをデータセット DataSet 型としてまとめます。

convertDataset :: IO [DataSet]
convertDataset = do
    tri <- loadImg . snd . head $ keyFiles
    trl <- loadLabel . snd . (!!1) $ keyFiles
    ti <- loadImg . snd . (!!2) $ keyFiles
    tl <- loadLabel . snd . (!!3) $ keyFiles

    return [(tri, trl), (ti, tl)]

変換したものをPickle化して保存する→バイナリで保存する

Pythonにはオブジェクトをバイナリファイルで保存しそれを復元するための機能が pickleモジュール で提供されています。

Haskellにはpickleのような機能は見当たらなかったのでpickleでやっていることを愚直にやることにしました。幸いなことにhmatrixの VectorMatrix という型は Data.Binaryインスタンスとなっているため、 Data.Binary の関数がそのまま使えます。

https://github.com/albertoruiz/hmatrix/blob/0.18.0.0/packages/base/src/Internal/Vector.hs#L417 https://github.com/albertoruiz/hmatrix/blob/0.18.0.0/packages/base/src/Internal/Element.hs#L40

実際にバイナリで保存するコードは下記のようになります。

createPickle :: String -> [DataSet] -> IO ()
createPickle p ds = BL.writeFile p $ (GZ.compress . encode) ds

データセットをまずバイナリに変換して圧縮します。 Data.Binaryインスタンスとなっているので encode という関数を適用するだけでバイナリ ( ByteString )に変換することができます。あとはこのBytestringを圧縮してバイナリファイルとして保存するだけです。圧縮には compress 、保存には writeFile を使います。

Pickle化されたファイルを読み込んで復元する→バイナリを読み込む

バイナリで保存したときと逆のことをすれば元のデータセットを復元できます。

loadPickle :: String -> IO [DataSet]
loadPickle p = do
    eds <- BL.readFile p
    return $ (decode . GZ.decompress) eds

利用例

MNISTを扱えるようになっただけでは少し物足りないので、これらのコードを使ってニューラルネットワークの推論処理をやって締めようと思います。

下記コードは こちらのサンプルコードHaskellで実装し直したものです。

入力層を784個、出力層を10個のニューロンで構成しています。

import Numeric.LinearAlgebra
import ActivationFunction
import Mnist
import SampleWeight

batchSize = 100

predict :: SampleWeight -> Vector R -> Vector R
predict ([w1,w2,w3],[b1,b2,b3]) x =
    softMax' . (\x'' -> sumInput x'' w3 b3) . sigmoid . (\x' -> sumInput x' w2 b2) . sigmoid $ sumInput x w1 b1

sumInput :: Vector R -> Weight -> Bias -> Vector R
sumInput x w b = (x <# w) + b

maxIndexPredict :: SampleWeight -> Vector R -> Double
maxIndexPredict sw x = fromIntegral . maxIndex $ predict sw x

take' :: Indexable c t => Int -> Int -> c -> [t]
take' n1 n2 x
    | n1 >= n2  = []
    | otherwise = (x ! n1) : take' (n1+1) n2 x

increment :: [Double] -> [Double] -> Double
increment ps l = fromIntegral . length . filter id $ zipWith (==) ps l

countAccuracy' :: Double -> Int -> SampleWeight -> DataSet -> Double
countAccuracy' a n sw ds@(i,l)
    | n <= 0    = a
    | otherwise = countAccuracy' (a+cnt) (n-batchSize) sw ds
        where ps = maxIndexPredict sw <$> take' (n-batchSize) n i
              ls = take' (n-batchSize) n l
              cnt = increment ps ls

main = do
    [_, ds] <- loadMnist True
    sw <- loadSW
    let r = rows $ fst ds
        cnt = countAccuracy' 0 r sw ds

    putStrLn $ "Accuracy: " ++ show (cnt / fromIntegral r)
$ stack runghc src/NeuralnetMnist.hs
Accuracy: 0.9352

サンプルコードと同じ値が出力されたので正しく実装できていそうです。

まとめ

MNISTを扱うコードは100行ほどで実装できましたが、テストを書かなくてもそれなりに動くものが作れるのはやはり型を定義しているおかげなのでしょう(コンパイルが通れば大体意図した通りに動く)。コードを読むときも型が書いてあることで一目で関数の入出力がわかるため全体の処理の流れが追いやすいです。しかし、今回のようにファイル操作など IO モナドを多用しているために若干読みづらいコードになっている気がします…。

今回紹介したコードは一部なので全貌が気になる方は こちらのリポジトリソースコードを置いてあります。

参考文献

MNIST 手書き数字データを画像ファイルに変換する - y_uti のブログ
HaskellでParsecを使ってCSVをパースする - Qiita
Haskellから簡単にWeb APIを叩く方法 - Qiita

Haskell で『ゼロから作るDeep Learning』(3)

『ゼロから作るDeep Learning – Pythonで学ぶディープラーニングの理論と実装』 の読書メモです。

今回は 「4.5.1 2層ニューラルネットワークのクラス」の手前まで。

4章 ニューラルネットワークの学習

学習

  • この章でいう学習とは、訓練データから最適なパラメータの値を自動で獲得すること
  • 機械学習ニューラルネットワーク(ディープラーニング)は、人の介入を極力避けてデータからパターンを見つけ出すことができる
    • 例えば、ゼロから数値を認識するアルゴリズムを考える代わりに、画像から特徴のパターンを機械学習で学習させる
    • この特徴のことを 特徴量 といい、有名なものとしては SIFT 、 SURF 、 HOG などが挙げられる
      • ただし、解く問題に応じた特徴量の設計が必要になることも
    • 機械学習の識別器として有名なのは SVM や KNN など
  • 一方で、ニューラルネットワークによる学習では、機械学習に必要な特徴量の設計は不要

訓練データとテストデータ

  • 訓練データ: 学習を行い、最適なパラメータを探索するのに使う
    • 教師データとも呼ばれる
  • テストデータ: 訓練データにより学習を行ったモデルの汎化能力を評価するのに使う
    • 訓練データには含まれていないデータで評価する
  • あるデータセットにだけ過度に対応した状態を 過学習 という

損失関数

  • ニューラルネットワークの学習では、指標(基準)を設け、それを手がかりに最適な重みパラメータを探す。この指標のことを 損失関数 と呼ぶ
  • 損失関数は、モデルがどれだけ訓練データに適合していないかというモデルの性能の悪さを示す指標である
  • 損失関数として有名なのは、 2乗和誤差交差エントロピー誤差
  • 損失関数はすべての訓練データを対象として求めるのが理想だが、データ量が膨大であるときは一部データを全体の近似として対象とする。このような学習手法を ミニバッチ学習 という
  • 認識精度を指標としないのは、パラメータの微分がほとんどの場所で 0 になるため
    • ある瞬間だけ変化するのではなく連続的に変化する指標が必要
    • 活性化関数を例に挙げると、シグモイド関数微分がそれにあたる

勾配

  • 勾配: すべての変数の偏微分をベクトルとしてまとめたもの
  • 勾配の結果にマイナスを付けて描画したとき、勾配の示す方向は各場所において関数の値を最も減らす方向となる
  • 勾配は損失関数が最小値を取る場所を探索するための手がかりとなる
    • 勾配はあくまで関数の値を最も減らす方向を示すだけであり、その方向が最小値を取るとは限らない(極小値や鞍点の可能性もある)
  • 勾配方向へ一定距離進むことを繰り返して関数の値を減らしていく手法を 勾配法 と呼ぶ
    • 厳密には最小値を探す場合を 勾配降下法 、最大値を探す場合を 勾配上昇法 という

ニューラルネットワークの学習

  • ニューラルネットワークの学習は 1.ミニバッチ学習 -> 2.勾配の算出 -> 3.パラメータの更新 -> 4. 1,2,3 の繰り返し という手順で行う
  • 上記手順は、無作為に選定したデータを用いていることから 確率的勾配降下法 (SGD) と呼ばれる

実践編

今回は無し

『デザインパターンとともに学ぶオブジェクト指向のこころ』を読んだ

デザインパターンとともに学ぶオブジェクト指向のこころ (Software patterns series)

デザインパターンとともに学ぶオブジェクト指向のこころ (Software patterns series)

なぜ読んだのか

最近、仕事でコードレビューを受けたときに「このコードはオブジェクト指向でないのでは? なんか関数型っぽい」と言われたことで Static おじさん?化の兆候に気がつき、なんとかせねばと思ったのがきっかけでした。

そもそも、何気なく触れていたであろうオブジェクト指向って一体何なんだっけという疑問を改めて解消するために読み始めました。

本書を選んだのは、チームメンバーがおすすめしていた本から選びました。 (教えていただきありがとうございました :pray: )

感想

オブジェクト指向について、本書では次のように説明しています。

オブジェクト指向は、オブジェクトという概念を中心に捉えたものの考え方です。このため、オブジェクトという観点からすべてのものごとを見ることになります。つまり、問題領域を機能に分解していくのではなく、オブジェクトに分解していくわけです。

– 1.6 オブジェクト指向パラダイム

また、オブジェクトについては

概念上の観点に基づいた場合、オブジェクトは責務を備えた実体であると定義できます。こういった責務によって、オブジェクトの振る舞いが定義されるのです。また、場合によっては、オブジェクトは特定の振る舞いを保持した実体であるとも考えられます。

– 8.2 オブジェクト : 従来からの考え方と新たな考え方

私は問題領域に存在するものを核に、その問題領域に存在する要求をメソッドとして肉付けしたものをオブジェクトとし、それらを扱うことをオブジェクト指向だと教わってきました。しかし、そのようなテクニックではすぐに設計が複雑化してしまい、変化に対応することが困難になることを指摘しています。オブジェクトをただの再利用可能な汎用モジュールとして定義するだけでは足りないというわけです。

本書は、変化に対応できるような柔軟な設計にするための道具として「共通性/可変性分析」や「デザインパターン」を用いた設計アプローチについての説明が主ですが、その説明を通してどのようにオブジェクト指向という考え方が使われているのかを知ることができました。また、デザインパターンの使い所についても、デザインパターンを適用するだけですべての問題が解決できるわけではないので、解決しようとしている問題に集中することが重要というのはなるほどなあという感じでした。デザインパターンはある問題に対する解決策の一例として見るべきだったのですね。

まとめ

一番の収穫は、今までの自分のオブジェクト指向の理解では不十分だったことを知れたことでした。旧来の考え方が間違っていたというよりも、問題に取り組むための視点を変えることでより柔軟な設計をすることができるという本書の説明の仕方にしている点も良かったです。

また、今までデザインパターンの本を読んできて腑に落ちていなかった点も、解決しようとしている問題についての比重が少なくデザインパターンを使う上での When , Why が曖昧だったのが原因なのかなと思ったのでした。このような視点を意識しつつ他の本ももう一度読み直してみたいです。

Haskell で『ゼロから作るDeep Learning』(2)

『ゼロから作るDeep Learning – Pythonで学ぶディープラーニングの理論と実装』 の読書メモです。

今回は 「3.6 手書き数字認識」の手前まで。

3章 ニューラルネットワーク

ニューラルネットワークとは

  • パーセプトロンでは人力で重みを決めていくのに対して、ニューラルネットワークは適切な重みを自動で学習できる
  • ニューラルネットワークは入力層、中間層(隠れ層)、出力層で構成される
  • 前章の ニューロンは受け取った信号の総和を計算し、それがある値を超えたときのみに 1 を出力する というような動作は関数で表すことができる。このような関数を「活性化関数」と呼ぶ
  • 出力層では、重み付き入力信号とバイアスの総和を計算し、それを活性化関数に渡した結果が出力される

活性化関数

  • 前章のパーセプトロンで使われていた、閾値を境にして出力が切り替わる関数を「ステップ関数(階段関数)」と呼ばれる
  • 活性化関数としてステップ関数以外の関数を使うことでパーセプトロンからニューラルネットワークの世界へ進むことができる
  • ニューラルネットワークでよく使われる活性化関数として「シグモイド関数」と呼ばれる関数がある
  • ステップ関数とシグモイド関数にはいくつか共通点がある
    • 入力信号の重要度に応じて、重要であれば大きな値を、逆に重要でなければ小さな値を出力する
    • 出力信号の値は 0 から 1 の間
    • 非線形関数である
      • 何か入力に対して、出力が入力の定数倍になるような関数を線形関数と呼ぶので、そうではない関数のこと
  • 最近は ReLU 関数というのも使われる
    • 入力が 0 を超えるとその入力をそのまま出力し、 0 以下なら 0 を出力する関数

出力層の設計

  • ニューラルネットワークの出力層の活性化関数は、問題の種類によって変更する必要がある
    • 回帰問題なら恒等関数、分類問題ならソフトマックス関数
  • ソフトマックス関数の出力の総和は 1 になる(もちろん各出力は 0 から 1.0 の実数の間に収まる)
    • この性質のおかげでこの出力を確率として解釈することができる
    • 確率として解釈できるから分類問題に適している
    • しかし、各要素の大小関係はこの関数を適用しても変わらないので、推論(分類)のフェーズでは省略されるのが一般的
  • 出力層のニューロンの数は解く問題に応じて決める必要がある
    • たとえば、手描きの画像が数字の 0 から 9 のどれか当てる問題なら、ニューロンの数を 10 個に設定する

実践編

行列の計算をやってみる

hmatrix ライブラリを使うことでベクトルや行列の計算ができるようになります。 以下は書籍で使われている PythonNumPy ライブラリとの比較です。

行列の生成

// Python
import numpy as np
A = np.array([1,2],[3,4])
B = np.array([5,6],[7,8])

// Haskell
// `R` は `Double` の型シノニムです。
import Numeric.LinearAlgebra
let a = (2><2) [1,2,3,4] :: Matrix R
let b = (2><2) [5,6,7,8] :: Matrix R

行列の次元数

// Python
A.shape

// Haskell
size a

行列の積

// Python
np.dot(A,B)

// Haskell
a <> b

例として、書籍 p.57 のニュートラルネットワークの内積を hmatrix を使って計算してみます。

*Main Numeric.LinearAlgebra> let x = (1><2) [1,2] :: Matrix R
*Main Numeric.LinearAlgebra> size x
(1,2)
*Main Numeric.LinearAlgebra> let w = (2><3) [1,3,5,2,4,6] :: Matrix R
*Main Numeric.LinearAlgebra> print w
(2><3)
 [ 1.0, 3.0, 5.0
 , 2.0, 4.0, 6.0 ]
*Main Numeric.LinearAlgebra> size w
(2,3)
*Main Numeric.LinearAlgebra> let y = x <> w
*Main Numeric.LinearAlgebra> print y
(1><3)
 [ 5.0, 11.0, 17.0 ]

活性化関数のグラフを描画してみる

グラフ描画には今回 Chart というライブラリを使いました。実際にコードを書くときにはパッケージのドキュメントを見ても使い方がよくわからなかったので、こちらの Wiki を見ながらやりました。

そして、書いたコードから出力されたグラフが以下。

f:id:ku00:20170225112708p:plain

参考文献

書いたコード

GitHub - ku00/deep-learning-practice

Haskell で『ゼロから作るDeep Learning』(1)

『ゼロから作るDeep Learning – Pythonで学ぶディープラーニングの理論と実装』の読書メモです。

www.oreilly.co.jp

この書籍のサンプルプログラムは Python で書かれていますが、表題の通り自分は Haskell で書いていきます。

今回は、2章全部です。

2章 パーセプトロン

パーセプトロンとは

  • パーセプトロンとは、複数の信号を受け取ってひとつの信号を出力するアルゴリズムのこと
  • 信号を入出力するものを「ニューロン」や「ノード」と呼ぶ
  • 入力信号が送られるときには重みが乗算される
    • 入力(x) -> 重み(w) -> 出力(y)
  • ニューロンは受け取った信号の総和を計算し、それがある値を超えたときのみに 1 を出力する
    • そのある値を「閾値」と呼ぶ
    • 閾値を超えて 1 を出力することを「ニューロンが発火する」と表現することもある

論理回路パーセプトロンで表現する

  • 論理回路 AND・NAND・OR はパーセプトロンを使って表現することができる
  • 信号の総和の計算に「バイアス」を加えることで、ニューロンの発火のしやすさを調整できる
    • 重みはその信号の重要度を調整するために使う
    • 両者をまとめて「重み」と呼ぶこともある
  • 論理回路 XOR は AND などと同じような構造のパーセプトロンでは表現できない
    • 2次元グラフに描画したときに、出力する値の境界を1本の直線で分けることができない
  • XOR はパーセプトロンの層を重ねることで表現できる

参考文献

書いたコード

今回は、論理回路(AND・NAND・OR・XOR)を hmatrix というライブラリを使って実装しました。

deep-learning-practice/Gate.hs at master · ku00/deep-learning-practice · GitHub

2016年振り返り

2016年をざっくり振り返ります。

仕事

今年は個人的に下半期の密度が高かったように感じました。

下半期は おいちゃん(@inouetakuya) と二人三脚で頑張りつつ、11月頃には ペパカレ のメンター的な役割もこなしたりいろいろやっていました。

周りに支えてもらいながらガシガシ開発した結果、いい感じの Gem も作ることができました。

github.com

やってる様子は下記のインタビュー記事でも確認できますので是非。

employment.en-japan.com

プライベート

イベント

去年は3つほどしか行ってませんでしたが、今年は毎月何かしらのイベントに行っていました。

原因は μ's ファイナルライブによる μ's ロスだったのだと思います。 (9人全員のイベントに参加することができたのは本当に運が良かった...)

来年も9人全員のイベントに顔を出せるかは分かりませんが運とお金があるうちは追い続けていきたいですね。

以下参加したイベントです。

Haskell

上半期で すごいHaskellたのしく学ぼう! を読了して(遅い)、下半期は AOJ を Haskell で解くなどしてました。

Haskell と少しは仲良くなれた気がしますがまだ実用レベルには達してないので、来年はその辺りを視野に入れて勉強していきたいです。

AOJ で解いた問題のコード

github.com

AOJ を解いたときのブログ

ku00.hatenablog.com

その他

  • 数年ぶりにウォークマンを購入
  • スクフェスAC で人生初のアーケード体験
    • まだ12回くらいしかやってないけど EXTREME フルコンできそう
  • スクフェスで Rank 300
    • 始めたのがラブライブ!のアニメ1期の放送終了後くらいだったのでだいたい3年半くらいやってる
    • 動体視力はだいぶ鍛えられた

f:id:ku00:20161231195216p:plain

来年の抱負

Gem を作ったことで Ruby という言語に対する意識もだいぶ変わってきてより深く知ろうとする意欲が湧いたので、やっぱりアウトプットは大事だなと実感しつつこういった活動は継続していきたいと思いました。

Haskell は実用レベルで何か作って、最終的に仕事に組み込めるように頑張ります 💪

そんな感じで来年もバーンとやってこ。

Stack を使って Scotty 入門してみた

Haskell の Web フレームワークである Scotty を触ってみました。

Yesod で使えるなら Scotty でも同じようにできるだろうと思って今回も Stack を使ってみました。

Hello World 的なものを表示するまで

プロジェクトを新規作成してから setup までやっておきます。

stack new hello-scotty
cd hello-scotty
stack setup

次に、依存ライブラリとして Scotty を cabal ファイルに追記します。

executable hello-scotty-exe の段落に追記するのがポイントです。

( library ではありません)

hello-scotty.cabal

...

executable hello-scotty-exe
  hs-source-dirs:      app
  main-is:             Main.hs
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  build-depends:       base
                     , scotty
                     , hello-scotty
  default-language:    Haskell2010

...

そして stack build

$ stack build

ansi-terminal-0.6.2.3: configure
ansi-terminal-0.6.2.3: build
base-compat-0.9.1: configure
base-compat-0.9.1: build
auto-update-0.1.4: configure
auto-update-0.1.4: build
appar-0.1.4: configure
appar-0.1.4: build

...
Completed 67 action(s).

終わったら次は Scotty の README に載っているサンプルをそのままコピペしましょう。

Haskell のコードを試すときは src ディレクトリ以下にファイルを置いてそれを stack ghci で読み込むようにしてますが、今回はちゃんとしたアプリケーションなので app/Main.hs に書いていきます。

app/Main.hs

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty

import Data.Monoid (mconcat)

main = scotty 3000 $ do
    get "/:word" $ do
        beam <- param "word"
        html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]

あとはサーバを起動するだけです。 stack exec で実行します。デフォルトだと [プロジェクト名]-exe が実行ファイル名になります。

$ stack exec hello-scotty-exe

Setting phasers to stun... (port 3000) (ctrl-c to quit)

見れました。

f:id:ku00:20170109113731p:plain

実はテンプレートがあった

Yesod のときは「テンプレート」というものを使って簡単にセットアップできたのですが、調べたら Scotty のものもあったという...。

(Yesod のは 公式のクイックスタート にも載っています )

github.com

使い方は簡単で、 stack new でプロジェクト名の後に使いたいテンプレート名を入れるだけです。

$ stack new template-hello-scotty scotty-hello-world

Downloading template "scotty-hello-world" to create project "template-hello-scotty" in template-hello-scotty/ ...
Looking for .cabal or package.yaml files to use to init the project.
Using cabal packages:
- template-hello-scotty/template-hello-scotty.cabal
...

生成されたファイルも最低限という印象。

f:id:ku00:20170109113735p:plain

まとめ

Stack を使うと一通りのことをやってくれるので本当に便利ですね。また、 Yesod と比べると Scotty は依存ライブラリがそこまで多くないのでセットアップに時間がかからないです。

さて、次からは本格的なの作っていくぞ。

参考文献