[英]What is “value” in pure functional programming?
什么构成纯函数式编程的价值?
看到一句话后我问自己这些问题:
Task
(或IO
)有一个构造函数,可以将副作用捕获为值 。
assert(f == g)
。 对于两个相同但单独定义的函数=> f != g
,为什么它们不能作为1 == 1
? IO { println("") }
) 我们如何测试某些东西是否有价值? 不变性是一个充分条件吗?
更新:我正在使用Scala。
什么构成纯函数式编程的价值?
在纯函数式编程中,没有突变。 因此,代码如
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
因为,在纯函数式编程中,如果ax == bx
,那么我们将得到a == b
。 也就是说, a == b
将被实现比较内部的值。
但是,Scala并不纯粹,因为它允许变形,就像Java一样。 在这种情况下,当我们声明case class C(var x: Int)
时,我们没有上面两个片段之间的等价。 实际上,执行ax += 1
后跟不会影响第一个片段中的bx
,但会影响第二个片段中的bx
,其中a
和b
指向同一个对象。 在这种情况下,比较a == b
比较对象引用而不是内部整数值是很有用的。
当使用case class C(x: Int)
,Scala比较a == b
表现得更接近纯函数式编程,比较整数值。 对于常规(非case
)类,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
等效于fx == gx
用于所有可能的参数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
“不可改变的”,既然我们不能改变ac
,但我们应该小心,因为acx
可以突变。 根据不同的目的,我认为有些人会不叫a
不可改变的。 我不认为a
是一个价值。
为了使事情更加混乱,在不纯的编程中,有些对象使用突变以有效的方式呈现“纯粹”的界面。 例如,可以编写一个纯函数,在返回之前将其结果存储在缓存中。 当在同一个参数上再次调用时,它将返回先前计算的结果(这通常称为memoization )。 在这里,突变发生,但它不能从外部观察到,在那里我们最多可以观察到更快的实现。 在这种情况下,我们可以简单地假装该函数是纯函数(即使它执行变异)并将其视为“值”。
我将尝试通过将其与非价值的事物进行对比来解释什么是价值 。
粗略地说, 价值观是评估过程产生的结构,对应于不能进一步简化的术语 。
条款
首先, 条款是什么? 术语是可以评估的语法结构。 不可否认,这有点圆,所以让我们看一些例子:
常量文字是术语:
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方法和可变状态的对象是一个值吗? 具有可变状态的对象是否作为状态机的值?
在纯函数式编程中没有这样的东西。
与命令式语言的对比鲜明。 在像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
动作的效果与动作的实际执行分开,并允许将这些效果视为可以使用相同语言设施计算的第一类值。对于任何其他值,如数字。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.