gre*_*man 73 parameters scala implicit
到目前为止,Scala中的隐式参数对我来说并不好看 - 它太接近于全局变量,但是由于Scala看起来像是相当严格的语言,我开始怀疑自己的看法:-).
问题:当隐式参数真正起作用时,你能展示一个真实(或接近)的好例子吗?IOW:比这更严重的东西showPrompt,这将证明这种语言设计的合理性.
或者相反 - 你能否展示出可靠的语言设计(可能是想象的),这种设计会使隐含的不必要.我认为即使没有机制比implicits更好,因为代码更清晰,没有猜测.
请注意,我问的是参数,而不是隐式函数(转换)!
谢谢你们所有的好答案.也许我澄清了我的"全局变量"异议.考虑这样的功能:
max(x : Int,y : Int) : Int
Run Code Online (Sandbox Code Playgroud)
你叫它
max(5,6);
Run Code Online (Sandbox Code Playgroud)
你可以(!)这样做:
max(x:5,y:6);
Run Code Online (Sandbox Code Playgroud)
但在我眼中的implicits作用是这样的:
x = 5;
y = 6;
max()
Run Code Online (Sandbox Code Playgroud)
它与这样的构造(类似PHP)没有太大的不同
max() : Int
{
global x : Int;
global y : Int;
...
}
Run Code Online (Sandbox Code Playgroud)
这是一个很好的例子,但如果你能想到不使用发送消息的灵活使用implicit请发一个反例.我对语言设计的纯度非常好奇;-).
Dan*_*ral 97
在某种意义上,是的,暗示代表全球状态.但是,它们不是可变的,这是全局变量的真正问题 - 你没有看到人们抱怨全局常量,对吗?实际上,编码标准通常要求您将代码中的任何常量转换为常量或枚举,这些常量或枚举通常是全局的.
另请注意,implicits 不在 flat命名空间中,这也是globals的常见问题.它们明确地与类型相关联,因此与这些类型的包层次结构相关联.
因此,取出你的全局变量,使它们不可变并在声明站点初始化,并将它们放在名称空间中.它们看起来像全局变量吗?他们仍然看起来有问题吗?
但是,我们不止于此.Implicits 被捆绑的类型,和他们一样多的"全球性"的类型.类型是全局的事实是否会打扰你?
至于用例,它们很多,但我们可以根据它们的历史进行简要回顾.原来,afaik,Scala没有暗示.Scala的观点类型是许多其他语言的特征.每当你写出类似的东西时,我们仍然可以看到今天T <% Ordered[T],这意味着类型T可以被视为一种类型Ordered[T].视图类型是一种在类型参数(泛型)上使用自动转换的方法.
然后Scala 将该特征概括为含义.自动转换不再存在,而是隐式转换 - 它们只是Function1值,因此可以作为参数传递.从那时起,T <% Ordered[T]意味着隐式转换的值将作为参数传递.由于强制转换是自动的,因此函数的调用者不需要显式传递参数 - 因此这些参数成为隐式参数.
请注意,有两个概念 - 隐式转换和隐式参数 - 非常接近,但不完全重叠.
无论如何,视图类型变成了隐式转换隐式转换的语法糖.它们将被重写为:
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
Run Code Online (Sandbox Code Playgroud)
隐式参数只是该模式的一般化,使得传递任何类型的隐式参数成为可能,而不仅仅是Function1.然后遵循它们的实际用途,后者使用这些用途的语法糖.
其中一个是Context Bounds,用于实现类型类模式(模式因为它不是内置特性,只是一种使用提供与Haskell类型类相似功能的语言的方法).上下文绑定用于提供一个适配器,该适配器实现类中固有的功能,但不是由它声明的.它提供了继承和接口的好处,没有它们的缺点.例如:
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
Run Code Online (Sandbox Code Playgroud)
您可能已经使用过 - 有一个人们通常不会注意到的常见用例.就是这个:
new Array[Int](size)
Run Code Online (Sandbox Code Playgroud)
它使用类清单的上下文绑定来启用此类数组初始化.我们可以看到这个例子:
def f[T](size: Int) = new Array[T](size) // won't compile!
Run Code Online (Sandbox Code Playgroud)
你可以像这样写:
def f[T: ClassManifest](size: Int) = new Array[T](size)
Run Code Online (Sandbox Code Playgroud)
在标准库中,最常用的上下文边界是:
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
Run Code Online (Sandbox Code Playgroud)
后三者主要用于集合,方法如max,sum和map.一个广泛使用上下文边界的库是Scalaz.
另一种常见用法是减少必须共享共同参数的操作的样板.例如,交易:
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)
}
Run Code Online (Sandbox Code Playgroud)
然后将其简化为:
withTransaction { implicit txn =>
op1(data)
op2(data)
op3(data)
}
Run Code Online (Sandbox Code Playgroud)
这种模式与事务性内存一起使用,我认为(但我不确定)Scala I/O库也使用它.
我能想到的第三个常见用法是提供有关正在传递的类型的证据,这使得在编译时检测会导致运行时异常的事情成为可能.例如,请参阅以下定义Option:
def flatten[B](implicit ev: A <:< Option[B]): Option[B]
Run Code Online (Sandbox Code Playgroud)
这使得这成为可能:
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!
^
Run Code Online (Sandbox Code Playgroud)
一个广泛使用该功能的库是Shapeless.
我不认为Akka库的例子适用于这四个类别中的任何一个,但这是通用特征的全部要点:人们可以以各种方式使用它,而不是语言设计者规定的方式.
如果你喜欢被处方(比方说,像Python那样),那么Scala就不适合你了.
Der*_*att 23
当然.就其演员而言,Akka有一个很好的例子.当你在Actor的receive方法中时,你可能想要向另一个Actor发送消息.当你这样做时,Akka会捆绑(默认情况下)当前的Actor作为sender消息,如下所示:
trait ScalaActorRef { this: ActorRef =>
...
def !(message: Any)(implicit sender: ActorRef = null): Unit
...
}
Run Code Online (Sandbox Code Playgroud)
这sender是隐含的.在Actor中有一个看起来像这样的定义:
trait Actor {
...
implicit val self = context.self
...
}
Run Code Online (Sandbox Code Playgroud)
这会在您自己的代码范围内创建隐式值,并允许您执行以下简单操作:
someOtherActor ! SomeMessage
Run Code Online (Sandbox Code Playgroud)
现在,如果您愿意,也可以这样做:
someOtherActor.!(SomeMessage)(self)
Run Code Online (Sandbox Code Playgroud)
要么
someOtherActor.!(SomeMessage)(null)
Run Code Online (Sandbox Code Playgroud)
要么
someOtherActor.!(SomeMessage)(anotherActorAltogether)
Run Code Online (Sandbox Code Playgroud)
但通常你不这样做.您只需保留Actor特征中隐式值定义即可实现的自然使用.还有大约一百万个其他例子.集合类是一个巨大的类.尝试在任何非平凡的Scala库中闲逛,你会找到一辆卡车.
一个例子是比较操作Traversable[A].例如max或sort:
def max[B >: A](implicit cmp: Ordering[B]) : A
Run Code Online (Sandbox Code Playgroud)
这些可以仅在有操作被合理地定义<上A.因此,在没有暗示的情况下,Ordering[B]每次我们想要使用此功能时,我们都必须提供上下文.(或者在内部放弃类型静态检查max并冒运行时强制转换的风险.)
但是,如果隐式比较类型类在范围内,例如一些Ordering[Int],我们可以立即使用它,或者只是通过为隐式参数提供一些其他值来更改比较方法.
当然,隐含可能被遮蔽,因此可能存在范围内的实际隐含不够清晰的情况.对于简单的用途max或者sort它可能确实是足够有一个固定的排序trait上Int,并使用一些语法检查这种特质是否可用.但这意味着可能没有附加特征,每一段代码都必须使用最初定义的特征.
增加:
对全局变量比较的响应.
我认为你是正确的,在一个代码剪切像
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.
Run Code Online (Sandbox Code Playgroud)
它可能闻到腐败和邪恶的全局变量.然而,关键的一点是,范围内每种类型可能只有一个隐式变量.你的两个Ints的例子不会起作用.
而且,这意味着实际上,只有在类型不一定是唯一但不同的主要实例时才使用隐式变量.self演员的引用就是这样一个很好的例子.类类示例是另一个示例.对于任何类型,可能存在数十种代数比较,但有一种是特殊的.(在另一个层面上,代码本身的实际行号也可以构成一个好的隐式变量,只要它使用非常独特的类型.)
您通常不会将implicits用于日常类型.对于特殊类型(例如Ordering[Int]),遮蔽它们的风险并不大.
根据我的经验,并没有使用隐式参数或隐式转换的好例子。
与隐式创建的问题相比,使用隐式(无需显式编写参数或类型)的小好处是多余的。
我从事开发工作已有15年,在过去的1.5年中一直与scala合作。
我已经看到很多次由开发人员引起的错误,这些错误是由于不了解使用隐式的事实,并且特定的函数实际上返回的类型与指定的类型不同。由于隐式转换。
我还听到有声明说,如果您不喜欢隐式,请不要使用它们。在现实世界中,这是不切实际的,因为很多时候都在使用外部库,并且很多外部库都使用隐式,因此您的代码使用隐式,您可能不会意识到这一点。您可以编写具有以下任一功能的代码:
import org.some.common.library.{TypeA, TypeB}
Run Code Online (Sandbox Code Playgroud)
要么:
import org.some.common.library._
Run Code Online (Sandbox Code Playgroud)
这两个代码都将编译并运行。但是它们并不总是产生相同的结果,因为第二个版本导入隐式转换,这将使代码的行为有所不同。
如果最初未使用受此转换影响的某些值,则在编写代码后很长一段时间内可能会发生由此引起的“错误”。
遇到错误后,找到原因就不容易了。您必须进行一些深入的调查。
即使您发现bug并通过更改import语句进行了修复,就觉得自己是scala的专家,但实际上却浪费了很多宝贵的时间。
我通常反对隐式的其他原因是:
没有没有隐式编译scala的选项(如果有,请纠正我),并且如果有一个选项,那么所有通用社区的scala库都不会编译。
由于以上所有原因,我认为隐式是Scala语言使用的最差的做法之一。
Scala具有许多出色的功能,但很多还没有那么出色。
为新项目选择语言时,隐式是反对scala的原因之一,而不是赞成它。在我看来。