Quantcast
Channel: CSDN博客推荐文章
Viewing all articles
Browse latest Browse all 35570

[学习笔记-Haskell」Chapter 12 MONOIDS

$
0
0

Chapter 12 MONOIDS

This chapter features another useful and fun type class: Monoid. This type class is for types whose values can be combined together with a binary operation.

1 Wrapping an Existing Type into a New Type

The newtype keyword in Haskell is made exactly for cases when we want to just take one type and wrap it in something to present it as another type. In the actual libraries, ZipList a is defined like this:

newtype ZipList a = ZipList { getZipList :: [a] }

1.1 Using newtype to Make Type Class Instances

we can newtype our tuple in such a way that the second type parameter represents the type of the first component in the tuple

newtype Paire b a = Paire { getPair :: (a,b) }

And now we can make it an instance of Functor so that the function is mapped over the first component.

instance Functor (Pair c) where
    fmap f (Pair (x, y)) = Pair (f x, y)
ghci>getPair $ fmap (*100) (Pair (2,3))
(200,3)
ghci>getPair $ fmap reverse (Pair ("asdf",2))
("fdsa",2)

1.2 On newtype Laziness

The undefined value in Haskell represents an erroneous computation. If we try to evaluate it (that is, force Haskell to actually compute it) by printing it to the terminal, Haskell will throw a hissy fit (technically referred to as an exception):

ghci>undefined 
*** Exception: Prelude.undefined

Here's another example.

ghci>head  [32,1,undefined,32,undefined]
32

This time,no exception,because Haskell is lazy,when we use head, we don't need to evaluate undefined which is not the "head".

Now consider the following type and a function:

data CoolBool = CoolBool { getCoolBool :: Bool }
helloMe :: CoolBool -> String
helloMe (CoolBool _) = "hello"

Let's test it.

ghci>helloMe (CoolBool True )
"hello"
ghci>helloMe undefined 
"*** Exception: Prelude.undefined

Why did this exception happen? Types defined with the data keyword can have multiple value constructors (even though CoolBool has only one). So in order to see if the value given to our function conforms to the (CoolBool _) pattern, Haskell must evaluate the value just enough to see which value constructor was used when we made the value. And when we try to evaluate an undefined value, even a little, an exception is thrown.

Instead of using data keyword, we use newtype.

newtype CoolBool = CoolBool { getCoolBool :: Bool }
helloMe :: CoolBool -> String
helloMe (CoolBool _) = "hello"

Let's test it again.

ghci>helloMe (CoolBool True )
"hello"
ghci>helloMe undefined 
"hello"

It worked! Hmmm, why is that? Well, as you’ve learned, when you use newtype, Haskell can internally represent the values of the new type in the same way as the original values. because Haskell knows that types made with the newtype keyword can have only one constructor, it doesn’t need to evaluate the value passed to the function to make sure that the value conforms to the (CoolBool _) pattern, because newtype types can have only one possible value constructor and one field!

1.3 type vs. newtype vs. data

At this point, you may be a bit confused about the differences between type, data, and newtype, so let’s review their uses. The type keyword is for making type synonyms. We use type synonyms when we want to make our type signatures more descriptive.

type IntList = [Int]

The newtype keyword is for taking existing types and wrapping them in new types, mostly so it’s easier to make them instances of certain type classes. Suppose we make the following newtype:

newtype CharList = CharList { getCharList :: [Char] }

We can’t use ++ to put together a CharList and a list of type [Char]. We can’t even use ++ to put together two CharList lists, because ++ works only on lists, and the CharList type isn’t a list, even though it could be said that CharList contains a list. We can, however, convert two CharLists to lists, ++ them, and then convert that back to a CharList. In practice, you can think of newtype declarations as data declarations that can have only one constructor and one field. If you catch yourself writ- ing such a data declaration, consider using newtype.

2 About Those Monoids

2.1 The Monoid Type Class

A monoid is made up of an associative binary function and a value that acts as an identity with respect to that function.For example,1 is the identity with respect to *, and [] is the identity with respect to ++. There are a lot of other monoids to be found in the world of Haskell, which is why the Monoid type class exists. Let’s see how the type class is defined:

class Monoid m where
  mempty :: m
  mappend :: m -> m -> m
  mconcat :: [m] -> m
  mconcat = foldr mappend mempty

First, we see that only concrete types can be made instances of Monoid, because the m in the type class definition doesn’t take any type parameters.

First,*mempty* represents the identity value for a particular monoid.

Next up, we have mappend, which, as you’ve probably guessed, is the bi- nary function. It takes two values of the same type and returns another value of that same type.

The last function in this type class definition is mconcat. It takes a list of monoid values and reduces them to a single value by using mappend between the list’s elements.

2.2 The Monoid Laws

  • mempty `mappend` x = x
  • x `mappend` mempty = x
  • (x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)

3 Meet Some Monoids

3.1 Lists Are Monoids

instance Monoid [a] where
  mempty = []
  mappend = (++)

Notice that we wrote instance Monoid [a] and not instance Monoid [], because Monoid requires a concrete type for an instance.

ghci>[1,2,3] `mappend` mempty 
[1,2,3]
ghci>mempty `mappend` [1,2,3]
[1,2,3]
ghci>"hi" `mappend` ",John"
"hi,John"
ghci>("1" `mappend ` "2") `mappend ` "3"
"123"
ghci>"1" `mappend ` ("2" `mappend ` "3")
"123"

3.2 Product and Sum

We already examined one way for numbers to be considered monoids: Just let the binary function be * and the identity value be 1. Another way for numbers to be monoids is to have the binary function be + and the identity value be 0. With two equally valid ways for numbers to be monoids, which way do we choose? Well, we don’t have to pick. Remember that when there are several ways for some type to be an instance of the same type class, we can wrap that type in a newtype and then make the new type an instance of the type class in a different way. The Data.Monoid module exports two types for this: Product and SumProduct is defined like this:

newtype Product a = Product { getProduct :: a}
                  deriving (Eq, Ord, Read, Show, Bounded)

It's instance for Monoid goes something like this:

instance Num a => Monoid (Product a) where
  mempty = Product 1
  Product x `mappend` Product y = Product (x*y)
ghci> Product 3 `mappend` Product 5
Product {getProduct = 15}
ghci>getProduct $ Product 3 `mappend` Product 5
15
ghci>getProduct . mconcat . map Product $ [3,4,5]
60

3.3 Any and All

Another type that can act like a monoid in two distinct but equally valid ways is Bool. The first way is to have the function ||, which represents a logical OR, act as the binary function along with False as the identity value. The Any newtype constructor is an instance of Monoid in this fashion. It’s defined like this.

newtype Any = Any { getAny :: Bool }
            deriving (Eq, Ord, Read, Show, Bounded)
instance Monoid Any where
  mempty = Any False
  Any x `mappend` Any y = Any (x || y)
ghci>getAny $ Any True `mappend` Any False
True
ghci>getAny $ Any False `mappend` Any False
False
ghci>getAny $ mempty `mappend` mempty
False
ghci>getAny $ mempty `mappend` Any True
True
ghci>getAny $ mempty `mappend` Any False
False
ghci>getAny . mconcat . map Any $ [False, False, False, True]
True

3.4 The Ordering Monoid

Remember the Ordering type? It’s used as the result when comparing things, and it can have three values: LT, EQ, and GT,

ghci>1 `compare` 2
LT
ghci>2 `compare` 2
EQ
ghci>3 `compare` 2
GT

Ordering is instance of Monoid too.

instance Monoid Ordering where
  mempty = EQ
  LT `mappend` _ = LT
  EQ `mappend` y = y
  GT `mappend` _ = GT
ghci>LT `mappend` GT
LT
ghci>GT `mappend` LT
GT
ghci>mempty `mappend` LT
LT

Okay, so how is this monoid useful? Here’s one way to write this:

lengthCompare :: String -> String -> Ordering
lengthCompare x y = let a = length x `compare` length y
                        b = x `compare` y
                    in if a == EQ then b else a

But by employing our understanding of how Ordering is a monoid, we can rewrite this function in a much simpler manner:

lengthCompare :: String -> String -> Ordering
lengthCompare x y = (length x `compare` length y) `mappend`
                    (x `compare` y)

3.5 Maybe the Monoid

Let’s take a look at the various ways that Maybe a can be made an instance of Monoid and how those instances are useful. Here’s the instance declaration

instance Monoid a => Monoid (Maybe a) where
  mempty = Nothing
  Nothing `mappend` m = m
  m `mappend` Nothing = m
  Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)

f we mappend two Just values, the contents of the Justs are mappended and then wrapped back in a Just. We can do this because the class constraint ensures that the type of what’s inside the Just is an in- stance of Monoid.

ghci>Nothing `mappend` Just "andy"
Just "andy"
ghci>Just LT `mappend` Nothing
Just LT
ghci>Just (Sum 3) `mappend` Just (Sum 4)
Just (Sum {getSum = 7})

4 Folding with Monoids

One of the more interesting ways to put monoids to work is to have them help us define folds over various data structures. So far, we’ve done folds over lists, but lists aren’t the only data structure that can be folded over. We can define folds over almost any data structure. Trees especially lend themselves well to folding.

Because there are so many data structures that work nicely with folds, the Foldable type class was introduced. Much like Functor is for things that can be mapped over, Foldable is for things that can be folded up!

It can be found in Data.Foldable, and because it exports functions whose names clash with the ones from the Prelude, it’s best imported qualified.

import qualified Data.Foldable as F

Let’s compare the types of Foldable’s foldr and foldr from Prelude to see how they differ

ghci>:t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
ghci>:t F.foldr
F.foldr :: F.Foldable t => (a -> b -> b) -> b -> t a -> b
ghci>foldr (*) 1 [1,2,3]
6
ghci>F.foldr (*) 1 [1,2,3]
6

Another data structures that support folds is the Maybe we all know and love!

ghci>F.foldr (+) 2 (Just 9)
11
ghci>F.foldr (||) False (Just True)
True
ghci>foldr (+) 2 (Just 9)
error...

Remember the tree data structure from Chapter 7?

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)

One way to make a type constructor an instance of Foldable is to just di- rectly implement foldr for it. But another, often much easier way, is to im- plement the foldMap function, which is also a part of the Foldable type class. The foldMap function has the following type:

foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m

This is how we make Tree an instance of Foldable:

instance F.Foldable Tree where
  foldMap f EmptyTree = mempty
  foldMap f (Node x l r) = F.foldMap f l `mappend`
                           f x           `mappend`
                           F.foldMap f r

Date: 2013-01-26 19:49:12 CST

作者:on_1y 发表于2013-1-26 19:47:54 原文链接
阅读:47 评论:0 查看评论

Viewing all articles
Browse latest Browse all 35570

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>