我想知道:
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
答案 0 :(得分:15)
这些功能完全相同吗?
实际上,不!有一些非常微妙的差异。首先,阅读dreaded monomorphism restriction。简而言之,类多态函数默认情况下会被赋予不同的类型,如果它们是“明显”函数的话。在您的代码中,这种差异不会显现,因为inc
和double
不是“明显”的函数,因此给出了单态类型。但如果我们做出轻微改变:
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中我们可以观察到func1
和func4
- 它们不是“明显”的函数 - 被赋予单态类型:
*Main> :t func1
func1 :: Integer -> Integer
*Main> :t func4
func4 :: Integer -> Integer
而func2
和func3
被赋予多态类型:
*Main> :t func2
func2 :: Num a => a -> a
*Main> :t func3
func3 :: Num a => a -> a
第二个细微差别是这些实现可能(非常轻微地)具有不同的评估行为。由于(.)
和($)
是函数,因此您可能会发现调用func1
和func2
需要进行一些评估才能运行。例如,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)