当我在Haskell中用+组成 * 时会发生什么?

k5hmc34c  于 2022-12-27  发布在  其他
关注(0)|答案(5)|浏览(190)

我想弄明白

(*) . (+)

在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

但是它无法编译,我试着在编写这些代码时遍历逻辑步骤,但是我不完全理解它是如何得到这个结果的(以及结果是什么)。

mkh04yzy

mkh04yzy1#

我理解你的感受。一开始我也发现函数组合很难掌握。帮助我理解这个问题的是类型签名。考虑一下:

(*) :: 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的示例呢?让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 -> tNum的示例。因此,...(t -> t) -> t -> t替换,并且添加了约束Num (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,这是25,我相信这是你想要的。
请注意,f .: g也可以写成(f .) . g(.:)也可以定义为(.:) = (.) . (.)。您可以在这里阅读更多信息:
What does (f .) . g mean in Haskell?

vwoqyblh

vwoqyblh2#

(*)(+)都有类型签名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 yMap到((x +) * f) y,其中f是从aa的函数,它也是一个数字。(*)需要函数的原因是在需要两个参数时使类型匹配,但该函数必须是数字,因为(*)只对数字起作用。
真的,这个函数根本没有意义。

oxalkeyp

oxalkeyp3#

首先是一些扩展:

{-# 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**添加了更好的链接。
im9ewurl

im9ewurl4#

假设:

m = (*)
a = (+)

那么

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

现在m需要一个Num a作为参数,另一方面,(a x),也就是说,(x +)是一个一元函数(a -> a),根据(+)的定义,我猜发生的事情是GHC试图将这两种类型结合起来,这样,如果你有一个既是数字又是一元函数的类型,m可以接受数字和一元函数,并返回一元函数,因为它们被视为相同的类型。
正如@Syd指出的,这种统一对于任何普通的数字类型(如整数和浮点数)都没有意义。

n3h0vuf2

n3h0vuf25#

这里有很好的答案,但让我快速指出几个步骤,你错了。
首先,函数组合的正确定义是

(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)

相关问题