【问题标题】:Correct encoding of this existential type in Scala?Scala中这种存在类型的正确编码?
【发布时间】:2013-04-13 11:41:55
【问题描述】:

我有兴趣从 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

我尝试了几种不同的方法,但我不确定如何在 Scala 中对 Stream 的类型进行编码,以使两个 S 的出现都引用相同的类型。我已经轻松地将 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 模块/函子风格对其进行编码:

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) }
                    ^

【问题讨论】:

  • case class Stream[A](t: (S =&gt; Step[A, S], S) forSome { type S }) 不起作用吗?
  • @TravisBrown 是的,但是 jroesch 正在尝试消除元组。
  • “unapply-selector”错误是issues.scala-lang.org/browse/SI-6130
  • “unapply-selector”错误是否有人正在积极处理(我唯一看到的是几个月前 Adriaan 的分支)?
  • 你为什么把一个存在符号放入通用符号所属的 Haskell 中?

标签: scala types stream scalaz existential-type


【解决方案1】:

首先,Step 对我来说很完美。至于Stream,我认为您在抽象类型方面走在了正确的轨道上。这是我想出的(包括 Coutts 论文第 2.1 节中剩余方法的实现):

abstract class Stream[A] {
  protected type S
  def next: S => Step[A, S]
  def state: S

  def map[B](f: A => B): Stream[B] = {
    val next: S => Step[B, S] = this.next(_) match {
      case Done        => Done
      case Skip(s)     => Skip(s)
      case Yield(a, s) => Yield(f(a), s)
    }
    Stream(next, state)
  }

  def unstream: List[A] = {
    def unfold(s: S): List[A] = next(s) match {
      case Done        => List.empty
      case Skip(s)     => unfold(s)
      case Yield(a, s) => a :: unfold(s)
    }
    unfold(state)
  }
}

object Stream {
  def apply[A, S0](n: S0 => Step[A, S0], s: S0) = new Stream[A] {
    type S = S0
    val next = n
    val state = s
  }

  def apply[A](as: List[A]): Stream[A] = {
    val next: List[A] => Step[A, List[A]] = {
      case a :: as => Yield(a, as)
      case Nil     => Done
    }
    Stream(next, as)
  }

  def unapply[A](s: Stream[A]): Option[(s.S => Step[A, s.S], s.S)] =
    Some((s.next, s.state))
}

需要注意的几点:

  • 我的unapply 有一个依赖方法类型:它依赖于s.S。我认为这可能是你的绊脚石。
  • unstream 中的 unfold 方法不是尾递归的。

我自己还不是很清楚,为什么S 存在/隐藏/其他很重要。如果不是,你可以写:

case class Stream[A, S](next: S => Step[A, S], state: S)

...但我认为这是有原因的。话虽如此,我也不确定这种方法是否真的以您想要的方式隐藏S。但这是我的故事,我会坚持下去。

【讨论】:

  • 我同意抽象类型是最有前途的方法。
  • 那么我将遵循抽象类型成员的方法,我仍然对编码存在时的最佳方法感到困惑。 @mergeconflict 感谢您指出我在依赖类型上的绊脚石,我看到了更清晰的解决方案。
  • 另外,隐藏类型是为了使相同类型的流无论其内部状态/生产者如何都可以等效,在这方面我认为它足够好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-23
  • 2011-06-26
  • 1970-01-01
  • 2017-09-18
  • 1970-01-01
  • 1970-01-01
  • 2018-08-13
相关资源
最近更新 更多