Scala隐式参数需要重复吗?
Scala implicit parameters need to be repeated?
我有以下简单的测试用例:
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
但是,创建一个应该调用 foo(x)
的新函数 bar[T](x: T)
会引发错误,即 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
调用 foo
。据我了解,当从 foo
.
中调用时,implicit def doubleToPrint
也应该在 foo
的范围内
我忽略了什么?
回复dth
的补充问题
所以我明白了答案,这实际上是非常合乎逻辑的。
但是,上下文边界的解决方案在 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)
}
}
那么不使用隐式参数也可以解决这个问题吗?
我自己的,不太好,解决方案
我得出的结论是我的特定代码中实际上不需要特征,可以用 abstract class FooBar[T <% Printable[T]]
.
替换 FooBar[T]
特征
这是我的问题的解决方案,但不是我添加的附加问题。
想一想,隐式参数是如何工作的:
当您在某处调用需要隐式参数的方法时,编译器会在上下文(作用域、涉及的类型)中查找合适的隐式参数。
现在看看你对 bar 的定义:
def bar[T](x: T) = {
foo(x)
}
当您调用 foo 时,编译器会查找它找不到的 T => Printable[T]
类型的隐式值。没关系,稍后您将使用 Double 类型调用 bar,因为它不知道。
所以答案是肯定的,你必须到处传递隐式参数,你在上下文中找不到合适的值(所以通常到处都是,你不知道具体类型)。
但是有一个叫做上下文边界的语法糖,所以你可以这样定义 bar:
def bar[T: Printable](x: T) = foo(x)
您也可以像这样定义 foo 并使用 implicitly[Printable[T]]
访问这些值。因此,在这样简单的设置中,您完全不必担心隐式参数。
特质
上下文边界只是隐式参数的语法糖。隐式值在您定义类型绑定的位置传递。 IE。如果它是方法的类型参数,则将其传递给方法。
特征可能没有任何构造函数参数,因为由于线性化,您不知道它在继承层次结构中的最终位置。因此它可能没有任何隐式参数或类型界限。
所以你真的必须为这两个方法添加一个隐式参数。
如果你正在实现一些复杂的 API 这很好,如果这在用户代码中出现很多,也许你可以改变你的设计。
类
您可以为 classes 的类型参数使用上下文边界,但是隐式参数不会直接在 class 的方法内部使用。为此,您必须像这样提供本地隐式转换:
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 它会告诉你。
这里的问题是类型T
。
意思是,没有
(implicit ev: T => Printable[T])
编译器试图找到适用于任何类型 T
的隐式
他不能,因为只有 Double
.
有隐式
但是如果你加上
(implicit ev: T => Printable[T])
编译器试图在你调用 bar 的地方找到隐含的。
如果你用 Double
参数调用它,他会接受 doubleToPrint
并传递给它。
我有以下简单的测试用例:
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
但是,创建一个应该调用 foo(x)
的新函数 bar[T](x: T)
会引发错误,即 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
调用 foo
。据我了解,当从 foo
.
implicit def doubleToPrint
也应该在 foo
的范围内
我忽略了什么?
回复dth
的补充问题所以我明白了答案,这实际上是非常合乎逻辑的。
但是,上下文边界的解决方案在 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)
}
}
那么不使用隐式参数也可以解决这个问题吗?
我自己的,不太好,解决方案
我得出的结论是我的特定代码中实际上不需要特征,可以用 abstract class FooBar[T <% Printable[T]]
.
FooBar[T]
特征
这是我的问题的解决方案,但不是我添加的附加问题。
想一想,隐式参数是如何工作的:
当您在某处调用需要隐式参数的方法时,编译器会在上下文(作用域、涉及的类型)中查找合适的隐式参数。
现在看看你对 bar 的定义:
def bar[T](x: T) = {
foo(x)
}
当您调用 foo 时,编译器会查找它找不到的 T => Printable[T]
类型的隐式值。没关系,稍后您将使用 Double 类型调用 bar,因为它不知道。
所以答案是肯定的,你必须到处传递隐式参数,你在上下文中找不到合适的值(所以通常到处都是,你不知道具体类型)。
但是有一个叫做上下文边界的语法糖,所以你可以这样定义 bar:
def bar[T: Printable](x: T) = foo(x)
您也可以像这样定义 foo 并使用 implicitly[Printable[T]]
访问这些值。因此,在这样简单的设置中,您完全不必担心隐式参数。
特质
上下文边界只是隐式参数的语法糖。隐式值在您定义类型绑定的位置传递。 IE。如果它是方法的类型参数,则将其传递给方法。
特征可能没有任何构造函数参数,因为由于线性化,您不知道它在继承层次结构中的最终位置。因此它可能没有任何隐式参数或类型界限。
所以你真的必须为这两个方法添加一个隐式参数。 如果你正在实现一些复杂的 API 这很好,如果这在用户代码中出现很多,也许你可以改变你的设计。
类
您可以为 classes 的类型参数使用上下文边界,但是隐式参数不会直接在 class 的方法内部使用。为此,您必须像这样提供本地隐式转换:
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 它会告诉你。
这里的问题是类型T
。
意思是,没有
(implicit ev: T => Printable[T])
编译器试图找到适用于任何类型 T
的隐式
他不能,因为只有 Double
.
但是如果你加上
(implicit ev: T => Printable[T])
编译器试图在你调用 bar 的地方找到隐含的。
如果你用 Double
参数调用它,他会接受 doubleToPrint
并传递给它。