我有以下简单的测试用例:
trait Printable[T] {
val string: String
def print() = println("It works: " + string)
}
def foo[T](x: T)(implicit ev: T => Printable[T]) = {
x.print()
}
implicit def doubleToPrint(x: Double): Printable[Double] = new Printable[Double] {
override val string: String = x.toString
}
foo(2.0) // => It works: 2.0
但是,创建一个应该调用bar[T](x: T)
的新函数foo(x)
会引发一个错误,即T => Printable[T]
没有隐式视图:
// this does not work
def bar[T](x: T) = {
println("Some other stuff")
foo(x)
}
然而,当添加相同的参数时,它的作用是:
// this does work
def bar[T](x: T)(implicit ev: T => Printable[T]) = {
println("Some other stuff")
foo(x)
}
在我看来,继续链接这些隐式参数非常麻烦,因为我假设foo
函数将开始搜索隐式转换,而不是函数bar
调用{{1 }}。据我所知,foo
也应该在implicit def doubleToPrint
的范围内进行foo
。
我在俯瞰什么?
回答dth的答案的其他问题
所以我理解答案,这实际上非常符合逻辑。
但是,上下文边界的解决方案在foo
和foo
都是另一个特征的一部分的情况下不起作用,因为特征中不允许上下文边界:
bar
那么也可以在不使用隐式参数的情况下解决这个问题吗?
我自己的,不太好的解决方案
我得出结论,我实际上并不需要特定代码中的特征,可以用// not allowed to do trait FooBar[T : Printable]
trait FooBar[T] {
def foo(x: T)(implicit ev: T => Printable[T]) = {
println("Running foo...")
x.print()
}
// this is not allowed
def bar(x: T) = {
println("But running bar first...")
foo(x)
}
}
替换FooBar[T]
特征。
这是我的问题的解决方案,但不是我添加的其他问题。
答案 0 :(得分:3)
想想隐式参数如何工作:
当您在某处调用需要隐式参数的方法时,编译器会在上下文(范围,涉及的类型)中查找合适的隐式参数。
现在看看你对bar的定义:
def bar[T](x: T) = {
foo(x)
}
当你调用foo时,编译器会查找它找不到的T => Printable[T]
类型的隐式值。没关系,你稍后会用Double类型调用bar,因为它不知道。
所以答案是肯定的,你必须在任何地方传递隐式参数,你在上下文中找不到合适的值(所以通常在任何地方,你都不知道具体的类型)。
然而,存在称为上下文边界的语法糖,因此您可以像这样定义条:
def bar[T: Printable](x: T) = foo(x)
你也可以像这样定义foo并使用implicitly[Printable[T]]
来访问这些值。因此,在这样的简单设置中,您根本不必关心隐式参数。
上下文边界只是隐式参数的语法糖。在您定义类型绑定的位置传递隐式值。即如果它是方法的类型参数,则将其传递给方法。
特征可能没有任何构造函数参数,因为线性化不会导致它不会在继承层次结构中结束。因此它也可能没有任何隐式参数或类型边界。
所以你真的必须为这两个方法添加一个隐含参数。 如果你正在实现一些复杂的API,这很好,如果在用户代码中出现很多,也许你可以改变你的设计。
您可以对类的类型参数使用上下文边界,但隐式参数不会直接在该类的方法中使用。要实现这一点,您必须提供如下的本地隐式转换:
class FooBar[T: Printable] {
implicit def conv(x: T) = implicitly[Printable[T]]
def foo(x: T) = {
println("Running foo...")
x.print()
}
def bar(x: T) = {
println("But running bar first...")
foo(x)
}
}
还有视图边界作为替代。然而,他们已被弃用。如果你使用-Xfuture运行scalac,它会告诉你。
答案 1 :(得分:1)
此处的问题是T
类型
意味着没有
(implicit ev: T => Printable[T])
编译器尝试查找适用于任何类型的T
的隐式
他不能,因为Double
只有隐含的。
但是如果你添加
(implicit ev: T => Printable[T])
编译器尝试在您调用bar的位置找到隐式。
如果您使用Double
参数调用它,则需要doubleToPrint
并将其传递给。