Tri*_*Gao 28 f# haskell functional-programming scala purely-functional
可以说下面的二分法是否安全:
每个给定的功能是
如果是这样,(函数的)副作用是在纯函数中找不到的任何东西.
Tom*_*cek 58
这在很大程度上取决于您选择的定义.一个函数是纯粹的还是不纯的,这绝对是公平的.纯函数始终返回相同的结果,并且不会修改环境.不正确的函数在重复执行时可能会返回不同的结果(这可能是由于对环境做了某些事情造成的).
所有杂质都有副作用吗?我不这么说 - 函数可以依赖于它执行的环境中的某些东西.这可能是读取一些配置,GPS位置或从互联网读取数据.这些并不是真正的"副作用",因为它对世界没有任何作用.
我认为有两种不同的杂质:
输出杂质是指函数对世界起作用的时候.在Haskell中,这是使用monads建模的 - 一个不纯的函数a -> b
实际上是一个函数a -> M b
,它M
捕获它对世界做的其他事情.
输入杂质是指功能需要环境中的某些东西.不纯函数a -> b
可以建模为一种函数C a -> b
,其中类型C
从函数可以访问的环境中捕获其他内容.
Monad和输出杂质当然更为人所知,但我认为输入杂质同样重要.我写了关于输入杂质的博士论文,我称之为系数,所以我这可能是一个有偏见的答案.
art*_*ter 10
要使功能变得纯粹,它必须:
但是,你看,这定义了功能纯度与属性或没有副作用.您正在尝试使用向后逻辑来使用纯函数来推断副作用的定义,这在逻辑上应该起作用,但实际上副作用的定义与函数纯度无关.
我没有看到纯函数定义的问题:纯函数是一个函数.即它有一个域,一个codomain,并将前者的元素映射到后者的元素.它是在所有输入上定义的.它对环境没有任何作用,因为此时"环境"不存在:没有机器可以执行给定函数(对于某些"执行"的定义).只有从某事物到事物的总体映射.
然后一些资本家决定侵入明确定义的功能世界并奴役这些纯粹的生物,但是他们的公正本质无法在我们残酷的现实中生存,功能变得肮脏并开始使CPU变得更加温暖.
因此,环境负责使CPU变暖,在其所有者被滥用和执行之前谈论纯度是完全合理的.同样地,引用透明度是一种语言的属性 - 它通常不会保留在环境中,因为编译器中可能存在错误,或者陨石可能会落在你的头上而你的程序将停止产生相同的结果.
但还有其他生物:黑社会的黑暗居民.它们看起来像函数,但是它们知道环境并且可以与它交互:读取变量,发送消息和发射导弹.我们把这些堕落的亲属称为"不纯"或"有效",并尽可能避免,因为他们的性质是如此黑暗,以至于无法推理它们.
因此,那些可以与外界互动的功能与不与之相互作用的功能之间存在很大差异.然而,"外部"的定义也可能有所不同.该State
单子只使用纯工具建模,但我们认为大约f : Int -> State Int Int
为约effectful计算.此外,非终止和异常(error "..."
)是效果,但是,黑客通常不会这样认为.
总而言之,纯函数是一个明确定义的数学概念,但我们通常会考虑编程语言中的函数以及纯粹的函数取决于您的观点,因此当涉及的概念不是时,讨论二分法没有多大意义.明确界定.
定义函数纯度的一种方法f
是?x?y x = y ? f x = f y
,即给定相同的参数,函数返回相同的结果,或者保持相等.
这不是人们在谈论"纯粹的功能"时通常所说的; 它们通常意为"纯粹",因为"没有副作用".我还没弄清楚如何鉴定"副作用"(评论欢迎!)所以我没有任何关于它的说法.
尽管如此,我将探讨这种纯度概念,因为它可能会提供一些相关的见解.我不是这里的专家; 这主要是我只是漫无边际.但我希望它能引发一些富有洞察力(和纠正!)的评论.
要理解纯洁,我们必须知道我们正在谈论的平等.什么x = y
意思,什么f x = f y
意思?
一种选择是Haskell语义相等.也就是说,Haskell语义的相等性赋予其术语.据我所知,Haskell没有官方的指称语义,但Wikibooks Haskell Denotational Semantics提供了一个合理的标准,我认为社区或多或少同意ad-hoc.当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
Run Code Online (Sandbox Code Playgroud)
现在我们有:
Pair 0 1 == Pair 0 2
Run Code Online (Sandbox Code Playgroud)
但:
swap (Pair 0 1) /= swap (Pair 0 2)
Run Code Online (Sandbox Code Playgroud)
注意:
¬(Pair 0 1 = Pair 0 2)
所以我们无法保证我们的定义(==)
是可以的.
一个更引人注目的例子是考虑Data.Set
.如果x, y, z :: Set A
那时你希望这有,例如:
x == y ? (Set.union z) x == (Set.union z) y
Run Code Online (Sandbox Code Playgroud)
特别是当Set.fromList [1,2,3]
和Set.fromList [3,2,1]
表示相同的集合但可能具有不同的(隐藏的)表示(不等同于Haskell的语义).也就是说我们希望确保这?z Set.union z
是纯粹(==)
的Set
.
这是我玩过的一种类型:
newtype Spry a = Spry [a]
instance (Eq a) => Eq (Spry a) where
Spry xs == Spry ys = fmap head (group xs) == fmap head (group ys)
Run Code Online (Sandbox Code Playgroud)
A Spry
是具有不相等的相邻元素的列表.例子:
Spry [] == Spry []
Spry [1,1] == Spry [1]
Spry [1,2,2,2,1,1,2] == Spry [1,2,1,2]
Run Code Online (Sandbox Code Playgroud)
鉴于此,什么是纯实现(根据==
Spry)flatten :: Spry (Spry a) -> Spry a
,如果x
是子spry的元素,它也是扁平spry的元素(即类似的东西?x?xs?i x ? xs[i] ? x ? flatten xs
)?为读者锻炼.
值得注意的是,我们一直在谈论的功能是在同一个域中,因此它们具有类型A ? A
.那就是当我们证明?f?x?y x = y ? f x == f y
从Haskell的语义域到我们自己的语义域时.这可能是某种类型的同态......也许类别理论家可以在这里权衡(请做!).