说下面的二分法是否安全:
每个给定的功能都是
如果是这样,(功能的)副作用是在纯函数中找不到的任何东西。
答案 0 :(得分:58)
这在很大程度上取决于您选择的定义。一个函数是 pure 或 impure 是绝对公平的。纯函数始终返回相同的结果,并且不会修改环境。不正确的函数在重复执行时会返回不同的结果(这可能是由于对环境做了某些事情造成的)。
所有杂质都有副作用吗?我不这么说 - 函数可以依赖于它执行的环境中的某些东西。这可能是读取一些配置,GPS位置或从互联网读取数据。这些并非真正的副作用"因为它不会对世界做任何事情。
我认为有两种不同的杂质:
输出杂质是一个函数对世界起作用的时候。在Haskell中,这是使用monad建模的 - 不纯函数a -> b
实际上是一个函数a -> M b
,其中M
捕获它对世界所做的其他事情。
输入杂质是指函数需要环境中的某些内容。不纯函数a -> b
可以建模为函数C a -> b
,其中类型C
从函数可以访问的环境中捕获其他内容。
Monad和输出杂质当然更为人所知,但我认为输入杂质同样重要。我写了my PhD thesis about input impurities我称之为 coeffects ,所以我这可能是一个有偏见的答案。
答案 1 :(得分:10)
要使一个纯粹的功能必须:
但是,你看,这个定义了功能纯度 属性或没有副作用。您试图使用向后逻辑来使用纯函数来推断副作用的定义,这在逻辑上应该起作用,但实际上副作用的定义与功能纯度无关。
答案 2 :(得分:6)
我没有看到纯函数定义的问题:纯函数是一个函数。即它有一个域,一个codomain,并将前者的元素映射到后者的元素。它已在所有输入上定义。它对环境没有任何作用,因为"环境"此时并不存在:没有机器可以执行(对于某些定义"执行")给定的功能。只有从某事物到事物的总体映射。
然后,一些资本家决定入侵定义明确的功能世界并奴役这些纯粹的生物,但他们的公正本质无法在我们残酷的现实中生存,功能变得肮脏并开始使CPU变暖。因此,环境负责使CPU变暖,在其所有者被滥用和执行之前谈论纯度是完全合理的。同样,引用透明度是一种语言的属性 - 它通常不会保留在环境中,因为编译器中可能存在错误或者陨石可能会落在你的头上并且你的程序将停止产生同样的结果。
但还有其他生物:黑社会的黑暗居民。它们看起来像函数,但是它们知道环境并且可以与它交互:读取变量,发送消息和发射导弹。我们把这些堕落的亲戚称为“不纯洁的”。或者"有效的"并尽可能避免,因为他们的性质是如此黑暗,以至于无法对其进行推理。
因此,那些可以与外界互动的功能与那些不与之相互作用的功能之间显然存在很大差异。然而,"外部"的定义也可以变化。 State
monad仅使用纯工具建模,但我们考虑f : Int -> State Int Int
关于有效计算。此外,non-termination和例外(error "..."
)是效果,但是,使用者通常不会这样做。
总结一下,纯函数是一个定义明确的数学概念,但我们通常会考虑编程语言中的函数,而纯粹的函数取决于你的观点,所以谈论二分法并没有多大意义。当涉及的概念没有明确定义时。
答案 3 :(得分:5)
定义函数f
的纯度的方法是∀x∀y x = y ⇒ f x = f y
,即,给定相同的参数,函数返回相同的结果,或者保持相等。
这不是人们在谈论“纯粹的功能”时通常所说的;它们通常意为“纯粹”,因为“没有副作用”。我还没弄明白如何鉴定“副作用”(评论欢迎!)所以我对此没什么可说的。
尽管如此,我将探索这种纯度概念,因为它可能提供一些相关的见解。我不是这里的专家;这主要是我只是漫无边际。但我希望它能引发一些富有洞察力的(和纠正性的)评论。
要了解纯洁,我们必须知道我们所谈论的平等。 x = y
的含义是什么,f x = f y
是什么意思?
一种选择是Haskell语义相等。也就是说,Haskell语义的相等性赋予其术语。据我所知,Haskell没有官方的指称语义,但Wikibooks Haskell Denotational Semantics提供了一个合理的标准,我认为社区或多或少同意临时。当Haskell说其功能是纯粹的时,这就是它所指的平等。
通过派生(==)
类,另一个选择是用户定义的相等(即Eq
)。这在使用指称性设计时是相关的 - 也就是说,我们将自己的语义分配给术语。有了这个选择,我们可能会意外地编写不纯的函数; Haskell并不关心我们的语义。
我将Haskell语义相等性称为=
,将用户定义的相等性称为==
。另外我假设==
是一个相等关系 - 这不适用于==
的某些实例,例如Float
。
当我使用x == y
作为命题时,我的意思是x == y = True ∨ x == y = ⊥
,因为x == y :: Bool
和⊥ :: Bool
。换句话说,当我说x == y
为真时,我的意思是如果它计算的不是⊥那么它会计算为真。
如果根据Haskell的语义x
和y
相等,则根据我们可能选择的任何其他语义,它们是相等的。
证明:如果x = y
,那么x == y ≡ x == x
和x == x
为真,因为==
是纯粹的(根据=
)和反身。
同样,我们可以证明∀f∀x∀y x = y ⇒ f x == f y
。如果x = y
然后f x = f y
(因为f
是纯粹的),因此f x == f y ≡ f x == f x
和f x == f x
为真,因为==
是纯粹的和反身的。
这是一个愚蠢的例子,说明我们如何为用户定义的平等打破纯度。
data Pair a = Pair a a
instance (Eq a) => Eq (Pair a) where
Pair x _ == Pair y _ = x == y
swap :: Pair a -> Pair a
swap (Pair x y) = Pair y x
现在我们有:
Pair 0 1 == Pair 0 2
可是:
swap (Pair 0 1) /= swap (Pair 0 2)
注意:
¬(Pair 0 1 = Pair 0 2)
所以我们无法保证我们对(==)
的定义会合适。
更有说服力的例子是考虑Data.Set
。如果x, y, z :: Set A
那么您希望这样做,例如:
x == y ⇒ (Set.union z) x == (Set.union z) y
特别是当Set.fromList [1,2,3]
和Set.fromList [3,2,1]
表示相同的集合但可能具有不同的(隐藏的)表示形式时(不等同于Haskell的语义)。也就是说,我们希望∀z Set.union z
确保(==)
纯Set
{/ 1}}。
这是我玩过的一种类型:
newtype Spry a = Spry [a]
instance (Eq a) => Eq (Spry a) where
Spry xs == Spry ys = fmap head (group xs) == fmap head (group ys)
Spry
是具有不相等的相邻元素的列表。例子:
Spry [] == Spry []
Spry [1,1] == Spry [1]
Spry [1,2,2,2,1,1,2] == Spry [1,2,1,2]
鉴于此,==
的纯实现(根据flatten :: Spry (Spry a) -> Spry a
for Spry)是什么,如果x
是子spry的元素,它也是扁平的spry(即∀x∀xs∀i x ∈ xs[i] ⇒ x ∈ flatten xs
之类的东西)?为读者练习。
值得注意的是,我们一直在讨论的功能是在同一个域中,因此它们的类型为A → A
。那是因为当我们证明{H}从Haskell的语义域到我们自己的语义域时∀f∀x∀y x = y ⇒ f x == f y
。这可能是某种类型的同态......也许理论家可能会在这里权衡(请做!)。
答案 4 :(得分:1)
副作用是语言定义的一部分。在表达式
中f e
e
的副作用是e
行为的所有部分,它们被“移出”并成为应用程序表达式行为的一部分,而不是而不是作为f
的值的一部分传递到e
。
有关具体示例,请考虑以下程序:
f x = x; x
f (print 3)
在概念上,语法x; x
表示'运行x,然后再次运行并返回结果'。
在print
将stdout写入副作用的语言中,写入
3
因为输出是应用程序表达式语义的一部分。
在print
的输出不是副作用的语言中,写入
3
3
因为输出是x
定义中f
变量语义的一部分。