Scala隐含参数需要重复吗?

时间:2016-02-04 13:51:03

标签: scala implicit

我有以下简单的测试用例:

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的答案的其他问题

所以我理解答案,这实际上非常符合逻辑。 但是,上下文边界的解决方案在foofoo都是另一个特征的一部分的情况下不起作用,因为特征中不允许上下文边界:

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]特征。

这是我的问题的解决方案,但不是我添加的其他问题。

2 个答案:

答案 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并将其传递给。