2017-12-31 202 views
1

私はいくつかのジェネリック、怠惰、そして暗黙のうちに触れていて、壁に当たっていました。ジェネリック型の境界にしか関係していないと確信していますが(私は間違っているかもしれません...)ストリームを構築しようとしていました様の事:バウンドバリアントジェネリックはAnyになりますか?

object MyStream { 
    def empty = new MyStream[Nothing] { 
    def isEmpty = true 
    def head = throw new NoSuchElementException("tead of empty MyStream") 
    def tail = throw new NoSuchElementException("tail of empty MyStream") 
    } 

    def cons[T, U >: T](h: U, t: => MyStream[T]): MyStream[U] = new MyStream[U] { 
    def isEmpty = false 
    def head = h 
    lazy val tail = t 
    } 

    implicit class MyStreamOps[T](t: => MyStream[T]) { 
    def #:: [U >: T](h: U): MyStream[U] = 
     cons(h, t) 
    } 
} 

abstract class MyStream[+T] { 
    def isEmpty: Boolean 
    def head: T 
    def tail: MyStream[T] 
    @tailrec final def foreach(op: T => Unit): Unit = { 
    if (!isEmpty) { 
     op(head) 
     tail.foreach(op) 
    } 
    } 
} 

実際に(少なくとも限り私のテストを行っているようなので、私は他の問題を欠落している可能性があります)、一つのことを除いて、かなりうまく動作しているようです。その1つは、私がconsと#:behaviorsで使用した境界で、すべてのMyStreamがMyStream [Any]に縮退していることです。

しかし、私は素朴なジェネリックで行く場合:

def cons[T](h: T, t: => MyStream[T]): MyStream[T] = new MyStream[T] ... 

それはMyStreamだから、型が安定したまま、私は短所/#を使用することはできません:: MyStream.emptyには何も添付する[ナッシング]、また、これらの操作を使用するときに、私の型に他のバリエーションを持たせることはできません。

私は、Martin OderskyがList内の分散の文脈で与える例とかなり密接に従っていると思っていましたが、ここでの唯一の違いは私のcons /#演算の静的な性質です私は、少なくとも私には不可能だ「怠惰なこの」(概念的を持つことができるとは思わないようがesssentialあると信じてた!

何が不足しているのですか?

答えて

1

を私はいくつかのポイントを持っていますまず第一に、特許請求の範囲は、特許請求の範囲第

それは私がconsと#:: behaviorsで使用した境界を持つことですが、すべてのMyStreamはMyStream [Any]に縮退します。

は実際にはtrueではありません。あなたはこのlive demoであなた自身を見ることができます。 ssGoodは、キャストの必要なしにタイプssGood2に簡単に割り当てられ、MyStream[Any]と明示的に入力されたssBadではどうやって行うことができないのか注意してください。ここでのポイントは、このシナリオではScalaコンパイラが型を正しく取得できることです。私は容疑者 Intellij IDEAは間違ったタイプを推測し、ハイライトなどを悪用しているということです。IDEAはコードを複雑にすると独自のコンパイラを使用します。場合によっては、コードが正しいかどうかを実際にコンパイルする必要があります。

第2のクレームについての第2のクレームも、私には間違っています。

しかし、私は素朴なジェネリックで行く場合:

def cons[T](h: T, t: => MyStream[T]): MyStream[T] = new MyStream[T] ...

タイプは安定したまま、私は短所/#を使用することはできません:: MyStreamには何も添付します。空の ...私は、次のコード(available online

object MyStream { 
    val empty: MyStream[Nothing] = new MyStream[Nothing] { 
    override def isEmpty = true 

    override def head = throw new NoSuchElementException("tead of empty MyStream") 

    override def tail = throw new NoSuchElementException("tail of empty MyStream") 
    } 

    def cons[T](h: T, t: => MyStream[T]): MyStream[T] = new MyStream[T] { 
    def isEmpty = false 

    def head = h 

    lazy val tail = t 
    } 

    implicit class MyStreamOps[T](t: => MyStream[T]) { 
    def #::(h: T): MyStream[T] = cons(h, t) 
    } 

} 

abstract class MyStream[+T] { 
    def isEmpty: Boolean 

    def head: T 

    def tail: MyStream[T] 

    @tailrec final def foreach(op: T => Unit): Unit = { 
    if (!isEmpty) { 
     op(head) 
     tail.foreach(op) 
    } 
    } 
} 

import MyStream._ 

val ss0 = 1 #:: empty 
val ss1: MyStream[Int] = ss0 
val ss2: MyStream[Int] = 1 #:: empty 

を使用する場合

それがコンパイルされていれば MyStream[+T]宣言に[+T]があるとして私のために[OK]を実行します。そして今度は、あなたが間違って何をしているのか分かりません(実際のコンパイラエラーを提供していないので、推測が難しいです)。

また、emptyが非ジェネリックで不変の場合は、defである必要はありません。valでもかまいません。

まだ問題が発生している場合は、その再生方法とエラーの詳細をお伝えください。


更新(コメントするに答える)

トビーは、申し訳ありませんが、私はまだあなたの問題#2を理解していません。あなたの質問やコメントとしてコンパイルされないコードの例を教えてください。

私の唯一の推測は何を意味することは、あなたがメインの答えのようにただ1つの汎用Tでコードを使用している場合、このようなコードの一部に障害が発生したということであるということである。

def test() = { 
    import MyStream._ 

    val ss0: MyStream[String] = "abc" #:: empty 
    val sb = new StringBuilder 
    val ss1: MyStream[CharSequence] = ss0       //OK 
    val ss2: MyStream[CharSequence] = cons(sb, ss0)    //OK 
    val ss3: MyStream[CharSequence] = sb #:: ss0     //Bad? 
} 

はい、これは本当ですAFAIUは、暗黙のラッパーをチェックするときにScalaコンパイラがすべてのジェネリック型のすべてのポーズ可能な置換を通過しようとせず、最も特定のものだけを使用するためです。したがってss0MyStreamOps[String]に変換されますが、MyStreamOps[CharSequence]には変換されません。この問題を解決するには、別の汎用タイプU >: TMyStreamOps#::に追加する必要がありますが、consに追加する必要はありません。したがって、次のMyStream定義

object MyStream { 
    val empty: MyStream[Nothing] = new MyStream[Nothing] { 
    override def isEmpty = true 

    override def head = throw new NoSuchElementException("tead of empty MyStream") 

    override def tail = throw new NoSuchElementException("tail of empty MyStream") 
    } 

    def cons[T](h: T, t: => MyStream[T]): MyStream[T] = new MyStream[T] { 
    def isEmpty = false 

    def head = h 

    lazy val tail = t 
    } 

    implicit class MyStreamOps[T](t: => MyStream[T]) { 
    //def #::(h: T): MyStream[T] = cons(h, t) // bad 
    def #::[U >: T](h: U): MyStream[U] = cons(h, t) //good 
    } 
} 

でさえss3は(さえUのない正確+T動作するため、コンパイルconsを使用し、ss2)エラーなしでコンパイル。

+0

オンポイント1では、魅力的です。私はこれについて正しい正当な礼儀が何であるかは分かりませんが、これに対処するための "答え"を追加します。 ポイント2では、私は本当にミスピークをしました - 私はしばらくの間2つの問題で戦っていました、そして第2は私の心の中で失われました。何が壊れているのは空の使用ではありませんが、 "子"クラスのストリームを作成し、それに "親"クラスを追加しようとすると失敗します。 + Tの部分が "親のストリームに代入可能"を扱うので、+ Tの部分は扱いますが、それはU>:T境界のことであり、#::がその場合に必要な親の型のストリームを返すことができる。 –

+0

@TobyEggitt、申し訳ありませんが、私はまだあなたの問題#2を理解していません。ここでコンパイルされないコードの例を教えてください。私の答えは更新の詳細を参照してください。 – SergGr

+0

まあ、私はここでそれをフォーマットしますが、この使用を考慮することはできません。 ヴァル・P =新しい親( "P")=新CHILD1 ヴァル・C1( "C1") ヴァルPS = P#:: MyStreamを。空白 val pc1 = c1#:: MyStream.empty val px = p#:: pc1 //これ! –

0

上記の私の最初の指摘は、IntelliJコンパイラのバグを反映していると思われます。上記のSergGrの答えは、問題が見えないと指摘しています。そして、確かに、同じコードをコマンドラインでコンパイルすると、完全に動作します。しかし、これはIntelliJのが私を示しものです:

enter image description here

私はIntelliJのワークシート機能は(文法的に間違っていたリファクタリングを示唆してある時に)いくつかの「問題」を持っていることをすでに気づいたが、これはあります初めて "実際のコンパイラ"のセクションでそれが失敗するのを見たことがあります。

FWIW、これはIntelliJ 2017.3.2 CEです。Open JDK 1.8.0で動作しているようです(私はそこに置かなかった - 私はJava 9 for Javaの作業を使用しています)。それはIntelliJバンドルJVM)とScala 2.11.6です。

関連する問題