我理解为什么功能组成很重要。它允许从小而简单的功能构建大型复杂功能。
val f: A => B = ... val g: B => C = ... val h = f andThen g; // compose f and g
此构图符合身份和关联性法律。
关联性非常有用,因为它允许以任何顺序对f1 andThen f2 andThen f3 andThen f4 ...
进行分组。现在我想知道为什么身份是有用的。
def f[T](t:T) = t // identity function val g: A => B = ... // just any function g andThen f[B] == f[A] andThen g
所以,我的问题是这个身份的用途和原因。
答案 0 :(得分:10)
只要界面为您提供比实际需要更多的控制权,身份就非常有用。例如,Either
没有flatten
方法。我们假设你有
val e: Either[Double, Float] = Right(1.0f)
并且您希望将其展平为Double
。你怎么做呢?有一个方便的fold
方法,但没有方法可以将右侧转换为左侧的类型。所以你
e.fold(identity, _.toDouble)
你已经得到了你想要的东西。
答案 1 :(得分:3)
通常情况下,您可以通过分解公共部分来优化表格或代码等。也就是说,DRY原则说a ? v + x : v
包含冗余 - 您键入v
表达式两次。您必须将代码重写为最佳格式(a ? x: 0) + v
。你看,我们已经考虑了共同的部分。你很想用
if (!initialized) initialize(callback_with_very_long_name) else callback_with_very_long_name
如果需要,它会初始化某些内容并以任何方式调用回调。这看起来与上面的数学公式非常相似。您可以轻松地将数学/逻辑表达式中的公因子分解出来,但是如何分解函数应用程序呢?如果您了解mathiscs或Hascel,您应该看到
a ? x + v : v = v + (a ? x : 0)
非常像
a ? f value : value = ???
您在一个案例中添加x而在另一个案例中不添加。在一种情况下将函数应用于值,而在另一种情况下不应用函数。您将前者优化为(a ? x : 0) + v
,因为0是additive identity(添加时它不会改变任何内容)而v
是一个常见因素,无论{{1 }}。在函数应用程序(或非应用程序)的情况下,回调是公共因素。我们想要考虑它。什么是我们应该应用的身份函数,以便价值不会改变?身份功能!
application of x
是我们正在寻找的。我们摆脱了冗余。我们的原始示例如下所示
(a ? f : identity) value
请注意,它现在适合一行页面。我以这种方式提出这个问题,以突出问题"为什么我们需要不需要的东西"不是scala特定的。
答案 2 :(得分:3)
身份函数是函数组合的标识元素,例如1表示乘法,0表示加法。我们需要它就像我们需要1或0.例如,假设我们正在编写一个(更高阶)函数,它接受一个函数列表并返回它们的组成:然后自然地返回一个空列表的标识函数。这就像我们为一个空列表返回0,如果我们正在编写一个取整数列表并返回其总和的函数。
答案 3 :(得分:2)
最终会有一段时间您以这样的方式重构代码:您接受更高阶的功能,或者接受一些您不想要的M
的{{1}} Monad M[A => B]
价值为B
,但您需要放入相同的内容。保持代码流畅并响应项目不断变化的需求(或减少干扰违规行为),您无疑需要identity
。
答案 4 :(得分:2)
在一个更理论的方面(因为我可以猜出这个问题出现在哪里),身份功能的一个基本用途是它们允许你定义isomorphisms的概念,这通常会变成比通常的平等更有用的“同一性”定义(阅读this)。
同构告诉你“去那里又回来就像留在这里一样”。在类似集合的结构中,该定义对应于双射函数 - 但并非一切都是集合的。所以,一般来说我们可以说:函数(态射)f
是一个同构,如果有g
(它的逆),那么f . g == id
和g . f == id
。请注意,这至关重要取决于id
:我们通常不能假设事物具有我们可以参考的“元素”,就像我们在引入双射函数时通常所做的那样。
这是一个非基于集合的示例:考虑有向图。假设有顶点A -> B
和B -> A
。由于路径可以(关联!)连接,因此我们有路径A -> B -> A
和B -> A -> B
。但它们与循环A -> A
和B -> B
(或“停留在一个边缘”)只是“相同的东西”!我们现在也可以说这些是A
和B
的身份路径。根本没有双射或 A ......中的“forall x ”。
所有这些结构也可以用编程中的类别来描述(和使用)(Scala,Haskell);例如,pipes形成一个类别,因此需要具有标识管道。
除此之外,这里还有另一个实际用途,使用id
作为折叠的基本值:
doAll = foldr (.) id [(+1), (*2), (3-)]
结合多个内功能的简短版本。