在Scala中的隐式参数的好例子?

到目前为止,Scala中的隐式参数对我来说看起来不太好 – 它与全局variables太接近了,但是由于Scala看起来像是相当严格的语言,我开始怀疑我自己的看法:-)。

问题:当隐式参数真正起作用时,能否展示真实(或接近)的示例。 IOW:比showPrompt更严重的showPrompt ,这将certificate这样的语言devise。

或者相反 – 你能否展示可靠的语言devise(可以是虚构的),这将使隐含的不必要。 我认为即使没有任何机制比牵连更好,因为代码更清晰,没有猜测。

请注意,我在询问参数,而不是隐式函数(转换)!

更新

全局variables

谢谢你所有的好的答案。 也许我澄清了我的“全局variables”反对意见。 考虑这样的function:

 max(x : Int,y : Int) : Int 

你叫它

 max(5,6); 

你可以(!)这样做:

 max(x:5,y:6); 

但在我眼中, implicits作品:

 x = 5; y = 6; max() 

它与这样的构造(类似PHP)没有太大的不同,

 max() : Int { global x : Int; global y : Int; ... } 

德里克的回答

这是一个很好的例子,但是如果你可以想到灵活使用发送消息而不是使用implicit请发表反例。 我真的很好奇语言devise的纯度;-)。

在某种意义上,是的,暗示代表了全球的状态。 但是,它们是不可变的,这是全局variables的真正问题 – 你没有看到人们抱怨全局常量,是吗? 事实上,编码标准通常规定,将代码中的任何常量转换为常量或枚举(通常是全局的)。

还要注意,implicits 不在平面的命名空间中,这也是全局性的常见问题。 它们被明确地绑定到types上,因此被绑定到这些types的包层次结构上。

所以,拿你的全局variables,使它们不变,并在声明站点初始化,并把它们放在命名空间。 他们仍然看起来像全局? 他们仍然看起来有问题吗?

但是,我们不要止步于此。 蕴含与types绑定,并且types与“全局”一样。 types是全球性的事实打扰你吗?

至于用例,它们很多,但是我们可以根据自己的历史做一个简短的回顾。 原来,afaik,斯卡拉没有牵连。 斯卡拉是视图types,许多其他语言的function。 我们今天仍然可以看到,如果你写了类似于T <% Ordered[T] ,这意味着typesT可以被看作是Ordered[T]types。 视图types是在types参数(generics)上使用自动转换的一种方法。

然后Scala 这个特征推广到implicits。 自动转换不再存在,相反,您有隐式转换 – 这只是Function1值,因此可以作为parameter passing。 从此, T <% Ordered[T]意味着隐式转换的值将作为parameter passing。 由于转换是自动的,函数的调用者不需要显式地传递参数 – 所以这些参数变成了隐式参数

请注意,有两个概念 – 隐式转换和隐式参数 – 非常接近,但不完全重叠。

无论如何,视图types变成隐式转换隐式传递的语法糖。 他们会被重写为这样的:

 def max[T <% Ordered[T]](a: T, b: T): T = if (a < b) b else a def max[T](a: T, b: T)(implicit $ev1: Function1[T, Ordered[T]]): T = if ($ev1(a) < b) b else a 

隐式参数只是该模式的泛化,使得传递任何types的隐式参数成为可能,而不仅仅是Function1 。 然后跟着他们的实际使用, 这些使用的语法糖后来。

其中之一是Context Bounds ,用于实现types模式 (模式,因为它不是内置的特性,只是使用提供类似于Haskelltypes的function的语言的一种方式)。 上下文绑定用于提供一个适配器,该适配器实现类中固有的function,但不是由它声明的。 它提供了inheritance和接口的好处,没有它们的缺点。 例如:

 def max[T](a: T, b: T)(implicit $ev1: Ordering[T]): T = if ($ev1.lt(a, b)) b else a // latter followed by the syntactic sugar def max[T: Ordering](a: T, b: T): T = if (implicitly[Ordering[T]].lt(a, b)) b else a 

您可能已经使用过了 – 人们通常不会注意到的一个常见用例。 它是这个:

 new Array[Int](size) 

这使用类清单的上下文绑定来启用这样的数组初始化。 通过这个例子我们可以看到:

 def f[T](size: Int) = new Array[T](size) // won't compile! 

你可以这样写:

 def f[T: ClassManifest](size: Int) = new Array[T](size) 

在标准库中,最常用的上下文边界是:

 Manifest // Provides reflection on a type ClassManifest // Provides reflection on a type after erasure Ordering // Total ordering of elements Numeric // Basic arithmetic of elements CanBuildFrom // Collection creation 

后三者主要用于集合,有maxsummap 。 一个广泛使用上下文边界的库是Scalaz。

另一个常见的用法是减less必须共享通用参数的操作的锅炉板。 例如,交易:

 def withTransaction(f: Transaction => Unit) = { val txn = new Transaction try { f(txn); txn.commit() } catch { case ex => txn.rollback(); throw ex } } withTransaction { txn => op1(data)(txn) op2(data)(txn) op3(data)(txn) } 

然后这样简化:

 withTransaction { implicit txn => op1(data) op2(data) op3(data) } 

这种模式用于事务内存,我认为(但我不确定)Scala I / O库也使用它。

我能想到的第三个常见用法是对正在传递的types进行certificate,这样可以在编译时检测会导致运行时exception的事情。 例如,请参阅Option上的这个定义:

 def flatten[B](implicit ev: A <:< Option[B]): Option[B] 

这使得这成为可能:

 scala> Option(Option(2)).flatten // compiles res0: Option[Int] = Some(2) scala> Option(2).flatten // does not compile! <console>:8: error: Cannot prove that Int <:< Option[B]. Option(2).flatten // does not compile! ^ 

一个广泛使用该function的库是Shapeless。

我不认为阿卡图书馆的例子适合于这四类中的任何一类,但这就是generics特征的全部观点:人们可以以各种方式使用它,而不是由语言devise者规定的方式。

如果你喜欢被规定(比如Python),那么Scala就不适合你。

当然。 阿卡在演员方面就是一个很好的例子。 当你在一个Actor的receive方法中时,你可能想要发送一个消息给另一个Actor。 当你这样做的时候,Akka会将(默认情况下)当前Actor作为消息的sender进行绑定,如下所示:

 trait ScalaActorRef { this: ActorRef => ... def !(message: Any)(implicit sender: ActorRef = null): Unit ... } 

sender是隐含的。 在演员有一个定义,如下所示:

 trait Actor { ... implicit val self = context.self ... } 

这在你自己的代码范围内创build了隐式的值,它可以让你做这样简单的事情:

 someOtherActor ! SomeMessage 

现在,你也可以做到这一点,如果你喜欢:

 someOtherActor.!(SomeMessage)(self) 

要么

 someOtherActor.!(SomeMessage)(null) 

要么

 someOtherActor.!(SomeMessage)(anotherActorAltogether) 

但通常你不会。 您只需保留Actor特征中隐式值定义的自然使用。 还有大约一百万个其他的例子。 收集类是一个巨大的。 尝试在任何非平凡的Scala图书馆周围徘徊,你会发现一辆卡车。

一个例子是在Traversable[A]上的比较操作。 如maxsort

 def max[B >: A](implicit cmp: Ordering[B]) : A 

这些只能在< A上有操作时明确定义。 所以,在没有暗示的情况下,每次我们想要使用这个函数时,都必须提供上下文Ordering[B] 。 (或放弃在max内进行静态检查,并冒着运行时间转换错误的风险。)

但是,如果隐式的比较types在范围内,例如一些Ordering[Int] ,我们可以直接使用它,或者通过为隐式参数提供其他值来简单地改变比较方法。

当然,隐含可能被隐藏,因此可能会出现范围内的实际隐含不够清楚的情况。 对于简单的使用maxsort它可能确实已经足够有一个固定的sortingtrait ,并使用一些语法来检查这个特质是否可用。 但是这意味着可能没有附加的特征,每一个代码都不得不使用最初定义的特征。

加成:
响应全局variables比较。

我认为你是正确的,在代码剪辑像

 implicit val num = 2 implicit val item = "Orange" def shopping(implicit num: Int, item: String) = { "I'm buying "+num+" "+item+(if(num==1) "." else "s.") } scala> shopping res: java.lang.String = I'm buying 2 Oranges. 

它可能会闻到腐烂和邪恶的全局variables。 但是关键的一点是,范围内的每种types可能只有一个隐式variables。 你有两个Int的例子是不行的。

另外,这意味着实际上,只有当一个types不一定是唯一但不同的主要实例时才使用隐式variables。 演员的self参照就是一个很好的例子。 types的例子是另一个例子。 对于任何types,可能有几十个代数比较,但有一个是特殊的。 (在另一个层面上,只要代码本身使用了一个非常独特的types,代码本身的实际行号也可能会产生一个好的隐式variables。)

你通常不使用implicit的日常types。 而与专门的types(如Ordering[Int] )没有太多的风险来隐藏它们。

隐式参数的另一个很好的一般用法是使方法的返回types取决于传递给它的一些参数的types。 Jens提到的一个很好的例子是集合框架和像map这样的方法,其全部签名通常是:

 def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[GenSeq[A], B, That]): That 

请注意,返回types是由编译器可以find的最佳拟合CanBuildFrom确定的。

再举一个例子,看看答案 。 在那里,方法Arithmetic.apply的返回types是根据某个隐式参数types( BiConverter )确定的。

很简单,记住:

  • 声明variables被传入也是隐含的
  • 在非隐含的参数之后在一个单独的()中声明所有的隐含参数,

例如

 def myFunction(): Int = { implicit val y: Int = 33 implicit val z: Double = 3.3 functionWithImplicit("foo") // calls functionWithImplicit("foo")(y, z) } def functionWithImplicit(foo: String)(implicit x: Int, d: Double) = // blar blar 

根据我的经验,使用implicits参数或implicits转换没有真正的好例子。

与他们创build的问题相比,使用implicits(不需要明确写入参数或types)的小好处是多余的。

我是15年的开发人员,在过去的1.5年里一直在使用scala。

我曾经看到许多错误是由于开发人员没有意识到使用隐含这个事实而造成的,而且一个特定的函数实际上会返回一个与指定types不同的types。 由于隐式转换。

我也听到一些说法,如果你不喜欢暗示,不要使用它们。 这在现实世界中是不实际的,因为很多时候外部库都被使用,其中很多是使用implicits,所以你的代码使用implicits,而你可能没有意识到这一点。 你可以编写一个代码,

 import org.some.common.library.{TypeA, TypeB} 

要么:

 import org.some.common.library._ 

这两个代码将编译并运行。 但是,自从第二个版本导入implicits转换后,它们不会总是产生相同的结果,这将导致代码的行为不同。

由于这个原因造成的“错误”在代码写入后可能会发生很长一段时间,以防某些受此转换影响的值最初不被使用。

一旦遇到错误,find原因不是一件容易的事情。 你必须做一些深入的调查。

即使你觉得自己像一个scala的专家,一旦你find了bug,并通过改变import语句来修复它,你实际上浪费了很多宝贵的时间。

我通常反对暗示的其他原因是:

  • 他们使代码难以理解(代码less,但你不知道自己在做什么)
  • 编译时间。 当使用implicits时,scala代码的编译速度要慢得多。
  • 实际上,它将语言从静态types转换为dynamictypes。 确实,一旦遵循非常严格的编码准则,您可以避免这种情况,但在现实世界中,情况并非总是如此。 即使使用IDE“删除未使用的导入”,也会导致您的代码仍然可以编译和运行,但与之前删除“未使用”导入的代码不同。

没有选项可以毫无隐含地编译scala(如果有的话请纠正我),如果有选项,那么通用社区scala库都不会编译。

由于上述所有的原因,我认为implicits是scala语言使用的最糟糕的做法之一。

斯卡拉有很多很棒的function,很多都不是很好。

为新项目select语言时,implicits是反对scala的原因之一,不赞成。 我的想法是。

隐式参数在集合API中被大量使用。 许多函数得到一个隐式的CanBuildFrom,这可以确保你得到最好的结果集合实现。

没有暗示,你会一直传递这样的东西,这会使正常的使用繁琐。 或者使用较不专业的collections品,因为这意味着你会失去performance力。

我对这个post有点评论,但最近我开始学习scala。 Daniel和其他人对隐式关键字给出了很好的背景。 从实际使用的angular度来看,我会给我两分隐含的variables。

如果用于编写Apache Spark代码,则最适合Scala。 在Spark中,我们确实具有spark上下文,并且很可能是可以从configuration文件中获取configuration键/值的configuration类。

现在,如果我有一个抽象类,并且如果我声明一个configuration和火花上下文的对象如下: –

 abstract class myImplicitClass { implicit val config = new myConfigClass() val conf = new SparkConf().setMaster().setAppName() implicit val sc = new SparkContext(conf) def overrideThisMethod(implicit sc: SparkContext, config: Config) : Unit } class MyClass extends myImplicitClass { override def overrideThisMethod(implicit sc: SparkContext, config: Config){ /*I can provide here n number of methods where I can pass the sc and config objects, what are implicit*/ def firstFn(firstParam: Int) (implicit sc: SparkContext, config: Config){ /*I can use "sc" and "config" as I wish: making rdd or getting data from cassandra, for eg*/ val myRdd = sc.parallelize(List("abc","123")) } def secondFn(firstParam: Int) (implicit sc: SparkContext, config: Config){ /*following are the ways we can use "sc" and "config" */ val keyspace = config.getString("keyspace") val tableName = config.getString("table") val hostName = config.getString("host") val userName = config.getString("username") val pswd = config.getString("password") implicit val cassandraConnectorObj = CassandraConnector(....) val cassandraRdd = sc.cassandraTable(keyspace, tableName) } } } 

正如我们可以看到上面的代码,我有两个隐式对象在我的抽象类,我已经将这两个隐式variables作为​​函数/方法/定义隐式parameter passing。 我认为这是我们可以用隐式variables来描述的最好的用例。