首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >当我和+在Haskell作曲时发生了什么?

当我和+在Haskell作曲时发生了什么?
EN

Stack Overflow用户
提问于 2014-12-27 04:02:00
回答 5查看 1.7K关注 0票数 45

我在试着理解

代码语言:javascript
复制
(*) . (+) 

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

代码语言:javascript
复制
(f . g) = f (g x)

但是:

代码语言:javascript
复制
(*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a

我很难理解这种签名。我本来希望能做这样的事情:

代码语言:javascript
复制
((*) . (+)) 1 2 :: Num a => a -> a
= (* (+ 1 2))

(*)的意思是什么?(+)类型签名?我试着用这样的方法来玩它(只是与它的签名相匹配):

代码语言:javascript
复制
((*) . (+)) 1 (\x -> x + 1) 1

但这不能进行编译。在编写这些内容时,我试图遍历逻辑步骤,但我并不完全理解它是如何达到这个结果的(以及结果是什么)。

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2014-12-27 04:57:36

我理解你的感受。一开始,我发现函数构图也很难掌握。帮助我摸索这件事的是打字签名。考虑:

代码语言:javascript
复制
(*) :: Num x => x -> x -> x
(+) :: Num y => y -> y -> y
(.) :: (b -> c) -> (a -> b) -> a -> c

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

代码语言:javascript
复制
(.) :: (b -> c) -> (a -> b) -> a -> c
       |______|    |______|
           |           |
          (*)         (+)

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

代码语言:javascript
复制
(*) :: 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统一

代码语言:javascript
复制
(+) :: 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的一个实例似乎不合逻辑。那不可能是对的。

但是,当您查看功能组合的实际功能时,这是有意义的:

代码语言:javascript
复制
( 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类型。

您真正想要将(*)(+)结合起来的方法是使用(.:)

代码语言:javascript
复制
(.:) :: (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(.:)也可以定义为(.:) = (.) . (.)。你可以在这里读到更多关于它的内容:

什么是(f .)是在Haskell吗?

希望这能有所帮助。

票数 67
EN

Stack Overflow用户

发布于 2014-12-27 04:38:46

(*)(+)现在都有类型签名Num a => a -> a -> a,如果编写它们,就会得到一些古怪的东西。

代码语言:javascript
复制
(*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a

这是因为(*)(+)需要两个‘参数’。

(+)用一个参数就可以得到一个函数。.操作符期望这个函数(您看到的a -> a )。

以下是(*) . (+)的含义

代码语言:javascript
复制
                                       x     f          y
 (*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a

(*) . (+)x f y映射到((x +) * f) y,其中f是从aa的函数,也是一个数字。(*)期望一个函数的原因是为了使类型匹配,同时它需要两个参数,但是该函数必须是一个数字,因为(*)只在数字上工作。

实际上,这个函数根本就没有意义。

票数 9
EN

Stack Overflow用户

发布于 2014-12-27 07:43:05

一些扩展部分首先:

代码语言:javascript
复制
{-# LANGUAGE FlexibleContexts, FlexibleInstances, TypeSynonymInstances #-}

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

代码语言:javascript
复制
weird :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
weird x g = (x +) * g

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

有一个关于差异列表的概念。因此,有一个不同整数的概念。我看到它们只在依赖类型的设置中使用(例如这里,但这不是唯一的情况)。该定义的相关部分是

代码语言:javascript
复制
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中没有多大意义,但对于依赖类型可能很有用。

现在我们可以写

代码语言:javascript
复制
test :: DiffInt
test = toEnum 3 * toEnum 4

代码语言:javascript
复制
test :: DiffInt
test = weird 3 (toEnum 4)

在这两种情况下,fromEnum test == 12

编辑

可以避免使用TypeSynonymInstances扩展:

代码语言:javascript
复制
{-# 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

和以前一样,我们可以写

代码语言:javascript
复制
test' :: DiffInt
test' = weird 3 (toEnum 4)

但现在我们也可以写

代码语言:javascript
复制
-- difference ints over difference ints
type DiffDiffInt = DiffInt -> DiffInt

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

代码语言:javascript
复制
main = print $ fromEnum $ fromEnum test'

打印12

EDIT2更好的链接添加了。

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/27664213

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档