Coutts らの Stream Fusion 論文からこの Stream タイプをエンコードすることに興味があります。私は Scala でのストリーム フュージョンを調査しており、GHC の書き換え規則の代わりにマクロを使用しようとしています。
data Stream a = ∃s. Stream (s → Step a s) s
data Step a s = Done
| Yield a s
| Skip s
私はいくつかの異なるアプローチを試しましたが、S の両方の出現が同じ型を参照するように、Scala で Stream の型をエンコードする方法がわかりません。Step 型を次のように簡単に記述しました。
sealed abstract class Step[+A, +S]
case object Done extends Step[Nothing, Nothing]
case class Yield[A, S](a: A, s: S) extends Step[A, S]
case class Skip[S](s: S) extends Step[Nothing, S]
これまでのところ、このタイプは正しいようです。Yield を受け取って Done または Step を返しても、型 A => A の関数が機能するように、共分散を使用しました。Haskell と同じように。
私のこだわりは、Stream の特徴です。私はそれを単なるケースクラスとして定義しようとしています。これまでに機能した唯一の署名は、Exists 型演算子と Tuple を使用して、以下のように両方のコンポーネントで型 S の等価性を維持することです。
type Exists[P[_]] = P[T] forSome { type T }
case class Stream[A](t: Exists[({ type L[S] = (S => Step[A, S], S)})#L])
タプルが不要になるようにエンコードする方法はありますか? Haskell に近いもの (存在演算子を想定) は次のとおりです。
case class Stream(∃ S. f: S => Step[A, S], s: S)
各メンバーは個別のフィールドにすることができます。
これを SML Module/Functor スタイルで次のようにエンコードできることも思い浮かびます。
trait Stream[A] {
type S <: AnyRef
val f: S => Step[A, S]
val s: S
}
object Stream {
def apply[A, S1 <: AnyRef](next: S1 => Step[A, S1], st: S1): Stream[A] = new Stream[A] {
type S = S1
val f = next
val s = st
}
def unapply[A](s: Stream[A]): Option[(s.f.type, s.s.type)] = Some(s.f, s.s)
}
しかし、これはもう少し複雑です。私が知らない、より明確な方法が存在することを望んでいました。また、このパスを探索しようとしたときに、AnyRef バウンドを追加するなど、コンパイラを満足させるためにいくつかのことを行う必要がありましたが、unapply メソッドは機能しません。scalac からのこのエラー メッセージで:
scala> res2 match { case Stream(next, s) => (next, s) }
<console>:12: error: error during expansion of this match (this is a scalac bug).
The underlying error was: type mismatch;
found : Option[(<unapply-selector>.f.type, <unapply-selector>.s.type)]
required: Option[(s.f.type, s.s.type)]
res2 match { case Stream(next, s) => (next, s) }
^