Skip to content

Instantly share code, notes, and snippets.

@halcat0x15a
Last active March 19, 2024 03:30
Show Gist options
  • Save halcat0x15a/6102460 to your computer and use it in GitHub Desktop.
Save halcat0x15a/6102460 to your computer and use it in GitHub Desktop.
Pipe

Pipeモナドの紹介

Scalaの記事です。Haskellはあまり書けません。

Iterateeの複雑さから開放されたいのでPipe系ライブラリ使いましょうという記事です。

Iterateeとの比較や簡単な使い方についてつらつらと書いていきます。

Iterateeについて

Iterateeを複雑にしているものは何か?

ひとつは状態の多さであると考えられます。

IterateeはInputをとって、Iterateeを返すような手続きとみなせます。

この時、Inputには

  • El
  • Empty
  • EOF

の三つの状態があり、場合分けしてそれぞれの処理を記述します。

さらに、Iteratee自体にも

  • Cont
  • Done
  • Error

の三つの状態があり(Errorがない実装もある)、Iterateeを走査する時には場合分けをしなければなりません。

PlayのIterateeではパターンマッチやPartialFunctionで場合分けを記述しますが、ScalazのIterateeではcatamorphismによりそれぞれの処理を記述しています。

いずれにせよ、場合分けというものが煩雑になり易いということは変わりありません。

また、Iterateeの複雑さの権化であると考えられるのがEnumerateeです。

trait Enumeratee[From, To] {
  def apply[A](inner: Iteratee[To, A]): Iteratee[From, Iteratee[To, A]] 
}

これはIterateeを処理するIterateeであり、モナディックなEnumeratorでもあります。

Enumeratee一つ定義するだけでも、Inputを処理してIterateeを返す手続きをとってInputを処理してIterateeを返す手続きを返す手続きを定義する必要がある場合がほとんどなのでなかなかつらいです。

Pipeモナドについて

この記事で指すPipe系ライブラリというのは

  • Haskell
    • pipes
    • conduit
    • machines
  • Scala
    • scala-machines
    • scalaz-stream

のことであり、Pipeモナドというのは構成要素として

  • await -- 入力ストリームから値を取り出す
  • yield -- 出力ストリームへ値を送る

を持つものとします。

今回参考にする実装はscalaz-streamですが、Pipeモナドの本質はどれも変わりありません。

入出力の抽象化

val x = readLine
val y = readLine
println(x + y)

これは標準入力から2つ取り出して、連結したものを標準出力へ送っています。

Pipeはこの手続きを抽象化します。

import scalaz.stream._, Process._

val concat = for {
  x <- await1[String]
  y <- await1[String]
} yield x + y

入力列を与えてみましょう。

import scalaz._, Scalaz._

(Process("foo", "bar", "baz") |> concat toList) assert_=== List("foobar")

これではあまり面白くないですね。

実際にIOを利用しましょう。

(io.linesR("build.sbt") |>
  concat |>
  process1.utf8Encode to
  io.chunkW(System.out)).run
  .run
  • ファイルの読み込み
  • 二行読み込んで連結
  • バイト列へ変換
  • 標準出力へ出力

といったことをしています。

手続きの組み立てと合成

Pipe系ライブラリの大きな特徴は入出力手続きをモナドにより組み立てられるということです。

先のconcatの例では、

入力を2つとって連結し、出力する

ということを抽象化しており、入力元と出力先は自由に決めることができます。

さらに、この手続きは合成することが可能です。

IOを使った例ではconcatした後にprocess1.utf8Encodeで文字列をバイト列に変換しています。

このようにPipe系ライブラリでは入出力手続きをパイプのように連結していくことができます。

Iterateeとの比較

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment