【问题标题】:Is it possible to pattern match on a by-name parameter without evaluating it?是否可以在不评估的情况下对按名称参数进行模式匹配?
【发布时间】:2020-12-03 16:19:09
【问题描述】:

正在玩下面的惰性结构流

import Stream._

sealed trait Stream[+A] {
    ..
    def toList: List[A] = this match {
        case Empty => Nil
        case Cons(h, t) => println(s"${h()}::t().toList"); h()::t().toList
    }
    def foldRight[B](z: B) (f: ( A, => B) => B) : B = this match {
        case Empty => println(s"foldRight of Empty return $z"); z
        case Cons(h, t) => println(s"f(${h()}, t().foldRight(z)(f))"); f(h(), t().foldRight(z)(f))
    }
    ..
}
case object Empty extends Stream[Nothing]
case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]

object Stream {

    def cons[A](h: => A, t: => Stream[A]): Stream[A] = {
        lazy val hd = h
        lazy val tl = t
        Cons[A](() => hd, () => tl)
    }

    def empty[A]: Stream[A] = Empty

    def apply[A](la: A*): Stream[A] = la match {
        case list if list.isEmpty => empty[A]
        case _ => cons(la.head, apply(la.tail:_*))
    }

}

对于一个函数 takeWhile 通过 foldRight 我最初写道:

def takeWhileFoldRight_0(p: A => Boolean) : Stream[A] = {
        foldRight(empty[A]) {
                case (a, b) if p(a) => println(s"takeWhileFoldRight cons($a, b) with p(a) returns: cons($a, b)"); cons(a, b)
                case (a, b) if !p(a) => println(s"takeWhileFoldRight cons($a, b) with !p(a) returns: empty[A]"); empty[A]
            }
        }

当被称为:

Stream(4,5,6).takeWhileFoldRight_0(_%2 == 0).toList

导致以下跟踪:

f(4, t().foldRight(z)(f))
f(5, t().foldRight(z)(f))
f(6, t().foldRight(z)(f))
foldRight of Empty return Empty
takeWhileFoldRight cons(6, b) with p(a) returns: cons(6, b)
takeWhileFoldRight cons(5, b) with !p(a) returns: empty[A]
takeWhileFoldRight cons(4, b) with p(a) returns: cons(4, b)
4::t().toList
res2: List[Int] = List(4)

然后又问又问,我想可能是模式匹配中的 unapply 方法急切地求值。

所以我改成

def takeWhileFoldRight(p: A => Boolean) : Stream[A] = {
        foldRight(empty[A]) { (a, b) =>
            if (p(a)) cons(a, b) else empty[A]
        }
    }

当被称为

Stream(4,5,6).takeWhileFoldRight(_%2 == 0).toList

导致以下跟踪:

f(4, t().foldRight(z)(f))
4::t().toList
f(5, t().foldRight(z)(f))
res1: List[Int] = List(4)

因此我的问题是:

在使用按名称参数时,有没有办法恢复模式匹配的威力?

不同的情况下,我匹配按名称的参数而不急切地评估它们?

或者我必须去一组丑陋的嵌套“如果”:p 在那种情况下

【问题讨论】:

    标签: scala purely-functional


    【解决方案1】:

    仔细看看这个片段:

        def toList: List[A] = this match {
            case Empty => Nil
            case Cons(h, t) => println(s"${h()}::t().toList"); h()::t().toList
        }
        def foldRight[B](z: B) (f: ( A, => B) => B) : B = this match {
            case Empty => println(s"foldRight of Empty return $z"); z
            case Cons(h, t) => println(s"f(${h()}, t().foldRight(z)(f))"); f(h(), t().foldRight(z)(f))
        }
        ..
    }
    

    Cons 中的 ht 不会由 unapply 评估 - 毕竟 unapply 返回 () => X 函数而不调用它们。但你做了。每场比赛两次 - 一次用于打印,一次用于传递结果。而且您不记得结果,因此任何未来的折叠、地图等都会重新评估该函数。

    根据您想要的行为,您应该:

    1. 在匹配结果后立即计算结果:
    case Cons(h, t) =>
      val hResult = h()
      val tResult = t()
      println(s"${hResult}::tail.toList")
      hResult :: tResult.toList
    

    1. 不要使用case class,因为它无法记忆结果,您可能需要记忆它:
    class Cons[A](fHead: () => A, fTail: () => Stream[A]) extends Stream[A] {
      lazy val head: A = fHead()
      lazy val tail: Stream[A] = fTail()
      // also override: toString, equals, hashCode, ...
    }
    object Cons {
      def apply[A](head: => A, tail: => Stream[A]): Stream[A] =
        new Cons(() => head, () => tail)
      def unapply[A](stream: Stream[A]): Option[(A, Stream[A])] = stream match {
        case cons: Cons[A] => Some((cons.head, cons.tail)) // matches on type, doesn't use unapply
        case _             => None
      }
    }
    
    1. 如果你明白你在做什么,你也可以创建一个 case class 覆盖 applyunapply(如上),但这几乎总是一个信号,你不应该在第一名(因为toStringequalshashCode 等很可能会有无意义的实现)。

    【讨论】:

    • 嗯,我有些不同意你回答的前提。缺点是两个无参数函数。那些不是按名称的参数。所以我在 Cons 的情况下匹配函数。我只在需要评估它们时才给它们打电话。我只调用 h() 进行打印,这没关系,因为它会被热切地评估。这只是为了玩弄和理解行为。
    • 问题不在于按预期工作的 Cons。它与 (a,b) 在 take while 中,它是一个产品并且没有 thunk 参数因此评估它们
    • 所以我想解决它的方法是在 foldRight 的 f 函数中使用相同的技巧。而不是 =>B 我应该使用 ()=>B
    • 既不是h()也不是t() memoize。所以h(); h() 将评估 head 两次。如果您将println 放在那里,您将println 两次。 unapply 只是从函数中提取值,它不会将函数与非函数区分开来,以便调用它们。 case classes 不支持 lazy vals,所以底线是你要么重新计算并需要小心以避免重复计算,要么你不应该首先使用案例类,让自己记住。
    • 您对 h 的看法是对的,但我知道并且是故意这样做的。除此之外,我不知道你是否意识到 foldRight 根本不是这里的问题。问题出在两个版本的 takeWhilefoldRight 之间。一种在参数上使用模式匹配,另一种使用 if 并且它有所作为.....这就是问题所在......
    猜你喜欢
    • 2014-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-14
    • 1970-01-01
    • 1970-01-01
    • 2012-01-03
    • 2020-09-11
    相关资源
    最近更新 更多