3

こんにちは: 私は最近 Scala を学んでいます (私の関連するバックグラウンドは主に C++ テンプレートです)、Scala について現在理解していないことに出くわし、気が狂いそうになっています。:(

(また、これは StackOverflow への私の最初の投稿であり、本当に素晴らしい Scala の人々のほとんどがたむろしているように見えることに気づいたので、メカニズムでひどくばかげたことをした場合は本当に申し訳ありません。)

私の特定の混乱は、暗黙の引数バインディングに関連しています。暗黙の引数がバインドを拒否する特定のケースを考え出しましたが、一見同一のセマンティクスを持つ関数はバインドを拒否します。

もちろん、これはコンパイラのバグかもしれませんが、私が Scala を使い始めたばかりであることを考えると、何らかの深刻なバグに遭遇する可能性は十分に小さいので、誰かが私の間違いを説明してくれることを期待しています。;P

私はコードを調べて、動作しない単一の例を思い付くためにかなり削りました。残念ながら、問題は一般化でのみ発生するように見えるため、この例はまだかなり複雑です。:(

1) 思ったように動かない単純化されたコード

import HList.::

trait HApplyOps {
    implicit def runNil
        (input :HNil)
        (context :Object)
        :HNil
    = {
        HNil()
    }

    implicit def runAll[Input <:HList, Output <:HList]
        (input :Int::Input)
        (context :Object)
        (implicit run :Input=>Object=>Output)
        :Int::Output
    = {
        HCons(0, run(input.tail)(context))
    }

    def runAny[Input <:HList, Output <:HList]
        (input :Input)
        (context :Object)
        (implicit run :Input=>Object=>Output)
        :Output
    = {
        run(input)(context)
    }
}

sealed trait HList

final case class HCons[Head, Tail <:HList]
    (head :Head, tail :Tail)
    extends HList
{
    def ::[Value](value :Value) = HCons(value, this)
}

final case class HNil()
    extends HList
{
    def ::[Value](value :Value) = HCons(value, this)
}

object HList extends HApplyOps {
    type ::[Head, Tail <:HList] = HCons[Head, Tail]
}

class Test {
    def main(args :Array[String]) {
        HList.runAny(   HNil())(null) // yay! ;P
        HList.runAny(0::HNil())(null) // fail :(
    }
}

Scala 2.9.0.1 でコンパイルされたこのコードは、次のエラーを返します。

broken1.scala:53: error: No implicit view available from HCons[Int,HNil] => (java.lang.Object) => Output.
        HList.runAny(0::HNil())(null)

この場合の私の期待は、runAllへの暗黙のrun引数にバインドされることrunAnyです。

ここで、runAll2 つの引数を直接受け取る代わりに、それらの 2 つの引数を受け取る関数を返すように変更すると (他の人のコードで見たように、試してみようと思ったトリック)、動作します。

2) 同じ実行時の動作を持ち、実際に動作する変更されたコード

    implicit def runAll[Input <:HList, Output <:HList]
        (implicit run :Input=>Object=>Output)
        :Int::Input=>Object=>Int::Output
    = {
        input =>
        context =>
        HCons(0, run(input.tail)(context))
    }

本質的に、私の質問は次のとおりです。なぜこれが機能するのですか? ;( 私は、これら 2 つの関数が全体的に同じ型シグネチャを持つことを期待しています。

1: [Input <:HList, Output <:HList] (Int::Input)(Object):Int::Output
2: [Input <:Hlist, Output <:HList] :Int::Input=>Object=>Int::Output

問題を理解するのに役立つ場合は、他のいくつかの変更も「機能」します (ただし、これらは関数のセマンティクスを変更するため、使用可能なソリューションではありません)。

3) runAllOutput を HNil に置き換えて、第 2 レベルのみをハードコーディングする

    implicit def runAll[Input <:HList, Output <:HList]
        (input :Int::Input)
        (context :Object)
        (implicit run :Input=>Object=>HNil)
        :Int::HNil
    = {
        HCons(0, run(input.tail)(context))
    }

4) 暗黙の関数からコンテキスト引数を削除する

trait HApplyOps {
    implicit def runNil
        (input :HNil)
        :HNil   
    = {
        HNil()  
    }

    implicit def runAll[Input <:HList, Output <:HList]
        (input :Int::Input)
        (implicit run :Input=>Output)
        :Int::Output
    = {
        HCons(0, run(input.tail))
    }

    def runAny[Input <:HList, Output <:HList]
        (input :Input) 
        (context :Object)
        (implicit run :Input=>Output)
        :Output 
    = {
        run(input)
    }
}

これについて誰かが持っているかもしれない説明は大歓迎です。:(

(現在、私の最善の推測では、他の引数に対する暗黙の引数の順序が、私が見逃している重要な要因であるということですが、私が混乱しrunAnyているのは、最後にも暗黙の引数があるため、明らかな「implicit def末尾でうまく機能しないimplicit」は意味がありません。)

4

2 に答える 2

6

あなたがこのように宣言するときimplicit def

implicit def makeStr(i: Int): String = i.toString

次に、コンパイラはFunctionこの定義から暗黙のオブジェクトを自動的に作成し、暗黙の型Int => Stringが期待される場所にそれを挿入します。これはあなたのラインで起こることHList.runAny(HNil())(null)です。

implicit defただし、 (メソッドのように)暗黙のパラメーターを受け入れるsを定義すると、コンパイラーはメソッドが暗黙を必要とするオブジェクトをrunAll作成できないため、機能しなくなります。そのような暗黙がで使用可能であるという保証ははるかに少なくなります。サイトを呼び出します。Functionapply

runAllこれに対する解決策は、次の代わりに次のようなものを定義することです。

implicit def runAllFct[Input <: HList, Output <: HList]
    (implicit run: Input => Object => Output):
        Int :: Input => Object => Int :: Output =
  { input: Int :: Input =>
    context: Object =>
      HCons(0, run(input.tail)(context))
  }

Functionコンパイラはからオブジェクトを作成する必要がないため、この定義はもう少し明確ですが、def代わりにを直接呼び出して必要な関数オブジェクトを取得します。defそして、それを呼び出している間、それはすぐに解決することができる必要な暗黙のパラメータを自動的に挿入します。

私の意見では、このタイプの暗黙の関数を期待するときはいつでも、implicit def実際にオブジェクトを返すを提供する必要がありFunctionます。Function(他のユーザーは同意しないかもしれません…誰か?)コンパイラがラッパーを作成できるという事実は、implicit def主に、より自然な構文で暗黙の変換をサポートするためにあると思います変換。implicit defIntString

于 2011-05-30T09:43:57.723 に答える
3

(注:これは、この質問に関する別の回答のコメントセクションでおそらくより詳細に行われた議論の要約です。)

ここでの問題implicitパラメーターがの先頭にrunAnyないことですが、暗黙のバインディング メカニズムがそれを無視しているためではありません。代わりに、問題は、型パラメーターOutputが何にもバインドされておらず、型パラメーターから間接的に推論する必要があることです。run「遅すぎる」発生している暗黙的なパラメーターのタイプ。

本質的に、「未決定の型パラメーター」のコード (Outputこの状況にあるもの) は、問題のメソッドが直接のパラメーター リストによって決定される「暗黙的」であると見なされる状況でのみ使用されます。 、runAnyのパラメーター リストは実際には単なる (input :Input)であり、「暗黙的」ではありません。

そのため、 の型パラメーターはInputなんとか機能します ( に設定されInt::HNilます) が、Output単に に設定されNothing、これが「くっついて」run引数の型を にしますInt::HNil=>Object=>Nothing。これは では満足できず、 の型推論が失敗し、失格となりrunNilます。runAnyへの暗黙の引数として使用しますrunAll

変更したコード サンプル #2 で行ったようにパラメーターを再編成することで、runAnyそれ自体を "暗黙的" にし、残りの引数を適用する前に型パラメーターを完全に決定できるようにします。これは、暗黙的な引数が最初にrunNil(またはrunAny2 つ以上のレベルの場合)、その戻り値の型は取得/バインドされます。

未解決の問題を解決するには: コード サンプル #3 がこの状況で機能した理由は、Outputパラメーターが必要でさえなかったからです: それがバインドされていたという事実は、Nothingそれを何かにバインドしたり、使用したりするその後の試みには影響しませんでした。それは何でも、そのバージョンの暗黙的なパラメーターrunNilにバインドするために簡単に選択されました。run

最後に、コード サンプル #4 は実際には縮退しており、「機能する」と見なされるべきではありませんでした (適切な出力が生成されたことではなく、コンパイルされたことを確認しただけです)。その暗黙的なパラメーターのデータ型は非常に単純化されていました。 ( Input=>Output、ここでInputOutputは実際には同じ型であることが意図されていました) にバインドされるだけでしたconforms:<:<[Input,Output]: この状況で ID として機能する関数。

(#4のケースの詳細については、この明らかにデッドオン関連の質問を参照してください:私のメソッドと Predef の適合の間の暗黙のあいまいさの問題。)

于 2011-05-31T11:08:31.090 に答える