gpt4 book ai didi

scala - Scala 中隐式参数的好例子?

转载 作者:行者123 更新时间:2023-12-03 05:21:38 25 4
gpt4 key购买 nike

关闭。这个问题是opinion-based .它目前不接受答案。












想改善这个问题吗?更新问题,以便可以通过 editing this post 用事实和引文回答问题.

2年前关闭。




Improve this question




到目前为止,Scala 中的隐式参数对我来说并不好看——它太接近全局变量了,但是由于 Scala 似乎是一种相当严格的语言,我开始怀疑我自己的观点:-)。

问题:当隐式参数真正起作用时,您能否展示一个真实(或接近)的好例子。 IOW:比showPrompt更严重的事情,这将证明这种语言设计是合理的。

或者相反 - 您能否展示可靠的语言设计(可以是想象的),这将使隐式变得不必要。我认为即使没有机制比隐式更好,因为代码更清晰,没有猜测。

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

更新

全局变量

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

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 发送消息的灵活用法请贴一个反例。我真的很好奇语言设计的纯度;-)。

最佳答案

从某种意义上说,是的,隐式表示全局状态。然而,它们不是可变的,这就是全局变量的真正问题——你不会看到人们提示全局常量,是吗?事实上,编码标准通常要求您将代码中的任何常量转换为常量或枚举,它们通常是全局的。

另请注意,隐式不在平面命名空间中,这也是全局变量的常见问题。它们明确绑定(bind)到类型,因此绑定(bind)到这些类型的包层次结构。

因此,使用您的全局变量,使它们不可变并在声明站点初始化,并将它们放在 namespace 中。它们看起来仍然像全局变量吗?它们看起来仍然有问题吗?

但我们不要止步于此。隐式与类型相关联,并且它们与类型一样具有“全局性”。类型是全局的这一事实是否困扰着您?

至于用例,它们很多,但我们可以根据它们的历史进行简要回顾。最初,afaik,Scala 没有隐式。 Scala 拥有的是 View 类型,这是许多其他语言都有的特性。我们今天仍然可以看到,无论何时你写下诸如 T <% Ordered[T] 之类的东西,表示类型 T可以看作是一个类型 Ordered[T] . View 类型是一种在类型参数(泛型)上进行自动转换的方法。

Scala 然后用隐式概括了这个特性。自动转换不再存在,取而代之的是隐式转换——这只是 Function1值,因此可以作为参数传递。从此,T <% Ordered[T]意味着隐式转换的值将作为参数传递。由于强制转换是自动的,函数的调用者不需要显式传递参数——所以这些参数变成了隐式参数。

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

无论如何, View 类型成为隐式传递隐式转换的语法糖。它们将被重写如下:

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

隐式参数只是该模式的泛化,可以传递任何类型的隐式参数,而不仅仅是 Function1 .然后是它们的实际使用,然后是这些用途的语法糖。

其中之一是 Context Bounds,用于实现类型类模式(模式因为它不是内置功能,只是一种使用语言的方式,该语言提供与 Haskell 的类型类类似的功能)。上下文绑定(bind)用于提供一个适配器,该适配器实现类中固有但未由类声明的功能。它提供了继承和接口(interface)的优点而没有它们的缺点。例如:
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)

使用类 list 的上下文绑定(bind)来启用此类数组初始化。我们可以通过这个例子看到:
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

后三个多用于集合,方法如 max , summap .一个广泛使用上下文边界的库是 Scalaz。

另一个常见用法是减少必须共享公共(public)参数的操作的样板。例如交易:
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 库也使用它。

我能想到的第三个常见用法是对正在传递的类型进行证明,这使得在编译时检测可能会导致运行时异常的事情成为可能。例如,请参阅 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!
^

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

我认为 Akka 库的例子不适合这四个类别中的任何一个,但这就是通用特性的全部意义:人们可以以各种方式使用它,而不是语言设计者规定的方式。

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

关于scala - Scala 中隐式参数的好例子?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9530893/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com