How should the cloned sheep type be constructed

Viewed 75

So in the explanation of Haskell monads they give the example of cloned sheeps. However they only give a partial implementation of the data type:

type Sheep = ...

father :: Sheep -> Maybe Sheep
father = ...

mother :: Sheep -> Maybe Sheep
mother = ...

Which makes it quite difficult to experiment and understand the usage of the monads such as comb:

-- comb is a combinator for sequencing operations that return Maybe
comb :: Maybe a -> (a -> Maybe b) -> Maybe b
comb Nothing  _ = Nothing
comb (Just x) f = f x

How should Sheep be implemented?

3 Answers

One minimal implementation for Sheep is

data Sheep = Sheep {mother :: Maybe Sheep, father :: Maybe Sheep}

A more sane implementation, where you could have siblings (which can be distinguished by their name) would be

data Sheep = Sheep {name :: String, mother :: Maybe Sheep, father :: Maybe Sheep}

This answer is to address the clarification that you gave in the comments:

the given definition is of type and the answer is of data, which I don't understand yet how this can happen.


Haskell has a few different ways of declaring types.

The type keyword is used to declare a type alias - similar to typedef or using in C++. One could, theoretically, as an exercise in futility, declare Sheep as an alias of String, with the implied understanding that the String would signify the sheep's name, and then there is a "global" list of parents for each sheep, which the mother and father functions would use for lookup:

type Sheep = String

allParents :: [(Sheep, (Sheep, Sheep))]
allParents =
  [ ("dolly", ("rufus", "peggy"))
  , ("peggy", ("ramses", "woolly"))
  , ...
  ]

mother :: Sheep -> Maybe Sheep
mother s = fst <$> lookup s allParents

father :: Sheep -> Maybe Sheep
father s = snd <$> lookup s allParents

But this would be very unusual and not functional at all. I think this would be a prime example of a gorilla holding a banana and the entire jungle.

A more Haskell way of modeling the situation would be to declare Sheep as a data structure with (at least) two fields - mother and father. For this sort of thing, one uses the keyword data:

data Sheep = Sheep (Maybe Sheep) (Maybe Sheep)

father :: Sheep -> Maybe Sheep
father (Sheep f _) = f

mother :: Sheep -> Maybe Sheep
mother (Sheep _ m) = m

But defining these accessor functions for every field of your type is quite mundane, so Haskell has a shorter syntax for it:

data Sheep = Sheep { father :: Maybe Sheep, mother :: Maybe Sheep }

this would declare the same structure with two fields, plus automatically create the accessor functions that work the same way as in the previous example.

To preempt your next question, I should also mention a third way of defining types - newtype, which is like data, but only for structures with exactly one field, and is just a performance optimization.


Don't get hung up on the specific keyword used. The article just needed to make a point that there is this type and two functions to go with it, and it doesn't really matter how they're defined. The article had to use some keyword to make this point, and they chose type. A more or less arbitrary choice.

The definition of Sheep truly doesn't matter. It's the definitions of mother and father that use the definition that is important. The following would work

type Sheep = Int

mother :: Sheep -> Maybe Sheep
mother 0 = Nothing
mother 1 = Nothing
mother n = Just (n `div` 2)

father :: Sheep -> MaybeSheep
father 0 = Nothing
father 1 = Nothing
father n = Just (n `mod` 2)

as would

type Sheep = String

mother :: Sheep -> Maybe Sheep
mother (x:_:rest) = Just [x]
mother _ = Nothing

father :: Sheep -> Maybe Sheep
father (_:x:rest) = Just [x]
father _ = Nothing

(In neither case am I too concerned about what mother and father mean; the point it to illustrate that the types are what matter.)

comb itself actually doesn't care what mother and father return; it simply handles the problem of trying to find the mother or father of a non-sheep. You can use mother and father with comb as long as they have the correct type.

comb (Just name) mother == mother name -- name :: Sheep, so mother name :: Maybe Sheep
comb Nothing mother == Nothing -- Nothing :: Maybe Sheep in both cases
Related