Можно ли сопоставить шаблон с параметром по имени без его оценки?

Играл с Lazy Structure Stream, как показано ниже

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)

Затем задавая вопросы и задавая вопросы, я понял, что, возможно, это был метод неприменения в сопоставлении с образцом, который оценивает с нетерпением.

Поэтому я изменился на

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)

Отсюда мой вопрос:

Есть ли способ восстановить силу сопоставления с образцом при работе с параметром по имени?

Другими словами, случай, когда я сопоставляю параметры по имени, не оценивая их с нетерпением?

Или мне нужно перейти к набору уродливых вложенных if :p в таком сценарии


person MaatDeamon    schedule 03.12.2020    source источник


Ответы (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))
    }
    ..
}

Здесь h и t в Cons не оцениваются unapply - ведь unapply возвращает () => X функции, не вызывая их. Но ты делаешь. Дважды за каждое совпадение - один раз для печати и один раз для передачи результата дальше. И вы не помните результат, поэтому любая будущая складка, карта и т. Д. Будут оценивать функцию заново.

В зависимости от того, какое поведение вы хотите иметь, вы должны:

  1. Вычислите результаты один раз, сразу после их сопоставления:
case Cons(h, t) =>
  val hResult = h()
  val tResult = t()
  println(s"${hResult}::tail.toList")
  hResult :: tResult.toList

or

  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 с переопределенными apply и unapply (как выше), но это почти всегда сигнал о том, что вам не следует использовать case-класс в первую очередь (потому что, скорее всего, toString, equals , hashCode и т. д. будут иметь бессмысленную реализацию).
person Mateusz Kubuszok    schedule 03.12.2020
comment
Хм, я уже как-то не согласен с предпосылкой вашего ответа. Минусы в том, что две функции без аргументов. Те не поименные параметры. Итак, я сопоставляю функции в случае с Cons. Я звоню им только тогда, когда мне нужно их оценить. Я вызываю h() только для печати, и это нормально, потому что он будет оценен с нетерпением. И это только для игры и понимания поведения. - person MaatDeamon; 03.12.2020
comment
Проблема не в минусах, которые работают по назначению. Это с (a, b) во время выполнения, которое является продуктом и не имеет параметра thunk, поэтому оценивайте их - person MaatDeamon; 03.12.2020
comment
Поэтому я думаю, что способ решить эту проблему — использовать тот же трюк в функции f foldRight. Вместо =›B Я ДОЛЖЕН ИСПОЛЬЗОВАТЬ ()=›B - person MaatDeamon; 03.12.2020
comment
Нет не получится к сожалению - person MaatDeamon; 03.12.2020
comment
Ни h(), ни t() не запоминают. Таким образом, h(); h() будет оценивать голову дважды. Если вы поставите println, вы получите println дважды. unapply просто извлекает значения из функции, он не отличает функции от не-функций, чтобы вызывать их. case classes не поддерживает lazy vals, поэтому суть в том, что вы либо пересчитываете и требуете некоторой осторожности, чтобы избежать двойного вычисления, либо вам не следует использовать case class в первую очередь, чтобы позволить себе запоминать. - person Mateusz Kubuszok; 03.12.2020
comment
Вы правы насчет h, но я знаю и сделал это нарочно. Кроме того, я не знаю, понимаете ли вы, что foldRight здесь принципиально не проблема. Проблема заключается в двух версиях takeWhilefoldRight. Один использует сопоставление с образцом для параметра, а другой использует, если и это имеет значение..... вот где был вопрос.... - person MaatDeamon; 03.12.2020
comment
Но готовясь между ними, я думаю, ваш ответ - нет, вы не можете восстановить здесь, потому что пара - это продукт, который является классом случаев, который с нетерпением оценивается. - person MaatDeamon; 03.12.2020
comment
Так что не могу использовать трюк, если продукт для быстрой оценки - person MaatDeamon; 03.12.2020
comment
Возможное решение scala-archive.org/scala-Lazy-tuple-td1990380 .html - person MaatDeamon; 03.12.2020