函数式编程中的一般函数是什么样的?
有人说“我们没有对象,但我们有更高阶的功能”。更高阶函数会替换对象吗?
在编写面向对象的应用程序时,我尝试从更一般的概念转向更详细的概念,很多时候。如果我尝试在函数式编程中这样做,我是否需要更多高阶函数?
答案 0 :(得分:11)
这个答案面向Haskell而不是Lisp,因为虽然lisp具有更高阶的函数,但惯用的lisp可能并且通常非常面向对象。
我们也会忽略通常与面向对象编程相关的继承(和ad-hoc多态),但它有些正交。
通常,抽象数据类型“替换”对象,在某种意义上,通常您使用对象将一堆相关数据捆绑在一起。 Java或Python,并声明一种数据类型,以便在Haskell或ML中执行此类操作。
但是,对象还会将行为与数据捆绑在一起。因此,类的对象具有数据,但也有可以访问和改变该数据的函数。在函数式样式中,您只需在数据类型之外声明数据类型上的函数。然后通过任一模块或使用闭包来提供封装。
关于后一点 - 闭包和对象是双重的,尽管表达它们不一定是惯用的。在波特兰模式维基上有一些非常老派的讨论:http://c2.com/cgi/wiki?ClosuresAndObjectsAreEquivalent。
哦,以及来自oleg的一个例子:http://okmij.org/ftp/Scheme/oop-in-fp.txt。
忽略类型类(对于惯用的Haskell来说是必不可少的),并且只关注核心函数式编程,这里是一个不同方法的草图,这些方法将通过OO语言中的继承来完成。函数foo使用一些实现接口A的对象和一些实现接口B的对象来生成一些Double。使用更高阶函数,您可能具有fooGen :: (a -> Double -> Double) -> (b -> String -> Double) -> a -> b -> Double
的类型签名。
那个签名说fooGen
从一些a和一个Double到另一个Double取一个函数,一个函数b和一个String到一个Double,然后它取一个a和ab,最后它返回一个Double。
所以现在你可以通过部分应用,通过声明,例如fooSpecialized = fooGen funcOnA funcOnB
,将“接口”与具体值分开传递。
使用类型类,您可以抽象出“接口实现”(或者更合适的语言,字典)的具体传入,并声明foo :: HasSomeFunc a, HasSomeOtherFunc b => a -> b -> Double
。您可以将=>
左侧的内容视为松散地声明您的具体a和b类型需要实现的接口。
对于一个非常普遍的问题,这当然是一个手写的,部分的答案。
答案 1 :(得分:4)
有人说“我们没有对象,但我们有更高阶的功能”。更高阶函数会替换对象吗?
如果你的意思是高阶函数可以包含一些隐藏状态,那么是的。在其他函数内部定义的函数可以从其作用域中捕获一些信息,如果返回到外部世界,则将保留此信息。这就是关闭的内容。
如果你的意思是更高阶函数可以包含可变状态,那么没有。在纯函数式编程中,它们不是有状态的。它们在相同的输入上产生相同的结果。如果要模拟某些内容发生变化,则不要覆盖变量,而是定义如何从旧变量计算新值。
当然,有快捷方式,甚至功能语言都允许以命令式的方式进行写作。
如果我尝试在函数式编程中这样做,我是否需要更多高阶函数?
你会经常使用它们。可能甚至没有想到你的功能是高阶的。也许,享受他们很多。您只需将函数作为值传递给其他函数。
例如,map
是HOF。它的第一个参数是另一个函数。你会想到在命令式语言中作为循环“对于集合中的每个元素:应用一些函数,保存结果”,在函数式语言中将“将函数映射到集合上并获得新的结果集合” 。折叠是HOF的另一个例子。因此,命令式语言中的大多数循环可以转换为函数式语言中更高阶函数的调用。这是为了明确您使用它们的频率。
概述,但在函数式编程中已经完成了
这是一个很好的起点:Functional Programming。
f = let x = 3
in let withX y = x + y
in withX
现在f
与withX
相同,x = 3
是一个“记住”的函数f
。当我们使用y
时,我们只需提供一个参数x
,并将其与3
(3)的“记住”值相加。
这应打印[4, 5, 6]
然后main = do
print $ f 0
print $ map f [1..3]
:
3
我们不会将f
作为参数传递给3
,它会从上面的闭包中“记住”f
,
我们可以将map
本身作为参数传递给f
,在这种情况下这是一个HOF。
因此函数可以封装状态。
正如我上面所说,在函数式编程中,状态是不可变的。因此,如果您希望将操作g
应用于值,保存结果,然后应用操作f
,在函数式语言中,您将使用中间变量表达它,其中包含结果应用g
,然后对其应用x0
来计算新值。请注意,您没有“覆盖”原始值applyTwo first second x0 =
let x1 = first x0
in second x1
:
applyTwo' f g = g . f
但实际上可以把它写得更短,因为它只是一个简单的函数组合:
applyAll [] = id -- don't do anything if there are no functions to apply
applyAll (f:otherFs) = applyAll otherFs . f
或者您可以概括这种方法,并编写一个函数,它将应用任意数量的函数:
applyTwo
请注意,applyAll
和ghci> applyTwo (+1) (*10) 2
30
ghci> applyAll [(+1), (*10)] 2
30
现在是更高阶的功能。当然,它们不会替换对象,但允许避免可变状态。
这是如何使用的:
{{1}}
答案 2 :(得分:2)
这都是编程;相同的模式一次又一次地出现。您可以用自己喜欢的OO语言编写类似的内容:
role Person {
has 'firstname' => ( isa => 'Str' );
has 'lastname' => ( isa => 'Str' );
}
class Employee does Person {
has 'manager' => ( isa => 'Employee' );
has 'reports' => ( isa => '[Employee]' );
}
而在Haskell,你会写:
class Person a where
firstname :: a -> String
lastname :: a -> String
data Employee = Employee { firstName :: String
, lastName :: String
, manager :: Employee
, reports :: [Employee]
}
instance Person Employee where
firstname = firstName
lastname = lastName
人们过分关注不同之处,而不是意识到大多数事情都是一样的。