当我在Haskell中用*编写时会发生什么?

时间:2014-12-27 04:02:00

标签: haskell functional-programming function-composition

我正在尝试理解

的结果
(*) . (+) 

在Haskell。我知道合成算子只是数学函数的标准组合 - 所以

(f . g) = f (g x)

可是:

(*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a

我很难理解这种类型的签名。我希望能够做到这样的事情:

((*) . (+)) 1 2 :: Num a => a -> a
= (* (+ 1 2))

(*)是什么意思。 (+)的签名?我尝试用类似的方式玩它(只是匹配它的签名):

((*) . (+)) 1 (\x -> x + 1) 1

但是无法编译。我正在尝试在编写这些步骤时完成逻辑步骤,但我还没有完全理解它是如何得到这个结果的(结果)。

5 个答案:

答案 0 :(得分:67)

我理解你的感受。我发现功能组成起初也很难掌握。什么帮助我解决这个问题是类型签名。考虑:

(*) :: Num x => x -> x -> x
(+) :: Num y => y -> y -> y
(.) :: (b -> c) -> (a -> b) -> a -> c

现在,当您编写(*) . (+)时,它实际上与(.) (*) (+)相同(即(*)(.)的第一个参数,(+)是第二个参数到(.)):

(.) :: (b -> c) -> (a -> b) -> a -> c
       |______|    |______|
           |           |
          (*)         (+)

因此(*)的类型签名(即Num x => x -> x -> x)与b -> c统一:

(*) :: Num x => x -> x -> x -- remember that `x ->  x -> x`
                |    |____| -- is implicitly `x -> (x -> x)`
                |       |
                b ->    c

(.) (*) ::          (a -> b) -> a ->    c
                          |             |
                          |          |‾‾‾‾|
           Num x =>       x          x -> x

(.) (*) :: Num x => (a -> x) -> a -> x -> x

因此(+)的类型签名(即Num y => y -> y -> y)与Num x => a -> x统一:

(+) :: Num y => y -> y -> y -- remember that `y ->  y -> y`
                |    |____| -- is implicitly `y -> (y -> y)`
                |       |
       Num x => a ->    x

(.) (*) (+) ::  Num x                => a ->     x    ->    x
                                        |        |          |
                                        |     |‾‾‾‾|     |‾‾‾‾|
                              Num y  => y     y -> y     y -> y

(.) (*) (+) :: (Num (y -> y), Num y) => y -> (y -> y) -> y -> y

我希望澄清Num (y -> y)Num y的来源。你留下了(Num (y -> y), Num y) => y -> (y -> y) -> y -> y类型的非常奇怪的函数。

令它如此奇怪的是,它希望yy -> y都是Num的实例。可以理解,y应该是Num的实例,但y -> y如何?使y -> y成为Num的实例似乎不合逻辑。这不是正确的。

但是,当你看一下函数组合实际上是什么时,这是有道理的:

( f  .  g ) = \z ->  f  ( g  z)

((*) . (+)) = \z -> (*) ((+) z)

所以你有一个函数\z -> (*) ((+) z)。因此,z必须明确是Num的实例,因为(+)已应用于此\z -> (*) ((+) z)。因此,Num t => t -> ...的类型为...,其中(*) ((+) z)((+) z)的类型,我们将在稍后找到。

因此,Num t => t -> t属于(*)类型,因为它需要多一个数字。但是,在将其应用于其他数字之前,会对其应用(*)

因此((+) z)期望Num成为t -> t的实例,这就是为什么Num应该是...的实例的原因。因此,(t -> t) -> t -> tNum (t -> t)替换,并添加了约束(Num (t -> t), Num t) => t -> (t -> t) -> t -> t,从而产生了(*)类型。

您真正希望合并(+)(.:)的方式是使用(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d f .: g = \x y -> f (g x y)

(*) .: (+)

因此\x y -> (*) ((+) x y)(+)相同。现在给((+) x y)两个参数确保Num t => t确实只是Num t => t -> t而不是((*) .: (+)) 2 3 5

因此(*) ((+) 2 3) 5(*) 5 5 25f .: g,我相信这就是你想要的。

请注意,(f .) . g也可以写为(.:)(.:) = (.) . (.)也可以定义为{{1}}。你可以在这里阅读更多相关信息:

What does (f .) . g mean in Haskell?

希望有所帮助。

答案 1 :(得分:9)

(*)(+)都有类型签名Num a => a -> a -> a 现在,如果你把它们组成,你会得到一些时髦的东西。

(*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a

那是因为(*)(+)期待两个'参数'。

带有一个参数的

(+)可以获得一个函数。 .运算符需要该函数(您看到的a -> a)。

这是(*) . (+)

的含义
                                       x     f          y
 (*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a

(*) . (+)x f y映射到((x +) * f) y,其中faa的函数,它也是一个数字。 (*)期望函数的原因是使类型匹配而它需要两个参数,但该函数必须是一个数字,因为(*)仅适用于数字。

真的,这个功能完全没有意义。

答案 2 :(得分:7)

首先是一些扩展:

{-# LANGUAGE FlexibleContexts, FlexibleInstances, TypeSynonymInstances #-}

正如其他答案所示,您的功能是

weird :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
weird x g = (x +) * g

但是这个函数确实有非奇怪的语义。

difference lists的概念。因此,存在差异整数的概念。我已经看到它们仅在依赖类型的设置中使用(例如here,但这不是唯一的情况)。该定义的相关部分是

instance Enum DiffInt where
    toEnum   n = (n +)
    fromEnum n = n 0

instance Num DiffInt where
    n + m = n . m
    n * m = foldr (+) id $ replicate (fromEnum n) m

这在Haskell中没有多大意义,但对于依赖类型可能很有用。

现在我们可以写

test :: DiffInt
test = toEnum 3 * toEnum 4

或者

test :: DiffInt
test = weird 3 (toEnum 4)

在两种情况下fromEnum test == 12

修改

可以避免使用TypeSynonymInstances扩展程序:

{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}

weird :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
weird x g = (x +) * g

instance (Enum a, Num a) => Enum (a -> a) where
    toEnum   n = (toEnum n +)
    fromEnum n = fromEnum $ n (toEnum 0)

instance (Enum a, Num a) => Num (a -> a) where
    n + m = n . m
    n * m = foldr (+) id $ replicate (fromEnum n) m

type DiffInt = Int -> Int

和以前一样,我们可以写

test' :: DiffInt
test' = weird 3 (toEnum 4)

但现在我们也可以写

-- difference ints over difference ints
type DiffDiffInt = DiffInt -> DiffInt

test'' :: DiffDiffInt
test'' = weird (toEnum 3) (toEnum (toEnum 4))

main = print $ fromEnum $ fromEnum test'

打印12

EDIT2 添加了更好的链接。

答案 3 :(得分:2)

让:

m = (*)
a = (+)

然后

(m.a) x = (m (a x)) = m (a x) 

现在m期望Num a作为参数,另一方面(a x)(x +)(a -> a)的一元函数(+),定义为{{ 1}}。我想发生的事情是GHC试图统一这两种类型,这样,如果你有一个既是数字又是一元函数的类型,m可以取一个数和一元函数并返回一元函数,因为它们被认为是同一类型。

正如@Syd所指出的,这种统一对于任何正常的数字类型都没有意义,例如整数和浮点数。

答案 4 :(得分:2)

这里有很好的答案,但让我快点指出你出错的几个步骤。

首先,功能组合的正确定义是

(f . g) x = f (g x)

您在LHS上省略了x。接下来,您应该记住,在Haskell中h x y(h x) y相同。所以,与你的预期相反,

((*) . (+)) 1 2 = (((*) . (+)) 1) 2 = ((*) ((+) 1)) 2 = ((+) 1) * 2,

现在你明白为什么失败了。此外,

((*) . (+)) 1 (\x -> x + 1) 1

不起作用,因为不满足约束Num (Int -> Int)