在研究函数式编程时,部分应用函数的概念出现了很多。在Haskell中,内置函数take
被认为是部分应用的。
我仍不清楚部分应用函数或其使用/含义的确切含义。
答案 0 :(得分:2)
功能本身不能“部分应用”或不能。这是一个毫无意义的概念。
当您说某个函数是“部分应用”时,您可以参考该函数的调用方式(又名“应用”)。如果使用其所有参数调用该函数,则称其为“完全应用”。如果缺少某些参数,则该函数被称为“部分应用”。
例如:
-- The function `take` is fully applied here:
oneTwoThree = take 3 [1,2,3,4,5]
-- The function `take` is partially applied here:
take10 = take 10 -- see? it's missing one last argument
-- The function `take10` is fully applied here:
oneToTen = take10 [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,42]
部分函数应用程序的结果是另一个函数 - 一个仍然“期望”得到它缺少的参数的函数 - 比如上面例子中的take10
,它仍然“期望”接收列表。 / p>
当然,一旦进入高阶函数,一切都会变得复杂一些 - 即将其他函数作为参数或返回其他函数作为结果的函数。考虑这个功能:
mkTake n = take (n+5)
函数mkTake
只有一个参数,但它返回另一个函数作为结果。现在,考虑一下:
x = mkTake 10
y = mkTake 10 [1,2,3]
在第一行,函数mkTake
显然是“完全应用”,因为它给出了一个参数,这正是它所期望的参数数量。但第二行也是有效的:因为mkTake 10
返回一个函数,我可以用另一个参数调用这个函数。那么mkTake
是什么? “过度应用”,我猜?
然后考虑这样的事实(除非编译器优化)所有函数在数学上都是恰好一个参数的函数。怎么会这样?当你声明一个函数take n l = ...
时,你在概念上说的是take = \n -> \l -> ...
- 也就是说,take
是一个接受参数n
并返回另一个函数的函数接受参数l
并返回一些结果。
所以底线是“部分应用程序”的概念并不是真正严格定义的,它只是一个方便的简写来引用“应该”(在常识中)采用N个参数的函数,但是而是给出M< N个论点。
答案 1 :(得分:2)
经典的例子是
add :: Int -> Int -> Int
add x y = x + y
add
函数接受两个参数并添加它们,我们现在可以实现
increment = add 1
部分应用add
,现在等待另一个参数。
答案 2 :(得分:2)
严格地说,部分应用程序指的是这样一种情况:你提供的参数少于预期的参数,并且获取一个动态创建的新函数,该函数需要剩下的参数。
同样严格地说,这不适用于Haskell,因为每个函数都使用完全一个参数。没有部分应用程序,因为您要么将函数应用于其参数,要么不应用。
但是,Haskell为定义包含模拟多参数函数的函数提供了语法糖。然后,在Haskell中,“部分应用程序”指的是提供少于获取不能进一步应用于另一个参数的值所需的全部参数。使用每个人最喜欢的add
示例,
add :: Int -> Int -> Int
add x y = x + y
该类型表示add
接受一个类型为Int
的参数,并返回类型为Int -> Int
的函数。 ->
类型构造函数是右关联的,因此有助于显式地将其括起来以强调Haskell函数的单参数性质:Int -> (Int -> Int)
。
当调用这样的“多参数”函数时,我们利用了函数应用程序 left -associative这一事实,因此我们可以编写{{1}而不是add 3 5
。调用(add 3) 5
被认为是部分应用,因为我们可以进一步将结果应用于另一个参数。
我提到了Haskell提供的语法糖,以便于定义复杂的高阶函数。定义函数有一种基本方法:使用lambda表达式。例如,要定义一个向其参数添加3的函数,我们编写
add 3
为便于参考,我们可以为此功能指定一个名称:
\x -> x + 3
为了进一步简化定义,Haskell允许我们在其位置写作
add = \x -> x + 3
隐藏显式的lambda表达式。
对于高阶“多参数”函数,我们会编写一个函数来添加两个值
add x = x + 3
为了隐藏currying,我们可以改为编写
\x -> \y -> x + y
结合使用参数化名称替换lambda表达式的语法糖,以下所有内容都是明确键入的函数\x y -> x + y.
的等效定义:
add :: Num a => a -> a -> a
答案 3 :(得分:0)
通过示例可以最好地理解这一点。这是加法运算符的类型:
(+) :: Num a => a -> a -> a
这是什么意思?如您所知,它需要两个数字并输出另一个数字。对?好吧,有点。
您看,a -> a -> a
实际上意味着:a -> (a -> a)
。哇,看起来很奇怪!这意味着(+)
是一个函数,它接受一个参数并输出一个函数(!!),它也接受一个参数并输出一个值。
这意味着您可以将一个值提供给(+)
以获取另一个部分应用,功能:
(+ 5) :: Num a => a -> a
此函数接受一个参数并向其添加五个参数。现在,使用一些参数调用该新函数:
Prelude> (+5) 3
8
你走了!部分功能应用在工作中!
请注意(+ 5) 3
的类型:
(+5) 3 :: Num a => a
看看我们最终得到了什么:
(+) :: Num a => a -> a -> a
(+ 5) :: Num a => a -> a
(+5) 3 :: Num a => a
您是否看到每次添加新参数时类型签名中a
的数量减少一个?
“...内置函数take
之类的内容被视为部分应用” - 我不太明白这意味着什么。函数take
本身未部分应用。如果函数是部分应用,它是在带有N个参数的函数之间提供1和(N-1)个参数的结果。因此,take 5
是部分应用的函数,但take
和take 5 [1..10]
不是。