我在考虑像 Ruby 这样的纯面向对象语言,其中包括数字、int、浮点数和字符串在内的所有内容本身都是对象。这与纯函数式语言相同吗?例如,在 Haskell 中,数字和字符串也是函数吗?
我知道 Haskell 是基于 lambda 演算的,它将所有内容(包括数据和操作)都表示为函数。在我看来,“纯函数式语言”将一切建模为函数,并保持函数最总是返回具有相同输入且没有状态的相同输出的定义,这似乎是合乎逻辑的。
我在考虑像 Ruby 这样的纯面向对象语言,其中包括数字、int、浮点数和字符串在内的所有内容本身都是对象。这与纯函数式语言相同吗?例如,在 Haskell 中,数字和字符串也是函数吗?
我知道 Haskell 是基于 lambda 演算的,它将所有内容(包括数据和操作)都表示为函数。在我看来,“纯函数式语言”将一切建模为函数,并保持函数最总是返回具有相同输入且没有状态的相同输出的定义,这似乎是合乎逻辑的。
理论上可以考虑,但是...
就像在 Ruby 中不是所有东西都是对象(例如,参数列表不是对象)一样,在 Haskell 中也不是所有东西都是函数。
如需更多参考,请查看这篇简洁的帖子:http ://conal.net/blog/posts/everything-is-a-function-in-haskell
@wrhall 给出了一个很好的答案。但是,您有点正确,在纯 lambda 演算中,一切都是函数是一致的,并且该语言是图灵完备的(能够表达 Haskell 等的任何纯计算)。
这给了你一些非常奇怪的东西,因为你唯一能做的就是把它应用到别的东西上。你什么时候能观察到一些东西?你有一些价值f
并且想知道一些关于它的东西,你唯一的选择是将它应用一些值x
来获取f x
,这是另一个函数,唯一的选择是将它应用到另一个值y
,获取f x y
等等。
我经常将纯 lambda 演算解释为谈论对不是函数但只能表达函数本身的事物的转换。也就是说,我可以创建一个函数(使用一些 Haskelly 语法糖进行递归 & 让):
purePlus = \zero succ natCase ->
let plus = \m n -> natCase m n (\m' -> plus m' n)
in plus (succ (succ zero)) (succ (succ zero))
这里我已经表达了计算2+2
,而不需要知道有非函数之类的东西。我只是将我需要的东西作为我正在定义的函数的参数,这些参数的值可以是教堂编码,也可以是“真实”数字(无论这意味着什么)——我的定义不在乎。
你也可以想到 Haskell。没有特别的理由认为有些东西不是函数,也没有特别的理由认为一切都是函数。但是 Haskell 的类型系统至少可以防止您将参数应用于数字(fromInteger
现在正在考虑的任何人都需要保持沉默!:-)。在上面的解释中,这是因为数字不一定被建模为函数,所以你不一定对它们应用参数。
如果现在还不清楚,那么整个答案在某种程度上是技术/哲学的题外话,而您的问题的简单答案是“不,并非所有内容都是函数式语言中的函数”。函数是你可以应用参数的东西,仅此而已。
这个问题的角度非常不同:Haskell 中的各种数据都可以表示为函数,使用一种称为Church 编码的技术。这是一种控制反转的形式:不是将数据传递给使用它的函数,而是将数据隐藏在一组闭包中,并使用它传递回调来描述如何处理这些数据。
例如,任何使用列表的程序都可以翻译成使用函数而不是列表的程序:
-- | A list corresponds to a function of this type:
type ChurchList a r = (a -> r -> r) --^ how to handle a cons cell
-> r --^ how to handle the empty list
-> r --^ result of processing the list
listToCPS :: [a] -> ChurchList a r
listToCPS xs = \f z -> foldr f z xs
该功能以具体列表为起点,但这不是必需的。您可以仅从纯函数构建ChurchList
函数:
-- | The empty 'ChurchList'.
nil :: ChurchList a r
nil = \f z -> z
-- | Add an element at the front of a 'ChurchList'.
cons :: a -> ChurchList a r -> ChurchList a r
cons x xs = \f z -> f z (xs f z)
foldChurchList :: (a -> r -> r) -> r -> ChurchList a r -> r
foldChurchList f z xs = xs f z
mapChurchList :: (a -> b) -> ChurchList a r -> ChurchList b r
mapChurchList f = foldChurchList step nil
where step x = cons (f x)
filterChurchList :: (a -> Bool) -> ChurchList a r -> ChurchList a r
filterChurchList pred = foldChurchList step nil
where step x xs = if pred x then cons x xs else xs
最后一个函数使用Bool
,当然我们也可以Bool
用函数替换:
-- | A Bool can be represented as a function that chooses between two
-- given alternatives.
type ChurchBool r = r -> r -> r
true, false :: ChurchBool r
true a _ = a
false _ b = b
filterChurchList' :: (a -> ChurchBool r) -> ChurchList a r -> ChurchList a r
filterChurchList' pred = foldChurchList step nil
where step x xs = pred x (cons x xs) xs
这种转换基本上可以对任何类型进行,所以理论上,你可以去掉 Haskell 中的所有“值”类型,只保留()
类型、(->)
和IO
类型构造函数、return
for>>=
和IO
一组合适的IO
原语。这显然是不切实际的——而且效果会更差(尝试写作tailChurchList :: ChurchList a r -> ChurchList a r
以换取品味)。
“纯功能”中的“纯”是指“无副作用”的纯。当人们谈论“纯面向对象的语言”时,它与“纯”的含义几乎没有关系,这只是意味着该语言纯粹(仅)在对象中进行操作。
原因是 pure-as-in-only 是用于分类面向对象语言的合理区别,因为有像 Java 和 C++ 这样的语言,它们的值显然与对象没有太多共同之处,还有像 Python 和 Ruby 这样的语言,可以说每个值都是一个对象1
而对于函数式语言,没有“纯函数式”的实用语言,因为语言可以操作的每个值都是函数。用这种语言编程当然是可能的。lambda 演算的最基本版本没有任何非函数事物的概念,但是您仍然可以通过提出将要计算的事物表示为函数的方法来对它们进行任意计算。2
但是,虽然 lambda 演算的简单性和极简主义往往非常适合证明有关编程的事情,但实际上用这种“原始”编程语言编写大量程序是很尴尬的。像数字这样的基本事物的函数表示在实际的物理机器上实现也往往效率很低。
但是,鼓励函数式风格但在任何地方都允许未跟踪的副作用的语言与实际上强制您的函数是“纯”函数(类似于数学函数)的语言之间存在一个非常重要的区别。面向对象的编程与不纯计算的使用非常紧密3,因此没有实用的面向对象的编程语言在这个意义上是纯粹的。
所以“纯函数式语言”中的“纯”与“纯面向对象语言”中的“纯”有很大不同。4在每种情况下,“纯与非纯”的区别对于另一种语言来说完全没有意义,因此没有非常强烈的动机来规范该术语的使用。
1在我所知道的所有“纯面向对象”语言中都有一些极端情况可供选择,但这并不是很有趣。很明显,对象隐喻在作为某个类的实例的语言中走得更远1
,并且该类可以被子类化,而不是在1
不是对象的语言中。
2 无论如何,所有计算都是关于表示的。计算机对数字或其他任何东西一无所知。它们只是具有我们用来表示数字的位模式,以及恰好对应于数字操作的位模式上的操作(因为我们设计它们是为了让它们这样做)。
3这也不是根本的。你可以设计一种在这个意义上是纯粹的“纯粹的”面向对象的语言。无论如何,我倾向于将我的大部分 OO 代码编写为纯粹的。
4如果这看起来很迟钝,您可能会认为“功能”、“对象”和“语言”这些术语在其他上下文中也有截然不同的含义。
是getChar :: IO Char
函数还是不函数?Haskell 报告没有为我们提供定义。但它声明这getChar
是一个函数(见这里)。(好吧,至少我们可以说它是一个函数。)
所以我认为答案是肯定的。
我认为除了“一切都是函数”之外,“函数”不能有正确的定义。(什么是“正确定义”?好问题......)考虑下一个例子:
{-# LANGUAGE NoMonomorphismRestriction #-}
import Control.Applicative
f :: Applicative f => f Int
f = pure 1
g1 :: Maybe Int
g1 = f
g2 :: Int -> Int
g2 = f
是f
函数还是数据类型?这取决于。