在过去的几个月里,我一直在学习Haskell,我遇到了一个Monoid的例子,这让我感到困惑。
根据这些定义:
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
instance F.Foldable Tree where
foldMap f Empty = mempty
foldMap f (Node x l r) = F.foldMap f l `mappend`
f x `mappend`
F.foldMap f r
还有这棵树:
testTree = Node 5
(Node 3
(Node 1 Empty Empty)
(Node 6 Empty Empty)
)
(Node 9
(Node 8 Empty Empty)
(Node 10 Empty Empty)
)
ghci> F.foldl (+) 0 testTree
42
ghci> F.foldl (*) 1 testTree
64800
示例来源:http://learnyouahaskell.com/functors-application-functors-and-monoids#monoids
简而言之:它是foldmap
签名中的类型约束。
如果我们查看foldable
(更具体地说foldmap
)的源代码,我们会看到:
class Foldable (t :: * -> *) where
...
foldMap :: Monoid m => (a -> m) -> t a -> m
这意味着,如果我们声明树
是foldable
的成员(并不是树
具有*->*
),这意味着在该树上定义了foldmap
,例如:foldmap::Monoid m=>(a->m)->树a->m
。因此,这意味着结果类型(以及传递给foldmap
)M
的函数结果)必须是monoid
。
Haskell是静态类型化的:编译后,Haskell确切地知道每个函数实例中传递的类型。例如,这意味着它知道输出类型,以及如何处理它。
现在int
不是monoid
的实例。这里使用f.foldl(+)0 testtree
,这意味着您或多或少构造了一个“ad hoc”monoid。如果我们查看foldl
的源代码,这种方法就会奏效:
foldl :: (b -> a -> b) -> b -> t a -> b
foldl f z t = appEndo (getDual (foldMap (Dual . Endo . flip f) t)) z
这有很多围绕参数f
、z
和t
的逻辑。所以让我们先分解一下。
让我们首先看一下dual。远藤。翻转f
。这是:
helper = \x -> Dual (Endo (\y -> f y x))
dual
和endo
都是类型,每个构造函数都接受一个参数。因此,我们将f y x
的结果包装在dual(Endo…)
构造函数中。
我们将此用作foldmap
的函数。如果我们的f
具有a->b->a
类型,那么此函数具有b->Dual(endoa)
类型。因此,传递给foldmap
的函数的输出类型为dual(endoa)
。现在,如果我们检查源代码,我们会看到两件有趣的事情:
instance Monoid (Endo a) where
mempty = Endo id
Endo f `mappend` Endo g = Endo (f . g)
instance Monoid a => Monoid (Dual a) where
mempty = Dual mempty
Dual x `mappend` Dual y = Dual (y `mappend` x)
(请注意,它是Y`mappend`X
,而不是X`mappend`Y
)。
因此,这里发生的事情是,foldmap
中使用的mempty
是mempty=Dual mempty=Dual(Endo id)
。因此,封装标识函数的dual(Endo...)
。
此外,两个对偶的mappend
归结为endo
中值的函数组合。所以:
mempty = Dual (Endo id)
mappend (Dual (Endo f)) (Dual (Endo g)) = Dual (Endo (g . f))
-- specialized foldMap
foldMap f Empty = Dual (Endo id)
foldMap f (Node x l r) = Dual (Endo (c . b . a))
where Dual (Endo a) = foldMap f l
Dual (Endo b) = helper x
Dual (Endo c) = foldMap f l
这意味着如果我们有一棵树:
5
|- 3
| |- 1
| `- 6
`- 9
|- 8
`- 10
这将产生一个函数:
(Dual (Endo ( (\x -> f x 10) .
(\x -> f x 9) .
(\x -> f x 8) .
(\x -> f x 5) .
(\x -> f x 6) .
(\x -> f x 3) .
(\x -> f x 1)
)
)
)
(省略了身份,使它更干净)。这是getdual(foldMap(Dual.Endo.flip f))
的结果。但是现在我们需要postprocess这个结果。使用getdual
,我们获得包装在dual
构造函数中的内容。所以现在我们有:
Endo ( (\x -> f x 10) .
(\x -> f x 9) .
(\x -> f x 8) .
(\x -> f x 5) .
(\x -> f x 6) .
(\x -> f x 3) .
(\x -> f x 1)
)
( (\x -> f x 10) .
(\x -> f x 9) .
(\x -> f x 8) .
(\x -> f x 5) .
(\x -> f x 6) .
(\x -> f x 3) .
(\x -> f x 1)
)
f (f (f (f (f (f (f z 1) 3) 6) 5) 8) 9) 10
Haskell 的一些特色,像是纯粹性,高端函数,algebraic data types,typeclasses,这些让我们可以从更高的角度来看到 polymorphism 这件事。不像 OOP 当中需要从庞大的型态阶层来思考。我们只需要看看手边的型态的行为,将他们跟适当地 typeclass 对应起来就可以了。像 Int 的行为跟很多东西很像。好比说他可以比较相不相等,可以从大到小排列,也可以
本文向大家介绍Scala和Haskell之间的区别,包括了Scala和Haskell之间的区别的使用技巧和注意事项,需要的朋友参考一下 Scala和Haskell都是最近开发的现代编程语言。但是两者都是不同类型的编程语言。同样,两者都是为了实现不同的目的而创建的。 在这里,我们将看到编程语言之间的区别点。但是首先,学习一些编程语言的基础知识。 Scala编程语言 Scala是基于Java构建的通用
问题内容: 两者都是其类型是所有类型(无人居住)的交集的术语。两者都可以在代码中传递,而不会失败,直到尝试对其进行评估。我能看到的唯一区别是,在Java中,存在一个漏洞,可以对一个操作进行精确的评估,即引用相等比较(),而在Haskell 中,如果不抛出异常就无法进行评估。这是唯一的区别吗? 编辑 我真正想解决的问题是,为什么在Java 中包含如此明显的错误决定,Haskell如何逃避呢?在我看来
到目前为止,我们讨论的所有示例本质上都是静态的。在本章中,我们将学习如何Haskell与用户动态交互。学习Haskell中使用的不同输入和输出技术。 1. 文件和流 到目前为止,我们已经对程序本身中的所有输入进行了硬编码,在前面学习的内容中都是从静态变量获取输入。本小节中,我们学习如何从外部文件读取和写入。 创建一个文件并命名为abc.txt。接下来,在此文本文件中输入以下一行: 接下来,我们将编
Haskell是一种函数语言,它是严格类型化的,Haskell编译器在编译时知道整个应用程序中使用的数据类型。 1. 内置类型类 在Haskell中,每个语句都被视为数学表达式,并且此表达式的类别称为类型()。可以说是在编译时使用的表达式的数据类型。 要了解有关类型的更多信息,可以使用命令。以通用的方式可以将类型视为值,而可以将类型类视为一组相似类型的类型。在本章中,我们将学习不同的内置类型。 2
我必须写一个函数来求和一列数字的立方体。 这是我目前的代码: 问题是,当我运行它时,会出现以下错误: 没有因使用“it”而产生的(Num[t0])实例 在交互式GHCI命令的stmt中:打印它