iTwenty
iTwenty

Reputation: 973

Haskell : How do I compose a recursive function that takes an element and gives gives back its list, but having different data type?

Say I have a data types as follows:

 data Cell = Cell (Maybe Player)
 data Board = Board [[Cell]]

Now I want to generate a recursive function like this:

 genBoard :: [Cell] -> Board
 genBoard [] = []
 genBoard c = (take 3 c) : (genBoard $ drop 3 c) -- takes list of 9 Cells and gives 3x3 list of cells

Obviously the above code fails as (:) can't prepend [Cell] to Board although technically, Board is nothing more than [[Cell]]. I need to have the Board as a separate data type to provide my own show function for it.

The best I have come up with so far is this:

genBoardList :: [Cell] -> [[Cell]]
genBoardList [] = []
genBoardList c =  (take 3 c) : (genBoardList $ drop 3 c)

boardListToBoard :: [[Cell]] -> Board
boardListToBoard [] = Board []
boardListToBoard s = Board s

genBoard :: [Cell] -> Board
genBoard = boardListToBoard . genBoardList

But this seems a bit too long and hackish to accomplish a seemingly simple thing. Any ideas how I can improve my code?

Upvotes: 3

Views: 240

Answers (3)

Jedai
Jedai

Reputation: 1477

Note that for types that are just wrappers around other types, it's better to use newtype (no runtime overhead, newtypes are just a compile-time facility) :

newtype Board = Board [[Cell]]

You can use record syntax to make the "deconstructor" for you too :

newtype Board = Board { boardContent :: [[Cell]] }

Then you can "lift" a function on [[Cell]] to a function on Board simply :

liftBoard f = Board . f . boardContent

genBoard cs  = liftBoard (take 3 cs :) (genBoard (drop 3 cs))

But all that may not be necessary at all, if you just use a type synonym. Do you really need a datatype for your board or did you just give it a name for convenience and documentation ? The advantage of using newtype or data is that you won't be able to mix [[Cell]] and Board but do you really use [[Cell]] for anything else than your boards ? You would also be unable to define new instance on Board if you use a type synonym, does that bother you ?

If the answer to both question is no, just use :

type Board = [[Cell]]

genBoard :: [Cell] -> Board
genBoard [] = []
genBoard cs =  take 3 cs : genBoard (drop 3 cs)

Upvotes: 1

ehird
ehird

Reputation: 40797

You just have to unwrap the list from the Board constructor using pattern matching, and then wrap it back up on each step; for example, using let...in:

genBoard :: [Cell] -> Board
genBoard [] = []
genBoard cs =
    let Board css = genBoard (drop 3 cs)
    in Board (take 3 cs : css)

Or, more idiomatically, a where clause:

genBoard :: [Cell] -> Board
genBoard [] = []
genBoard cs = Board (take 3 cs : css)
  where
    Board css = genBoard (drop 3 cs)

Another improvement would be to use pattern-matching instead of take and drop:

genBoard :: [Cell] -> Board
genBoard [] = []
genBoard (c0:c1:c2:cs) = Board $ [c0, c1, c2] : css
  where
    Board css = genBoard cs

You could also make it simpler using the split package:

genBoard :: [Cell] -> Board
genBoard = Board . splitEvery 3

Upvotes: 5

sth
sth

Reputation: 229874

You could use a lifting function that turns functions on [[Cell]] into functions on Board:

liftBoard :: ([[Cell]] -> [[Cell]]) -> Board -> Board
liftBoard f (Board css) = Board (f css)

With this you can turn the (take 3 c) : into something that can be used on the Board returned by the recursive genBoard call:

genBoard :: [Cell] -> Board
genBoard [] = Board []
genBoard c  = liftBoard (take 3 c :) $ genBoard (drop 3 c)

Upvotes: 2

Related Questions