こんにちは: 私は最近 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」は意味がありません。)