2

私は本Functional programming in Scala、特に単純な Stream トレイトとコンパニオン オブジェクトを実装するセクションをフォローしています。参考までに、これまでのコンパニオン オブジェクトの内容を以下に示します。

object Stream {
  def empty[A]: Stream[A] =
    new Stream[A] {
      def uncons = None
    }

  def cons[A](hd: => A, tl: => Stream[A]): Stream[A] =
    new Stream[A] {
      lazy val uncons = Some((hd, tl))
    }

  def apply[A](as: A*): Stream[A] =
    if (as.isEmpty)
      empty
    else
      cons(as.head, apply(as.tail: _*))
}

そしてこれまでの特徴:

trait Stream[A] {
  import Stream._

  def uncons: Option[(A, Stream[A])]

  def toList: List[A] = uncons match {
    case None => Nil: List[A]
    case Some((a, as)) => a :: as.toList
  }

  def #::(a: => A) = cons(a, this)

  def take(n: Int): Stream[A] =
    if (n <= 0)
      empty
    else (
      uncons
      map { case (a, as) => a #:: (as take (n - 1)) }
      getOrElse empty
    )
}

次の演習では、の実装を作成する必要がありtakeWhile、次のようにするとよいと思いました。

  def takeWhile(f: A => Boolean): Stream[A] = (
    uncons
    map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty }
    getOrElse empty
  )

残念ながら、追跡できない分散エラーが発生したようです。

error: type mismatch;  found   : Stream[_2] where type _2 <: A 
required: Stream[A]
Note: _2 <: A, but trait Stream is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
           getOrElse empty
           ^

バリアンス アノテーションを追加することもできますが、その前に、ここで何が問題なのかを理解したいと思います。助言がありますか?

4

2 に答える 2

2

これは、部分式の型を明示的に指定すると機能するため、型推論の問題のようですuncons map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty }

def takeWhile(f: A => Boolean): Stream[A] = {
  val mapped:Option[Stream[A]] = uncons map {
    case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty
  }
  mapped getOrElse empty
}
于 2012-08-16T09:30:30.550 に答える
1

他の答えを少し完成させるには、empty次の行で:

map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty }

は と推論されますempty[Nothing]。つまり、(a #:: (as takeWhile f)) else emptyは と推論されStream[Foo <: A]、 aStream[A]が期待され、Stream不変であるため、エラーが発生します。

したがって、これはこれを修正するための最もクリーンな方法を提供します: ただ注釈を付けemptyます:

map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty[A] }

そして、それはうまくコンパイルされます。

Stream共変であるため、これはオリジナルでは発生しません。したがって、実際Stream.emptyに aになりたいStream[Nothing]( Nilis a のようにList[Nothing]) か、気にしないかのいずれかです。

さて、それがempty[Nothing]でなくであると推論される正確な理由についてはempty[A]、これはおそらく SLS 6.26.4 の「ローカル型推論」のどこかに隠されていますが、この部分は読みやすいと非難することはできません...

原則として、メソッドを呼び出すときは常に疑ってください:

  • 推測する唯一の方法が期待される戻り値の型である型パラメーターを持つもの (通常は引数がないため)、
  • 同時に、期待される戻り値の型は、それ自体が別の場所から推測されることになっています。
于 2012-08-22T18:11:57.357 に答える