Haskell チュートリアル (Haskell Day 2016)

## agenda

演習時間を使ってチューターに質問して下さい。

* [事前準備](http://qiita.com/hiratara/items/7d5456eb5ae239de34a8)
* チューターの自己紹介
* Haskellの基本
* 演習
* I/Oアクション
* 演習
* Turtleを使ったShellプログラミング
* 演習

問題が解けたら、 [演習問題の回答例](https://gist.github.com/hiratara/22384fab8cbf5ca7670b2dc348b916c7) も参照してみて下さい。

----

## Haskellとは

* 普通のプログラミング言語
* 他の言語で書ける全てのプログラムは書くことができる
* 一方で、特徴的な側面もある

----

## Haskellの特徴

* 純粋関数型言語
* 強力な型推論
* 遅延評価

----

## 関数型言語とは

* 全てを式で表す
* プログラムの実行 = 式の評価
* 副作用のある式を評価すると副作用が起こる

----

## 手続き型との違い

手続き型の言語では、1行目を実行し、2行目を実行する。

```
main = do
    putStr   "Hello, "
    putStrln "world!"
```

### 文法メモ

* `putStr`、`putStrLn` : 文字列(`String`)の表示

----

## 関数型言語の実行

関数型では、式の評価を進める。Haskellでは`main`の評価を進める。

```
    putStr   "Hello, " >>= (\_ ->
    putStrln "world!")
```

`>>=` の第一引数を評価すると `()` 。この評価の副作用で、 "Hello," が印字される。

```
    (\_ -> putStrln "world!") ()
```

```
    putStrln "world!"
```

評価結果は `()` 。この式の評価の副作用で、 "world!\n" が印字される。

```
    ()
```

もう評価できないので、終了。

### 文法メモ

* `()` : 1つしか値がない型の値
* `\A -> B` : 構文。引数がAで本体がBの無名関数
* `>>=` : `IO ...`型の値へ関数を適用。(See also: [Monad](https://hackage.haskell.org/package/base/docs/Control-Monad.html))

----

## 「純粋」関数型言語

* 評価すると **暗黙的に** 副作用を起こす式がない
* **明示的に** 副作用を起こす式だけがある (`IO`型コンストラクタ)
* 明示的 == 副作用の有無を机上で判定できる

```haskell
reverse "PATH" :: String
length "PATH" :: Int
getEnv "PATH" :: IO String
putStrLn "PATH" :: IO ()
```

----

## 強力な型推論

Hindley/Milner 型推論アルゴリズム という、よく知られたものがベース。

型を1つも書かなくてよい。プログラムだけ書けば型チェックしてもらえる。

```haskell
Prelude> putStrLn (replicate '-' 4)

<interactive>:25:21:
    Couldn't match expected type ‘Int’ with actual type ‘Char’
    In the first argument of ‘replicate’, namely ‘'-'’
    In the first argument of ‘putStrLn’, namely ‘(replicate '-' 4)’
    In the expression: putStrLn (replicate '-' 4)
```

### 文法メモ

* `replicate` : 同じ文字を指定個数並べた文字列の生成

----

## 遅延評価

* 関数の引数は必要となるまで評価されない
* 評価しなくていい = 計算量を減らせる可能性
    * 例: 無限リスト
* 評価できない = スペースリーク
    * 例: `1 + 2 + 3 + ...` (計算できれば `Int` 型の値1つ)
* いつ必要となるのか、が不定
    * 「副作用」を伴う式と相性が悪い
    * `IO` 型による副作用の発生順の制御が必要

```haskell
print2nd x1 x2 = do
    print x2

-- 第一引数に渡るのは何行目？ (コンパイルできない)
-- main = do
--     print2nd getLine getLine

main = do
    x1 <- getLine
    x2 <- getLine
    print2nd x1 x2
```

----

## HaskellでShellプログラミング

副作用の塊なので、「純粋関数型言語」と相性が悪いと思われるかもしれませんが、全くそんなことはありません。

----

## `IO ...` 型の関数の利用

* 普通のプログラミング言語の関数は、全て副作用が起こりうる
* Haskellの副作用が起こりうる関数は、戻り値が `IO ...` 型
    * `x1 -> x2 -> ... -> xn -> IO y`
    * このような関数だけを使うのであれば普通の言語と同じ！
* 実際、`IO` 型は絶対に必要で超重要

----

## Stack

* Haskell のビルドツール : [stack](https://docs.haskellstack.org/)
    * `cabal`、`ghc` は直接使わない(直接使ってる記事は古い可能性大)
    * Haskell Platform ・・・？ (内部で `stack` を使っている)
* ライブラリのバージョンを固定
    * [hackage](http://hackage.haskell.org/) や [hoogle](https://www.haskell.org/hoogle/) は常に最新版
    * [stackageのAPIドキュメント](https://www.stackage.org/lts) を見た方がよい
    * hackageにしかないものは自分でバージョンを選んで採用する

----

## Stackのインタプリタ機能

今日はこの使い方（通常はプロジェクトを作ってビルドする）。

* コマンドではなくファイルを渡すとインタプリタになる
* 1行目にシェバング、2行目に`stack runghc`コマンド
* `stack runghc` へオプションを渡すための機能

```haskell
#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc --package turtle
...
```

----

## Haskellのプログラム

* `main` から始まる
* `do` で I/O のブロックを表す
* ブロックはインデントで

```haskell
main = do
    putStr   "Hello, "
    putStrLn "World."
```

----

## REPL

* `stack repl` で起動
* `:l` ソースコードの読み込み
* `:main` 実行
* `:r` リロード
* `:?` ヘルプ、 `:q` 終了

```haskell
$ stack repl
Prelude> :l pwd.hs
[1 of 1] Compiling Main             ( pwd.hs, interpreted )
Ok, modules loaded: Main.
*Main>
```

----

## 演習

### 演習1-1).

`hello.hs` を以下の内容で作成し、実行権限を与え、実行して下さい。

```haskell
#!/usr/bin/env stack
-- stack runghc

main = do
    putStr   "Hello, "
    putStrLn "World."
```

### 演習1-2).

REPLから`hello.hs`を読み込み、`:main`コマンドにより実行して下さい。

その後、 `hello.hs` で表示される文言を自由に編集し、REPLからリロードをして下さい。 `:main` を実行し、編集内容が結果に反映されていることを確認して下さい。

### 演習1-3).

REPLのヘルプを表示させ、興味のある機能を自由に試してみて下さい。

----

## doブロックの決まり

* `IO ...` という型しか書けない
* `...` の部分が戻り値
* 戻り値は `<-` で受け取る
* [Prelude](https://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude.html#g:26) から書ける関数を探そう

```haskell
main = do
    cs <- readFile "pwd.hs"
    putStrLn cs
```

### 文法メモ

* `readFile` : ファイルを文字列(`String`)として読み込み

----

## turtle パッケージ

* Haskellでshell相当の関数を実装したもの
    * 今日から使える関数たくさん！
* 2行のおまじないで使える (以下はシェバング付き)

```haskell
#!/usr/bin/env stack
-- stack --resolver lts-7.0 --install-ghc runghc --package turtle

{-# LANGUAGE OverloadedStrings #-}
import Turtle
```

----

## doブロックの決まり その2

型の先頭に `MonadIO m =>` がある場合、最後が `IO ...` ではなく `m ...` でもよい。

* `MonadIO` は型クラスと呼ばれるもの
    * `MonadIO m =>` : `m` は任意の `MonadIO` のインスタンスでよい
    * `IO` は `MonadIO` のインスタンス
* [Turtle.Prelude](https://hackage.haskell.org/package/turtle-1.2.8/docs/Turtle-Prelude.html#g:1)から探そう

```haskell
main = do
    echo "Hello, World"
```

`MonadIO io => Text -> io ()` は `Text -> IO ()` と読みかえてよい。

### 文法メモ

* `echo` : 文字列(`Text`)の表示

----

## `=`の利用

`do`ブロック内では `let ... = ...` で変数宣言できる。

```haskell
main = do
    let greeting = "Hello, world!"
    echo greeting
```

----

## `<-`と`=`の違い

* どちらも束縛
* `=` はHaskellの言語仕様。`let`式、`where`句で使える
* `<-`は糖衣構文。`do`専用
* 右辺が `IO ...` なら `<-` と覚える

```haskell
main = do
    let title = "now: "
    now <- date
    putStrLn (title <> show now)
```

### 文法メモ

* `date` : 日付の取得
* `show` : 様々な型を文字列(`String`)へ
* `<>` : 文字列(`String`または`Text`)の連結 (See also: [Monoid](https://hackage.haskell.org/package/base/docs/Data-Monoid.html))

----

## `<-`と`=`の違い

完全に違うものなので使い分け必須。

脱糖すると、`<-`のおかげで`IO`型と`>>=`の特殊さが隠蔽されて普通の手続き型のように書けていることがわかる。

```haskell
main =
    let title = "now: " in
    date >>= \now ->
    putStrLn (title <> show now)
```

----

## `<-`と`=`の違い

さらに関数型っぽく整理。`<-` による束縛は、後続の関数の引数名となる。

```haskell
main = let title = "now: "
           f now = putStrLn (title <> show now)
        in date >>= f
```

実用上は脱糖は考えずに`do`記法のルールを体に覚えこませる。

### 文法メモ

* `let A in B` : 構文。Aで変数宣言する。Bが式の値となる

----

## `return`

* 糖衣構文をそれっぽく見せるために`return`と命名された
* ただの関数。関数から値を戻す効果はない
* `do`記法では`IO ...`しか書けない、という制約の回避に使うもの

```haskell
main = do
    title <- return "now: "
    now <- date
    putStrLn (title <> show now)
```

----

## `return`

`do` 式の最後(戻り値)も `IO ...` 型じゃなければならない。最後に`return`を使うとそれっぽくなる。

```haskell
dateStr = do
    let title = "now: "
    now <- date
    return (title <> show now)

main = do
    str <- dateStr
    putStrLn str
```

----

## 型

全ての式は型付けされている。型の確認にはREPLが便利。

```haskell
*Main> :t dateStr
dateStr :: IO String
*Main> :t main
main :: IO ()
```

----

## 文字列

* `"ABC" : String` 言語仕様にある文字列
* `"ABC" : Text` 効率のよい、外部ライブラリの文字列
* 変換は [Data.Text](https://hackage.haskell.org/package/text/docs/Data-Text.html) の `pack` と `unpack`

----

## 関数

関数の型は `->` を使って記述。カリー化されている(部分適用すると関数が返る)。

* `a -> b -> c -> d`
    * 引数が `a` `b` `c` の型の値3つ、戻り値が `d` 型
* `a -> (b -> c) -> IO d`
    * 引数が `a` `b -> c` の型の値2つ、戻り値が `IO d` 型 (副作用あり)

----

## その他の型

* 数値 : `Int` `Double`
* 真偽値 : `True :: Bool`, `Flase :: Bool`
* NULL、エラーの表現
    * `Just 1 :: Maybe Int`、`Nothing :: Maybe a`
    * `Right 1 :: Either e Int`、`Left "Some error" :: Either Str a`
* リスト : `[1,2,3] :: [Int]`
* タプル : `(1, True) :: (Int, Bool)`
* ファイル `FilePath`
* 日付 `UTCTime`
* 終了コード `ExitCode`

----

## printfとformat

[Turtle.Format](https://hackage.haskell.org/package/turtle-1.2.8/docs/Turtle-Format.html)
を利用。

* `format` と `printf`
* `s`や`d`や`f`と文字列`"..."`を`%`演算子でつなぐと`%d`っぽくなる

```haskell
*Main> printf ("My name is "%s%". "%d%" years old.\n") "shu1" 0
My name is shu1. 0 years old.
```

----

## 演習

### 演習2-1).

`hostname` でマシン名を取得し、 `echo` でそれを画面に出力する `hostname.hs` というプログラムを作って下さい。

【ヒント】REPLから`:l`で対象のファイルを読み込み、 `:r` で再コンパイルをするとすぐフィードバックが得られます。

### 演習2-2).

コマンドライン引数を1つ受け取り、その最終更新日を表示する `modified.hs` というプログラムを作って下さい。ただし簡単のため、不正な引数を受け取った場合の例外処理は考えないものとします。

必要に応じて以下の関数を使って下さい。

* `arguments` : コマンドライン引数を文字列(`Text`)のリストで取得する
* `head` : リストの先頭要素を取得する
* `fromText` : 文字列(`Text`)をファイル型にする
* `datefile` : ファイル型から最終更新日付を取得する
* `repr` : 日付を文字列(`Text`)にする

【ヒント】`arguments`と`datefile`は副作用のある関数で、`<-`で結果を受け取れます。一方、 `head`と`fromText`、`repr`は副作用のない関数であり、戻り値は `let ... = ...` で束縛します。

### 演習2-3).

コマンドライン引数を複数受け取り、その全てのファイル名と最終更新日を表示する `modified-multi.hs` というプログラムを作って下さい。簡単のため、例外処理は考えないものとします。

【ヒント】まず、引数を1つ受け取り、そのファイル名と更新日時を表示する `echoModified` という関数を定義して下さい。`echoModified fname = do ...` という形式で `main` 関数と同様の記述方法で定義することができます。ファイル名と更新日時を整形して表示するには、`printf` を使うといいでしょう。

`mapM` は、繰り返し処理を行う関数です。`mapM func リスト` という形式で呼び出すと、リストの各要素について`func`を呼び出します。この関数の結果は `IO ...` という型であるため、 `do` ブロックに記述することができます。

### 演習2-4).

次のように定義される `nestedIO` という値は、 `IO (IO ())` という型を持ちます。この値を使って、画面に `"Hello, I/O!"` を表示するプログラム `hello-io.hs` を作って下さい。

```haskell
nestedIO = do
    putStr "Hello, "
    return (putStrLn "I/O!")
```

### 演習2-5).

[Turtle.Prelude](https://hackage.haskell.org/package/turtle-1.2.8/docs/Turtle-Prelude.html#g:1) より、I/Oアクションとして使えそうな関数をピックアップして下さい。それらの関数を組み合わせてプログラムを作成し、実際の挙動を確認して下さい。

※ ファイルの上書きや削除型のコマンドで自分の環境を壊してしまわないよう、十分注意して下さい。

----

## ストリーム処理

UNIXのパイプ `|` の実現に、`IO ...`では役者不足。 `Shell ...` という `Turtle` が提供する型を用いる。

* `IO ...` : すべての結果を一度に返す
* `Shell ...` : 複数行の結果を1行ずつ返す

----

## 入力

UNIXコマンド的に書くと、 `入力 | パイプ | 出力` 。

入力 は `Shell ...` という型で表現される。

* 入力なし: `empty :: Shell a`
* 標準入力: `stdin :: Shell Text`
* ファイル: `input :: FilePath -> Shell Text`
* リストを入力とする: `select :: [a] -> Shell a`
* 文字列一行を入力に: `"INPUT" :: Shell Text`

----

## 出力

UNIXコマンド的に書くと、 `入力 | パイプ | 出力` 。

出力は `Shell ... -> IO ...` という型で表現される。最終的に `IO ...` になるので、`do` ブロックに書ける。

* 出力を捨てる: `sh :: Shell a -> IO ()`
* 出力を表示する: `view :: Shell a -> IO ()`
* 標準出力: `stdout, stderr :: Shell Text -> IO ()`
* ファイル: `output :: FilePath -> Shell Text -> IO () `
* 外部コマンドに流し込む: `shell :: Text -> Shell Text -> IO ExitCode`

----

## パイプ

UNIXコマンド的に書くと、 `入力 | パイプ | 出力` 。

パイプ( `|` と `|` の間) は `Shell ... -> Shell ...` という型で表現される。

* 何もしない: `id :: Shell a -> Shell a`
* 先頭n行取る: `limit :: Int -> Shell a -> Shell a`
* 外部コマンドを通す: `inshell :: Text -> Shell Text -> Shell Text`

----

## 関数適用による組み合わせ

入力、パイプ、出力は引数と関数の関係にあるため、単純に関数適用で組み合わせてよい。

* `input "your.txt" :: Shell Text`
* `limit 10 :: Shell a -> Shell a`
* `stdout :: Shell Text -> IO ()`

`stdout (limit 10 (input "your.txt"))`

`$` を使うとかっこを省略して記述でき、データが右から左に流れるように見える。

`stdout $ limit 10 $ input "your.txt"`

`&` を使うとこれを逆向きにできる。これがUNIXのパイプ的な記法。

`input "your.txt" & limit 10 & stdout`

----

## 組み合わせ例

REPLで試すことができる。

* 何もしない `empty & id & sh`
* 外部コマンドの利用 `input "sample.txt" & inshell "uniq" & shell "wc -l"`
* リストを流し込む `select ["To UNIX", "From Haskell"] & output "sample.txt"`

----

## 出力:fold

[Control.Foldl](https://hackage.haskell.org/package/foldl-1.2.1/docs/Control-Foldl.html) に定義された `Fold` を使い、`Stream ...`を回収できる。reduceみたいなもの。

* `fold   :: Shell a -> Fold     a r -> IO r`
* `foldIO :: Shell a -> FoldM IO a r -> IO r`

先頭で `import qualified Control.Foldl as Fold` のように `import` した上で使う。

`select [1..6] & (`foldIO` Fold.random)`

[wcの実装例](http://lpaste.net/155533) がすごく良い見本。

### 文法メモ

* `` `f` `` : 2引数関数 `f` を中置演算子として使う記法
* `(* 1) ` : 中置演算子 `*` の第二引数に`1` を部分適用 (セクション)

----

## パイプ:fmap

全ての行を関数によって変換する。リストに対する `map` に相当。

* `fmap :: (a -> b) -> Shell a -> Shell b`

`ls "." & fmap (format fp) & stdout`

----

## パイプ:grep

パターンと `Shell ...` をとり、grepされた`Shell ...`を作る。

* `grep :: Pattern a -> Shell Text -> Shell Text`

`select ["Haskell", "Turtle", "Shell"] & grep (plus dot <> "ll") & stdout`

正規表現との対応は [ドキュメント](https://hackage.haskell.org/package/turtle-1.2.8/docs/Turtle-Tutorial.html#g:14) を見る。

`has`、`prefix`、`suffix` を使うと簡単になる。

`select ["Haskell", "Turtle", "Shell"] & grep (suffix "ll") & stdout`

----

## `Shell ...`の`do`記法

`IO ...` と同様、 `Shell ...` でも `do` 記法が使える。

* 最終的な戻り値が `Shell ...` となっている関数のみ使える
* `do`ブロックの戻り値は `Shell ...`になる
* 上から順に実行されるのは同じだが、戻り値のループ処理となる

```haskell
lsPrintf = do -- Shell ... の do ブロック
    file <- ls "."
    -- 全ファイル分、ループ処理される
    printf (fp%"\n") file

main = do     -- IO    ... の do ブロック
    lsPrintf & sh
```

----

## `Shell ...`と`MonadIO`

* `Shell` は `MonadIO` のインスタンス
    * `MonadIO m => ... -> m ...` は `... -> Shell ...` と読み替えてよい
* `IO ...` の関数は使えない
    * `liftIO :: IO ... -> Shell ...`

```haskell
lsPrintf = do
    file <- ls "."
    liftIO $ print file

main = do
    lsPrintf & sh
```

----

## 演習

### 演習3-1).

外部コマンドを利用し、`./count-import.hs src` と実行すると `find src -name \*.hs | xargs grep '^import ' | wc -l` と同じ結果を出力する `count-import.hs` を作成して下さい。

【ヒント】`find` に引数を渡すには、 `format` で文字列を連結するか、`inshell`の代わりに`inproc`を使ってリストで引数を渡すといいでしょう。リストは `[x] <> ["hoge", "bar"]` のように連結できます。

### 演習3-2).

3-2で作成した `count-import.hs` を、外部コマンドを使わないように書き換えて下さい。

【ヒント】最初の`find` は `find` 関数で簡単に置き換えることができます。

最後の `wc -l` は、 `Control.Foldl` を `import` した上で、 `Fold.length` を使った畳み込みで置き換えられます。畳み込まれた数値を表示するため、最後に `view` へ流し込むといいでしょう。

`xargs` は、 `Shell ...` 型を `do` 記法で書くことで実現できます。次のような関数を作り、`Shell FilePath` から `<-` で値を取ると、`find`で見つかったすべてのファイルに対してループ処理させることができます。`grep`は `input` でファイルから読んだ各行を `grep` 関数でフィルタすることで実現できます。

```haskell
xargsGrep :: Shell FilePath -> Shell Text
xargsGrep files = do
    file <- files
    ...
```

### 演習3-3).

[Turtle.Prelude](https://hackage.haskell.org/package/turtle-1.2.8/docs/Turtle-Prelude.html#g:3) より、ストリーミングの入力、出力、パイプとして使えそうな関数をピックアップして下さい。それらの関数を組み合わせてプログラムを作成し、挙動を確認して下さい。

----

## 補講: Applicative

Applicative ファンクタ型クラス、と呼ばれるもの。

* `[...]`、`Maybe ...`、`IO ...`、`Shell ...`のようなコンテナライクなデータを指す
* 通常の関数を使った演算が可能なコンテナのこと
* 例えば、 `[1, 2] + [3, 4]` は？
* 2つの演算子
    * `(<*>) :: f (a -> b) -> f a -> f b`
    * `(<$>) :: (a -> b) -> f a -> f b`
    * え？ だからなに？

----

## Applicativeの使い方

原理はともかく、使い方を覚えるほうがよい。

* イディオム : `f` がn引数の関数のとき、 `f <$> x1 <*> x2 <*> x3 <*> x4 <*> ... <*> xn`
* 引数の数を間違えてなければ、絶対に型はあう
    * 気になる人は書き下すとよい

`(+)` は2引数関数。

```haskell
Prelude> (+) <$> [1, 2] <*> [3, 4]
[4,5,5,6]
```

----

## FoldはApplicative

`(,)`やコンストラクタに適用すると、一度に複数の方法を使って畳み込める。

```haskell
ghci> select [1..6] & (`fold` ((,) <$> Fold.minimum <*> Fold.maximum)) & view
(Just 1,Just 6)
```

----

## Applicativeとパーサー

`do`記法できる`IO ...`や`Shell ...`でも使われるが、特筆すべきはパーサーで使われることが多いこと。

* 例: `grep` で用いた `Pattern`
* パーサは「文字列の消費」と「結果の生成」からなる
* 「結果の生成」をApplicativeで演算
* `match :: Pattern a -> Text -> [a]` パースして結果を返す。

```haskell
ghci> match ((,) <$> "a" <> star dot <*> "d" <> star dot) "abcdefg"
[("abc","defg")]
```

----

## パーサの戻り値の差し替え

* `(*>) :: f a -> f b -> f b` 右のパーサの結果のみ使う
* `(<*) :: f a -> f b -> f a` 左のパーサの結果のみ使う
* `sed :: Pattern Text -> Shell Text -> Shell Text` 置換コマンド
* `pure` パースしない (結果のみ返す)

```haskell
ghci> "abcdefg" & sed ("abc" *> pure "xyz") & stdout
xyzdefg
ghci> "abcdefg" & sed ("abc" <* "def") & stdout
abcg
```

----

## コマンドライン引数のパーサ

[Turtle.Options](https://hackage.haskell.org/package/turtle/docs/Turtle-Options.html)でコマンドライン引数をパースできる。 [Options.Applicative](https://hackage.haskell.org/package/optparse-applicative-0.13.0.0/docs/Options-Applicative.html) がベース。

```haskell
import Turtle

parser :: Parser (Text, Maybe Int)
parser = subcommand "byopt" "オプション形式で指定する"
            ((,) <$> optText "opt1" 'x' "オプションひとつめ"
                 <*> optional (optInt  "opt2" 'y' "オプションふたつめ"))
         <|> (,) <$> argText "arg1" "引数ひとつめ"
                 <*> optional (argInt  "arg2" "引数ふたつめ")

main = do
    (str, mInt) <- options "An argument parser test" parser
    echo str
    case mInt of
      Nothing  -> return ()
      Just n -> printf d n
```

* `Parser ...` パーサ。`...`がパース結果
* `argXXX` : 普通の引数
* `optXXX` : `--XXX` 形式の引数
* `optional` : 省略可能な引数
* `subcommand` : サブコマンドの定義
* `func <$> n1 <*> n2 <*> n3` : パーサの組み立て ([Applicative](https://hackage.haskell.org/package/turtle-1.2.8/docs/Control.html#v:Applicative))
* `<|>` 引数のパースに失敗した場合の代替 ([Alteranative](https://hackage.haskell.org/package/base-4.8.2.0/docs/Control-Applicative.html#t:Alternative))
* `options` : 定義した `Parser ...` の適用

----

## 演習

### 演習4-1).

`Turtle.Options` を利用し、演習3-3 で作成した `count-import.hs` のコマンドライン引数を、`count-import.hs src` という形式ではなく、`count-import.hs --dir src` または `count-import.hs -d src` という形式で実行できるように変更して下さい。さらに引数の省略時に、カレントディレクトリ `.` を利用できるようにして下さい。変更することができたら、 `count-import.hs --help` でヘルプメッセージが表示されることを確認して下さい。

【ヒント】デフォルト値を用意する場合は、 `optional` を使って引数をオプショナルの扱いにします。これを使って定義したパーサが返す`Maybe Text` 型は、 `case ... of ...` 構文を使って場合分けすることができます。

```haskell
    let path = case maybePath of Nothing -> "."
                                 Just p  -> p
```

### 演習4-2).

`count-import.hs` に `--show (-s)` オプションを追加し、このオプションが指定された場合はカウントではなくモジュール名を表示するようにして下さい。

Haskell の `import` 文は `qualified` や `as` を伴い、 `import qualified Mod as Foo (x,y)` となることがあります。`sed` 関数を使い、余分な部分を削除してモジュール名 `Mod` のみを表示させましょう。

【ヒント】ON/OFFできるオプションは `switch` によって作成することができます。 `--show` の指定によって動作を分けるには、2つの出力関数 `wc` と `dump` と、`sink :: Shell Text -> IO ()` なる変数を用意し、指定によって中身を切り替えればいいでしょう。`wc`が今までの挙動を実現するための関数です。

```haskell
let sink  = if isShow then dump else wc
```

`dump` の実装には `sed` を使います。モジュール名までをパースできるよう `Pattern` を作成し、 `*>` を用いてモジュール名だけを返せるようにします。全体を `prefix` で修飾しておけば、`import` 構文の `as ...` の部分は捨てられるので明示的にパースする必要はありません。