什么构成纯函数式编程的价值?
看到一句话后我问自己这些问题:
Task
(要么 IO
)有一个捕获副作用的构造函数 值。
- 功能是一个值吗?
- 如果是这样,在将两个函数等同时它意味着什么:
assert(f == g)
。对于两个等效但单独定义的函数=> f != g
,他们为什么不工作 1 == 1
?
- 方法的对象是值吗? (例如
IO { println("") }
)
- 具有setter方法和可变状态的对象是一个值吗?
- 具有可变状态的对象是否作为状态机的值?
我们如何测试某些东西是否有价值?不变性是否足够?
更新:
我正在使用Scala。
我会试着解释一下 值 是将它与事物形成对比 不是价值观。
大致说来, 值 是由过程产生的结构 评测 对应于 条款 这无法进一步简化。
条款
首先,是什么 条款?术语是可以评估的语法结构。不可否认,这有点循环,让我们看一些例子:
常量文字是术语:
42
适用于其他术语的功能是术语:
atan2(123, 456 + 789)
函数文字是术语
(x: Int) => x * x
构造函数调用是以下术语:
Option(42)
对比如下:
类声明/定义不是术语:
case class Foo(bar: Int)
也就是说,你不能写
val x = (case class Foo(bar: Int))
这将是非法的。
同样,特征和类型定义不是术语:
type Bar = Int
sealed trait Baz
与函数文字不同,方法定义不是术语:
def foo(x: Int) = x * x
例如:
val x = (a: Int) => a * 2 // function literal, ok
val y = (def foo(a: Int): Int = a * 2) // no, not a term
包声明和import语句不是术语:
import foo.bar.baz._ // ok
List(package foo, import bar) // no
正常形式,价值观
现在,当希望有点清楚什么时候 术语 是什么意思“不能再进一步简化*?在理想化的函数式编程语言中,你可以定义什么 正常形式, 更确切地说 弱头正常形态 是。基本上,如果不能将减少规则应用于该术语以使其更简单,则术语采用(wh-)正常形式。再举几个例子:
这是一个术语,但它不是正常形式,因为它可以减少到 42
:
40 + 2
这不是弱头正常形式:
((x: Int) => x * 2)(3)
因为我们可以进一步评估它 6
。
这个lambda处于弱头正常形式(它被卡住,因为计算不能继续进行直到 x
供应):
(x: Int) => x * 42
这不是正常形式,因为它可以进一步简化:
42 :: List(10 + 20, 20 + 30)
这是正常形式,不可能进一步简化:
List(42, 30, 50)
从而,
42
,
(x: Int) => x * 42
,
List(42, 30, 50)
是值,而
40 + 2
,
((x: Int) => x * 2)(3)
,
42 :: List(10 + 20, 20 + 30)
不是价值,而只是可以进一步简化的非标准化术语。
示例和非示例
我将逐个列出您的子问题列表:
函数是一个值
是的,像 (x: T1, ..., xn: Tn) => body
在WHNF中被认为是卡住的术语,在功能语言中它们实际上可以被表示,因此它们是值。
如果是这样,在将两个函数等同时它意味着什么: assert(f == g)
对于两个相同但单独定义的函数=> f != g
,他们为什么不工作 1 == 1
?
函数扩展性与某个值是否为值的问题有些无关。在上面的“通过示例定义”中,我只讨论了术语的形状,而不是关于在这些术语上定义的某些可计算关系的存在/不存在。可悲的事实是你甚至无法确定lambda表达式是否实际上代表一个函数(即它是否终止所有输入),并且还知道不存在可以确定两个函数是否产生所有输入的输出相同(即在扩展上相等)。
方法的对象是值吗? (例如 IO { println("") }
)
不太清楚你在这里问的是什么。对象没有方法。类有方法。如果你的意思是方法调用,那么,不,它们是可以进一步简化的术语(通过实际运行方法),因此它们不是值。
具有setter方法和可变状态的对象是一个值吗?
具有可变状态的对象是否作为状态机的值?
在纯函数式编程中没有这样的东西。
什么构成纯函数式编程的价值?
背景
在 纯 函数式编程没有突变。因此,代码如
case class C(x: Int)
val a = C(42)
val b = C(42)
会变得相当于
case class C(x: Int)
val a = C(42)
val b = a
从那以后 纯 功能编程,如果 a.x == b.x
那我们就有了 a == b
。那是, a == b
将实现比较内部的值。
但是,Scala并不纯粹,因为它允许变换,就像Java一样。在这种情况下,当我们声明时,我们没有上面两个片段之间的等价 case class C(var x: Int)
。确实,表演 a.x += 1
后记不影响 b.x
在第一个片段中,但在第二个片段中,在哪里 a
和 b
指向同一个对象。在这种情况下,进行比较是有用的 a == b
比较对象 引用,而不是它的内部整数值。
使用时 case class C(x: Int)
,Scala比较 a == b
表现更接近纯函数式编程,比较整数值。有规律(非 case
)类,Scala反而比较对象引用,打破两个片段之间的等价。但是,再一次,Scala并不纯粹。相比之下,在Haskell中
data C = C Int deriving (Eq)
a = C 42
b = C 42
确实相当于
data C = C Int deriving (Eq)
a = C 42
b = a
因为Haskell中没有“引用”或“对象标识”。请注意,Haskell实现可能会在第一个片段中分配两个“对象”,而在第二个片段中只分配一个对象,但由于无法在Haskell内区分它们,因此程序输出将是相同的。
回答
功能是一个值吗? (那么当等于两个函数时它意味着什么:assert(f == g)。对于两个等价但又单独定义的函数=> f!= g,为什么它们不像1 == 1)
是的,函数是纯函数式编程中的值。
上面,当你提到“相当但单独定义的函数”时,你假设我们可以比较这两个函数的“引用”或“对象标识”。在纯函数式编程中我们不能。
纯函数式编程应该比较函数的制作 f == g
相当于 f x == g x
对于所有可能的论点 x
。当只有几个值时,这是可行的 x
,例如如果 f,g :: Bool -> Int
我们只需要检查 x=True, x=False
。对于具有无限域的函数,这要困难得多。例如,如果 f,g :: String -> Int
我们无法检查无限多的字符串。
理论计算机科学(可计算性理论)也证明了没有比较两个函数的算法 String -> Int
,即使我们有权访问这两个函数的源代码,甚至不是一个低效的算法。出于这个数学原因,我们必须接受函数是无法比较的值。在Haskell中,我们通过 Eq
类型类,声明几乎所有标准类型都是可比较的,函数是例外。
方法的对象是值吗? (例如,IO {println(“”)})
是。粗略地说,“一切都是价值”,包括IO行动。
具有setter方法和可变状态的对象是一个值吗?
具有可变状态的对象是否作为状态机的值?
纯函数式编程中没有可变状态。
充其量,setter可以生成带有修改字段的“新”对象。
是的,对象将是一个值。
我们如何测试它是否是一个值,是不可变的可以作为一个值的充分条件吗?
在纯函数式编程中,我们只能拥有不可变数据。
在不纯的函数式编程中,我认为当我们不比较对象引用时,我们可以调用大多数不可变对象“值”。如果“immutable”对象包含对可变对象的引用,例如
case class D(var x: Int)
case class C(c: C)
val a = C(D(42))
然后事情变得更棘手。我想我们还可以打电话 a
“不可改变的”,因为我们无法改变 a.c
,但是我们应该小心 a.c.x
可以变异。
根据意图,我认为有些人不会打电话 a
不可改变的。我不会考虑 a
成为一个价值。
为了使事情更加混乱,在不纯的编程中,有些对象使用突变以有效的方式呈现“纯粹”的界面。例如,可以编写一个纯函数,在返回之前将其结果存储在缓存中。当在同一个参数上再次调用时,它将返回先前计算的结果
(这通常被称为 记忆化)。在这里,突变发生了,但是从外部无法观察到,我们最多可以观察到更快的实现。在这种情况下,我们可以简单地假装该函数是纯函数(即使它执行变异)并将其视为“值”。
与命令式语言的对比鲜明。在像Python这样的复杂语言中,函数的输出是定向的。它可以分配给变量,显式返回,打印或写入文件。
当我在Haskell中编写函数时,我从不考虑输出。我从不使用“返回”一切都有“一个”价值。这称为“符号”编程。 “一切”,意思是“符号”。与人类语言一样,名词和动词代表某种东西。那是他们的价值所在。 “皮特”的“价值”是皮特。 “皮特”这个名字不是皮特,而是皮特这个人的代表。函数式编程也是如此。最好的比喻是数学或逻辑当你进行计算页面时,你是否指导每个函数的输出?您甚至可以在函数或表达式中“分配”变量以替换它们的“值”。
价值观是
- 不可改变/永恒
- 匿名
- 语义透明
42的价值是多少? 42.什么是“价值” new Date()
? Date object at 0x3fa89c3
。 42的身份是什么? 42.什么是身份? new Date()
?正如我们在前面的例子中看到的那样,它就是生活在这个地方的东西。它可能在不同的上下文中有许多不同的“值”,但它只有一个身份。 OTOH,42本身就足够了。询问42在系统中的哪个生命在语义上毫无意义。 42的语义是什么? 42的大小。是什么语义 new Foo()
?谁知道。
我会添加第四个标准(在野外的某些情况下看到这个,但不是其他情况),这是:值是 语言不可知论者 (我不确定前3个是否足以保证这一点,也不确定这样的规则是否与大多数人对价值意味着什么的直觉完全一致)。
价值观就是这样的
- 函数可以作为输入并作为输出返回,即可以计算,和
- 是一个类型的成员,即某些集合的元素,和
- 可以绑定到变量,即可以命名。
第一点是关键的测试是否是某种价值。或许这个词 值由于调节,可能会立即让我们想到数字,但这个概念非常笼统。基本上我们可以给予和离开函数的任何东西都可以被认为是一个值。数字,字符串,布尔值,类的实例,函数本身,谓词,甚至类型本身,可以是函数的输入和输出,因此是值。
IO
monad是这个概念一般性的一个很好的例子。当我们说 IO
monad模型副作用作为值,我们的意思是一个函数可以产生副作用(比如说 println
)作为输入并作为输出返回。 IO(println(...))
把思想分开了 影响 的一个动作 println
从实际执行动作开始,并允许将这些效果视为可以使用与任何其他值(如数字)相同的语言工具计算的第一类值。