YAMAMOTO Yuji (山本悠滋)
2025-06-15 関数型まつり 2025
Haskell以前における純粋関数型言語の代表、Miranda最新版(2020年リリース‼️)のマニュアル曰く…
read :: [char]->[char]
など
Report on the Programming Language Haskell Version 1.0より:
Haskell’s I/O system is based on the view that a program communicates to the outside world via streams of messages:
Report on the Programming Language Haskell Version 1.0より:
Response
のリスト(ストリーム)を受け取って、Request
のリスト(ストリーム)を返す関数
Request
: 外の世界に送る命令Response
:
外の世界から渡されるRequest
の結果Request
に応じて引数のResponse
が変わるという、直感に反する挙動
Response
を受け取ってRequest
を返す」だけの純粋な関数に収まっている
data Request =
ReadFile String
| WriteFile String String
| ReadChan String
| AppendChan String String
| GetEnv String
| SetEnv String String
| ...
data Response =
Success
| Str String
| Failure IOError
| ...
Dialogue
を使ったプログラムの例を紹介しますDialogue
を使って書いたコードの例
😩引数でパターンマッチした種類のResponse
と、対応するRequest
の順番が一致してないとダメ!
⏩使いにくいので継続渡しによるラッパーが
⏩継続渡しって?
⏩継続渡しって?
比較的身近な例: JavaScriptのコールバックを受け取る関数
// Promiseも立派な継続渡し
// then メソッドが継続を受け取る
readFile(filePath).then((contents) => {
console.log(contents);
});
⏩継続渡しに変換すると型はどう変わる?
// 継続渡しじゃない普通の関数(型定義はTypeScriptの構文)
foo(input: number): string;
// その継続渡しバージョン
foo(input: number, k: (output: string) => void): void
先程のプログラムをcontinuation basedで書き換えたもの
main =
appendChan stdout "please type a filename\n" exit (
readChan stdin exit (\userInput ->
let (name : _) = lines userInput in
appendChan stdout name exit (
readFile name
(\ioError ->
appendChan stdout
"can't open file" exit done)
(\contents ->
appendChan stdout contents exit done))))
[Response] -> [Request]
な関数Report on the Programming Language Haskell Version 1.3より:
The I/O system in Haskell is purely functional, yet has all of the expressive power found in conventional programming languages. To achieve this, Haskell uses a monad to integrate I/O operations into a purely functional context.
Dialogue
型はなくなり、代わりにIO
という型が登場先程のプログラムを現代のHaskellに(ほぼ)直訳したもの
main = do
putStrLn "please type a filename\n"
userInput <- hGetContents stdin
let (name : _) = lines userInput
putStrLn name
contents <- readFile name
putStrLn contents
do
ブロックの中で利用できる
IO
は純粋な関数?IO
を列挙する際 do
ブロックで囲う必要があったり、<-
を使うことIO
の正体: それでも純粋っぽい部分 (2)RealWorld
を受け取ってRealWorld
(と結果となる値)を返す関数をラップしたオブジェクト
ただし!直接実行する機能がない。普通の関数のように呼び出すことができない
IO
の正体: それでも純粋っぽい部分 (3)>>=
bind
」IO
とIO
を返す関数を渡すと、それぞれを繋げた新しいIO
ができるIO
の正体: それでも純粋っぽい部分 (4)IO
を繋げて「組み立てる」だけ
IO
を繋げた結果もまたIO
になるので、IO
を使っている箇所はみんなIO
型の関数になる>>=
の例1: IO
同士を繋げるdo
記法で分かりやすくしたバージョン:
やっぱりこれはダメ:
>>=
の例2:
純粋な関数の中からIO
を呼んでIO
にする (2)その中でIO
を呼ぶ
>>=
の例2:
純粋な関数の中からIO
を呼んでIO
にする (3)do
記法で分かりやすくしたバージョン:
IO
を繋げて「組み立てる」だけ? (1)それを言ったら、他の言語も「命令を列挙している」だけでは?
// JavaScript風の擬似コード
function main() {
print("please type a filename");
const userInput = getInput();
const name = userInput.split("\n")[0];
print(name);
const contents = readFile(name);
print(contents);
}
IO
を繋げて「組み立てる」だけ? (2)>>=
で繋げたIO
も、結局はコンパイラーなどを通じて実行する(副作用を起こす)プログラムになるIO
を実際に使用するときの体験は他のプログラミング言語で入出力をするときとほぼ同じ<-
を使う、など構文の細かい違いがあったり、IO
は常にファーストクラスオブジェクト」なのでその分少し機能が多いが…
IO
を実際に使用するときの体験は他のプログラミング言語で入出力をするときとほぼ同じIO
を実行できるし、
IO
はファーストクラスオブジェクトであり、IO
を実行する箇所とそうでない箇所をマークして区別できる言語Space, Right Arrow or swipe left to move to next slide, click help below for more details