功能组成及其表示

时间:2013-10-27 17:34:47

标签: haskell pointfree

我想知道:

1)以下功能完全相同:

inc = (+1)
double = (*2)

func1 = double . inc
func2 x = double $ inc x
func3 x = double (inc x)
func4 = \x -> double (inc x)

2)为什么不func5编译?

func5 = double $ inc        -- doesn't work

4 个答案:

答案 0 :(得分:15)

  

这些功能完全相同吗?

实际上,不!有一些非常微妙的差异。首先,阅读dreaded monomorphism restriction。简而言之,类多态函数默认情况下会被赋予不同的类型,如果它们是“明显”函数的话。在您的代码中,这种差异不会显现,因为incdouble不是“明显”的函数,因此给出了单态类型。但如果我们做出轻微改变:

inc, double :: Num a => a -> a
inc = (+1)
double = (*2)

func1 = double . inc
func2 x = double $ inc x
func3 x = double (inc x)
func4 = \x -> double (inc x)

然后在ghci中我们可以观察到func1func4 - 它们不是“明显”的函数 - 被赋予单态类型:

*Main> :t func1
func1 :: Integer -> Integer
*Main> :t func4
func4 :: Integer -> Integer

func2func3被赋予多态类型:

*Main> :t func2
func2 :: Num a => a -> a
*Main> :t func3
func3 :: Num a => a -> a

第二个细微差别是这些实现可能(非常轻微地)具有不同的评估行为。由于(.)($)是函数,因此您可能会发现调用func1func2需要进行一些评估才能运行。例如,func1 3的第一次调用可能是这样的:

func1 3
= {- definition of func1 -}
(double . inc) 3
= {- definition of (.) -}
(\f g x -> f (g x)) double inc 3
= {- beta reduction -}
(\g x -> double (g x)) inc 3
= {- beta reduction -}
(\x -> double (inc x)) 3

而第一次调用,例如func4 3以更直接的方式达到这一点:

func3 3
= {- definition of func3 -}
(\x -> double (inc x)) 3

但是,我不会太担心这个。我希望在GHC中启用优化后,对(.)($)的饱和调用都会被内联,从而消除了这种可能的差异;即使不是,它确实会是一个非常小的成本,因为每个定义可能只发生一次(每次调用不会发生一次)。

  

为什么不func5编译?

因为你不想编译!想象一下。让我们看看我们如何评估func5 3。我们会看到我们“陷入困境”。

func5 3
= {- definition of func5 -}
(double $ inc) 3
= {- definition of ($) -}
(\f x -> f x) double inc 3
= {- beta reduction -}
(\x -> double x) inc 3
= {- beta reduction -}
double inc 3
= {- definition of double -}
(\x -> x*2) inc 3
= {- beta reduction -}
(inc * 2) 3
= {- definition of inc -}
((\x -> x+1) * 2) 3

现在我们试图将函数乘以2。目前,我们还没有说过函数的乘法应该是什么(或者甚至,在这种情况下,应该是“两个”!),所以我们“陷入困境” - 我们无法进一步评估。这不好!我们不希望在如此复杂的术语中“陷入困境” - 我们只想陷入简单的术语,如实际的数字,函数,那种事情。

我们可以通过在开头观察double只知道如何对可以成倍增加的事物进行操作来防止整个混乱,inc不是可以成倍增加的事物。所以这就是类型系统所做的事情:它会进行这样的观察,并且当很明显会发生一些古怪的事情时就会拒绝编译。

答案 1 :(得分:7)

1)是的。这些功能都完全相同

2)要了解为什么func5不起作用,只需扩展其定义:

func5

-- Definition of `func5`
= double $ inc

-- Definition of `($)`
= double inc

-- Definition of `double`
= 2 * inc

-- Definition of `inc`
= 2 * (1 +)

编译器抱怨,因为(1 +)是一个函数,你不能将函数加倍。

答案 2 :(得分:4)

前四个功能相同。


您正尝试将double应用于inc。这不起作用,因为inc不能成倍增加。

double $ inc
-- is the same as
double inc

如果你添加了类型规格,你会看到它:

inc :: Integer -> Integer
double :: Integer -> Integer

double需要Integer,但您尝试将其传递给Integer -> Integer

请注意,最好在Haskell中明确说明顶级函数的类型,因为这些函数和程序经常会说明这些函数。

答案 3 :(得分:3)

$(称为apply运算符)与编写.(函数组合运算符)的方式不同。您可以使用 ghci

看到它们不一样
>:t ($)
($) :: (a -> b) -> a -> b

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

您始终可以用括号替换$。因此func2func5可以重写为:

func2 x = double (inc x)
func5 = double (inc)

但是double期望值Num a => a的值,并且您传递的值为Num a => a -> a,这就是为什么它不起作用。

您可以详细了解$ here. here