Scala 模式匹配与无限流

Scala 模式匹配与无限流,scala,pattern-matching,lazy-evaluation,Scala,Pattern Matching,Lazy Evaluation,所以,我正在努力自学Scala,我一直在玩的东西之一就是流类。我试着用一个简单的翻译来解释汉明数问题: object LazyHammingBad { private def merge(a: Stream[BigInt], b: Stream[BigInt]): Stream[BigInt] = (a, b) match { case (x #:: xs, y #:: ys) => if (x < y) x #:: merge(xs, b)

所以,我正在努力自学Scala,我一直在玩的东西之一就是
类。我试着用一个简单的翻译来解释汉明数问题:

object LazyHammingBad {
  private def merge(a: Stream[BigInt], b: Stream[BigInt]): Stream[BigInt] =
    (a, b) match {
      case (x #:: xs, y #:: ys) =>
        if (x < y) x #:: merge(xs, b)
        else if (y < x) y #:: merge(a, ys)
        else x #:: merge(xs, ys)
    }

  val numbers: Stream[BigInt] =
    1 #:: merge(numbers map { _ * 2 },
      merge(numbers map { _ * 3 }, numbers map { _ * 5 }))
}
我决定看看其他人是否使用Haskell方法解决了Scala中的问题,并根据Rosetta代码进行了修改:

object LazyHammingGood {
  private def merge(a: Stream[BigInt], b: Stream[BigInt]): Stream[BigInt] =
    if (a.head < b.head) a.head #:: merge(a.tail, b)
    else if (b.head < a.head) b.head #:: merge(a, b.tail)
    else a.head #:: merge(a.tail, b.tail)

  val numbers: Stream[BigInt] = 
    1 #:: merge(numbers map {_ * 2}, 
            merge(numbers map {_ * 3}, numbers map {_ * 5}))
}
objectlazyhamminggood{
私有def合并(a:Stream[BigInt],b:Stream[BigInt]):Stream[BigInt]=
如果(a.head
这一个工作得很好,但我仍然想知道我在《懒汉》(LazyHammingBad)中是如何出错的。使用
::
解构
x::xs
是否出于某种原因强制计算
xs
?有什么方法可以安全地对无限流使用模式匹配,或者如果你不想事情爆炸,你只需要使用
head
tail

匹配{case x#:::xs=>…
val(x,xs)=(a.head,a.tail)差不多
。因此,坏版本和好版本的区别在于,在坏版本中,您在一开始就调用
a.tail
b.tail
,而不是仅仅使用它们来构建结果流的尾部。此外,当您在
的右侧使用它们时:
(不是模式匹配,而是生成结果,如
#::merge(a.b.tail)
您实际上并没有调用merge,这将在以后访问返回流的尾部时完成。因此,在好的版本中,对merge的调用根本不调用
tail
。在坏的版本中,它一开始就调用它

现在,如果你考虑数字,甚至是一个简化的版本,比如说“代码>1×:::合并(数字,另一个流)< /代码>,当你调用它的代码<代码>尾/代码>时(如<代码>(10),

merge
必须进行计算。您在
numbers
上调用
tail
,它调用
merge
,以
numbers
为参数,调用
tails
,调用
merge
,调用
tail

相比之下,在超级懒惰的Haskell中,当您进行模式匹配时,它几乎不做任何工作。当您执行x:xs的案例l时,它将计算
l
,只需知道它是空列表还是空列表。 如果它确实是一个缺点,
x
xs
将作为两个thunk提供,这两个函数最终将允许访问内容。Scala中最接近的等效功能是只测试
空的


还要注意的是,在Scala流中,
尾部
是惰性的,
头部
不是惰性的流,头部必须是已知的。这意味着当你得到流的尾部时,必须计算流本身,它的头部,即原始流的第二个元素。这有时是有问题的,但在你的例子中,你甚至在到达那里之前就失败了。

注意,你可以通过定义一个更好的模式来做你想做的事情
流的cher

这里有一点我刚刚整理了一下:

对象汉明测试{
//一种方便的流模式匹配对象
对象#:::{
类尾包装器[+A](s:Stream[A]){
def unwrap=s.tail
}
对象尾包装器{
隐式def unwrap[A](包装:TailWrapper[A])=包装。展开
}
def取消应用[A](s:Stream[A]):选项[(A,TailWrapper[A])]={
如果没有
否则{
部分(头部、新尾罩)
}
}
}
def merge(a:Stream[BigInt],b:Stream[BigInt]):Stream[BigInt]=
(a,b)匹配{
大小写(x::xs,y::ys)=>
如果(x合并:(a:Stream[BigInt],b:Stream[BigInt])Stream[BigInt]
延迟val编号:流[BigInt]=
1::合并(数字映射{{u*2},合并(数字映射{u*3},数字映射{u*5}))
//>数字:流[BigInt]=
numbers.take(10).toList/>res0:List[BigInt]=List(1,2,3,4,5,6,8,9,10,12)
}
现在,您只需要确保Scala在进行模式匹配时找到您的
对象:
,而不是
Stream.class
中的对象。为了方便起见,最好使用不同的名称,如
。:
,然后记住在进行模式匹配时始终使用该名称


如果您需要匹配空流,请使用
case stream.empty
。使用
case stream()
将尝试在模式匹配中评估您的整个流,这将导致悲伤。

我使用的是
lazy val解决方案:List[Move]=pathsToGoal match{case(\uu,moveHistory)#::=>moveHistory.reverse case=>List.empty[Move]}
这不会计算tail。是因为我使用了o吗?在本例中,pathsToGoal是一个无限流
object LazyHammingGood {
  private def merge(a: Stream[BigInt], b: Stream[BigInt]): Stream[BigInt] =
    if (a.head < b.head) a.head #:: merge(a.tail, b)
    else if (b.head < a.head) b.head #:: merge(a, b.tail)
    else a.head #:: merge(a.tail, b.tail)

  val numbers: Stream[BigInt] = 
    1 #:: merge(numbers map {_ * 2}, 
            merge(numbers map {_ * 3}, numbers map {_ * 5}))
}
object HammingTest {
  // A convenience object for stream pattern matching
  object #:: {
    class TailWrapper[+A](s: Stream[A]) {
      def unwrap = s.tail
    }
    object TailWrapper {
      implicit def unwrap[A](wrapped: TailWrapper[A]) = wrapped.unwrap
    }
    def unapply[A](s: Stream[A]): Option[(A, TailWrapper[A])] = {
      if (s.isEmpty) None
      else {
        Some(s.head, new TailWrapper(s))
      }
    }
  }

  def merge(a: Stream[BigInt], b: Stream[BigInt]): Stream[BigInt] =
    (a, b) match {
      case (x #:: xs, y #:: ys) =>
        if (x < y) x #:: merge(xs, b)
        else if (y < x) y #:: merge(a, ys)
        else x #:: merge(xs, ys)
    }                                             //> merge: (a: Stream[BigInt], b: Stream[BigInt])Stream[BigInt]

  lazy val numbers: Stream[BigInt] =
    1 #:: merge(numbers map { _ * 2 }, merge(numbers map { _ * 3 }, numbers map { _ * 5 }))
                                                  //> numbers  : Stream[BigInt] = <lazy>
  numbers.take(10).toList                         //> res0: List[BigInt] = List(1, 2, 3, 4, 5, 6, 8, 9, 10, 12)
}