身份功能在哪里以及为何有用?

时间:2014-02-01 22:14:18

标签: function scala functional-programming composition

我理解为什么功能组成很重要。它允许从小而简单的功能构建大型复杂功能。

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 

所以,我的问题是这个身份的用途和原因。

5 个答案:

答案 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 == idg . f == id。请注意,这至关重要取决于id:我们通常不能假设事物具有我们可以参考的“元素”,就像我们在引入双射函数时通常所做的那样。

这是一个非基于集合的示例:考虑有向图。假设有顶点A -> BB -> A。由于路径可以(关联!)连接,因此我们有路径A -> B -> AB -> A -> B。但它们与循环A -> AB -> B(或“停留在一个边缘”)只是“相同的东西”!我们现在也可以说这些是AB的身份路径。根本没有双射或 A ......中的“forall x ”。

所有这些结构也可以用编程中的类别来描述(和使用)(ScalaHaskell);例如,pipes形成一个类别,因此需要具有标识管道。


除此之外,这里还有另一个实际用途,使用id作为折叠的基本值:

doAll = foldr (.) id [(+1), (*2), (3-)]

结合多个内功能的简短版本。