繁体   English   中英

纯函数式编程中的“价值”是什么?

[英]What is “value” in pure functional programming?

什么构成纯函数式编程的价值?

看到一句话后我问自己这些问题:

Task (或IO )有一个构造函数,可以将副作用捕获为

  • 功能是一个值吗?
    • 如果是这样,在将两个函数等同时它意味着什么: assert(f == g) 对于两个相同但单独定义的函数=> f != g ,为什么它们不能作为1 == 1
  • 方法的对象是值吗? (例如IO { println("") }
  • 具有setter方法和可变状态的对象是一个值吗?
  • 具有可变状态的对象是否作为状态机的值?

我们如何测试某些东西是否有价值? 不变性是一个充分条件吗?

更新:我正在使用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 ,其中ab指向同一个对象。 在这种情况下,比较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 )。 在这里,突变发生,但它不能从外部观察到,在那里我们最多可以观察到更快的实现。 在这种情况下,我们可以简单地假装该函数是纯函数(即使它执行变异)并将其视为“值”。

我将尝试通过将其与非价值的事物进行对比来解释什么是价值

粗略地说, 价值观评估过程产生的结构,对应于不能进一步简化的术语


条款

首先, 条款是什么? 术语是可以评估的语法结构。 不可否认,这有点圆,所以让我们看一些例子:

  1. 常量文字是术语:

     42 
  2. 适用于其他术语的功能是术语:

     atan2(123, 456 + 789) 
  3. 函数文字是术语

     (x: Int) => x * x 
  4. 构造函数调用是术语:

     Option(42) 

对比如下:

  1. 类声明/定义不是术语:

     case class Foo(bar: Int) 

    也就是说,你不能写

     val x = (case class Foo(bar: Int)) 

    这将是非法的。

  2. 同样,特征和类型定义不是术语:

     type Bar = Int sealed trait Baz 
  3. 与函数文字不同,方法定义不是术语:

     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 
  4. 包声明和import语句不是术语:

     import foo.bar.baz._ // ok List(package foo, import bar) // no 

正常形式,价值观

现在,当希望更清楚一个术语是什么时,“不能再进一步简化”是什么意思?在理想化的函数式编程语言中,你可以定义一个正常的形式 ,或者更简单的弱正常形式 。实际上,如果不能将减少规则应用于该术语以使其更简单,则术语是(wh-)正常形式。再次,一些例子:

  1. 这是一个术语,但它不是正常形式,因为它可以减少到42

     40 + 2 
  2. 这不是弱头正常形式:

     ((x: Int) => x * 2)(3) 

    因为我们可以进一步评估它到6

  3. 这个lambda是弱头正常形式(它被卡住了,因为在提供x之前计算不能进行):

     (x: Int) => x * 42 
  4. 这不是正常形式,因为它可以进一步简化:

     42 :: List(10 + 20, 20 + 30) 
  5. 这是正常形式,不可能进一步简化:

     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中编写函数时,我从不考虑输出。 我从不使用“返回”一切都有“一个”价值。 这被称为“符号”编程。 “一切”,意思是“符号”。 与人类语言一样,名词和动词代表某种东西。 那是他们的价值所在。 “皮特”的“价值”是皮特。 “皮特”这个名字不是皮特,而是皮特这个人的代表。 函数式编程也是如此。 最好的比喻是数学或逻辑当你进行计算页面时,你是否指导每个函数的输出? 您甚至可以在函数或表达式中“分配”变量以替换它们的“值”。

价值观是

  1. 不可改变/永恒
  2. 匿名
  3. 语义透明

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM