PureScriptで標準入力

## TL;DR

- PureScriptで標準入力を扱うコードが想像以上にめんどかった
- 書いたコードは https://github.com/goldarn-ring/how-to-stdin-purescript

## はじめに

[PureScriptの公式サイト](http://www.purescript.org/)には[Pursuit](https://pursuit.purescript.org/)という非常に便利なライブラリ検索エンジンがあります。
例えば、文字列の長さを求める関数を調べたいとしましょう。 `strlen`だっけ`length`だっけ`size`だっけ・・・？　何にせよ型は `String -> Int` なはず。そんなとき`String->Int`をそのまま入れると・・・

https://pursuit.purescript.org/search?q=String+-%3E+Int

はい出てきました！ `length`ですね！こんな感じで型を入れると、どのライブラリのどの関数かを教えてくれるのです。Haskellで言うところのHoogleみたいな検索エンジンです。

## あれ・・・？　無いぞ・・・？

ある日のこと、ある関数を探そうと`Effect String`で検索したんですよ。どんな関数か分かりますか？　`Effect`はHaskellで言うところの`IO`に相当する型です。そうですね、Haskellで言うところの`getLine :: IO String`みたいな関数を探していたんですよ。標準入力から一行読み込む関数です。Hoogleでは当然ヒットします。

https://www.haskell.org/hoogle/?hoogle=%3A%3A+IO+String

ですが、Pursuitでは・・・

https://pursuit.purescript.org/search?q=Effect+String

最初にヒットするのは`cwd`ですね。名前からしてカレントディレクトリを返す関数でしょう。確かに`Effect String`ですね。`getLine`とか`readLine`みたいな名前の関数は・・・あれれー・・・？ purescript-line-readerというライブラリの `readLine :: forall eff m. MonadAff (readline :: READLINE | eff) m => MonadAsk Interface m => m String` という関数は見つかりますが、型を見れば（PureScriptをお使いの方は）お分かりのとおり、PureScript 0.11時代の関数なので、できれば使いたくないやつです。

我らが求める `readLine :: Effect String` は・・・。まさか自分で実装するしか無いのか！？

## Node.jsを調べよう！

PureScriptはJavaScriptにコンパイルされる言語（いわゆるAltJS）です。Node.jsで標準入力を扱うコードがどんなものか調べれば、大いに参考になるはずです。というわけでNode.jsのドキュメントを見てみましょう。

https://nodejs.org/api/readline.html

readlineというライブラリの`createInterface`関数で入出力インターフェースのオブジェクトを作り、一行入力されたときに呼ばれるコールバックを設定することで入力を読み取ることができるようです。これを使って、名前を聞いてアイサツするコードを書くと以下のようになりました。

```javascript
const readline = require('readline')

function readLine() {
  const reader = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  })
  reader.setPrompt('> ')
  reader.prompt()
  return new Promise((resolve, reject) => {
    reader.on('line', (line) => {
      resolve(line)
      reader.close()
    })
  })
}

async function main() {
  console.log('Hello. What is your name?')
  const name = await readLine()
  console.log('Hello, ' + name)
}

main()
```

このreadLine関数をPureScriptを使って書き直せば・・・

## PureScriptで実装しよう！

PureScriptでは、前述のreadlineのバインディングが公式に提供されています。

https://pursuit.purescript.org/packages/purescript-node-readline
（現時点でPursitに上がっているドキュメントは古いので注意。GitHubの方のコードを見たほうがいいかもしれません）

このライブラリを使えば前掲のJavaScriptで標準入力を扱うコードをPureScriptに書き直せます。
というわけで書き直したのが以下のコードです！

```haskell
import Prelude
import Data.Either (Either(..))
import Node.ReadLine (createConsoleInterface, noCompletion, setPrompt, prompt, setLineHandler, close)
import Effect.Aff (Aff, Canceler, makeAff, launchAff_, effectCanceler)
import Effect (Effect)

readLine :: Aff String
readLine = makeAff handler
  where
    handler :: (Either Error String -> Effect Unit) -> Effect Canceler
    handler next = do
      interface <- createConsoleInterface noCompletion
      setPrompt "> " 2 interface
      prompt interface
      setLineHandler interface \str -> do
        close interface
        next $ Right str
      pure $ effectCanceler $ close interface
```

コールバックが`resolve`と`reject`の二つではなく`Either`を受け取る一つだけとか、おそらくCtrl+Cを押したときに呼ばれる`Canceler`を返しているだとか、細かな違いはありますが、やっていることはもとのJavaScriptのコードと概ね同じです。

非同期処理なので`Effect`ではなく`Aff`を使うことになってしまいましたが、これで標準入力から一行読み込む関数という当初の目的は達成されました。

## FFIを使おう！

もっと簡単な方法はないものでしょうか？　実はFFIを使えばもっと簡単にできます！

FFI（Foreign Function Interface）とはJavaScriptで書かれたライブラリをPureScriptから呼び出す仕組みです。もっと簡単に標準入力を扱えるNode.jsのライブラリを見つけて、それをFFIを使って呼び出せばいいのです。

というわけで、このreadline-syncというライブラリを使いましょう。
https://www.npmjs.com/package/readline-sync

このライブラリを使うと、たったこれだけのコードでこれまで書いてきた`readLine`と同じことができるのです。

```javascript
var readlineSync = require('readline-sync')

exports.readLineSync = function () {
  return readlineSync.question('> ')
}
```

そしてこの`readLineSync`をPureScriptから呼び出せるようにするには、これだけのコードでOKです！

```haskell
foreign import readLineSync :: Effect String
```

最後に`readLine`と`readLineSync`を使って、名前を聞いてアイサツするコードを書いてみましょう。`readLine`の方は`Aff`を返すので、使うためには`launchAff_`や`liftEffect`を使って方を合わせる必要がありますが、`readLineSync`の方はその必要もなく素直に呼び出せています。


```haskell
main :: Effect Unit
main = do
  askNameSync
  askNameAsync

askNameAsync :: Effect Unit
askNameAsync = launchAff_ do
  liftEffect $ log "(Async) Hello. What is your name?" 
  name <- readLine
  liftEffect $ log $ "Hello, " <> name

askNameSync :: Effect Unit
askNameSync = do
  log "(Sync) Hello. What is your name?"
  name <- readLineSync
  log $ "Hello, " <> name
```

## コード

冒頭にも貼りましたが、最後に書いたコードへのリンクを張っておきます。コードはご自由にお使いください。

https://github.com/goldarn-ring/how-to-stdin-purescript