Reputation: 353
I have an algebraic data type Newb defined like below. Now I want to write a custom map function for it without using recursion.Further I have a foldNewb function which could also help.
data Newb a = Leaf a | Node [Newb a]
foldNewb:: (a->b)->([b]->b)->Newb a -> b
foldNewb f _ (Leaf a) = f a
foldNewb f1 f2 (Node a) = f2 (map (foldNewb f1 f2) a)
Newbmap :: (a->b)-> Newb a -> Newb b
Newbmap f (Leaf a) = (Leaf (f a))
Newbmap f (Node a) = (Node (foldNewb f concat a))
above is my attempt at implementing this function. I can't get any further than this and I don't understand what I'm doing wrong here. Any help would be appreciated.
Upvotes: 2
Views: 202
Reputation: 34840
Without using foldNewb
:
data Newb a = Leaf a | Node [ Newb a]
deriving (Show)
newbMap :: (a -> b) -> Newb a -> Newb b
newbMap f (Leaf a) = Leaf (f a)
-- since a :: [Newb a] in the below clause, we can map (newbMap f) over the elements
newbMap f (Node a) = Node (map (newbMap f) a)
tree = Node [ Leaf 1, Node [Leaf 2, Leaf 3], Leaf 4]
mapped = newbMap (+1) tree -- Node [Leaf 2,Node [Leaf 3,Leaf 4],Leaf 5]
Upvotes: 1
Reputation: 70297
tl;dr Here's your function. But I recommend reading on to see how I came up with this, so you can work out the thought process.
newbmap :: (a->b)-> Newb a -> Newb b
newbmap f = foldNewb (Leaf . f) Node
You're pretty close, and you're using foldNewb
correctly, but you're overthinking it.
First, you can't name a function Newbmap
. Capital names are reserved for types. So we'll call it newbmap
. Now, foldNewb
already handles both of the cases Leaf
and Node
, so we shouldn't have to pattern match in newbmap
at all. In fact, your first newbmap
case does exactly what foldNewb
would do anyway, so let's just consider the second case.
newbmap :: (a->b)-> Newb a -> Newb b
newbmap f (Node a) = (Node (foldNewb f concat a))
We want to fold over our data structure. In particular, we want our fold call to completely produce the new data structure. We shouldn't need to explicitly use Node
at the end, since, again, foldNewb
already does that work for us.
newbmap :: (a->b)-> Newb a -> Newb b
newbmap f a = foldNewb f concat a
Now in the first case, we need a function a -> Newb b
(since the result is going to be of type Newb b
). You've passed f :: a -> b
, which is very close to what you want. We simply need to compose it with a function b -> Newb b
, and Leaf
will do exactly that.
newbmap :: (a->b)-> Newb a -> Newb b
newbmap f a = foldNewb (Leaf . f) concat a
For the second argument, you want [Newb b] -> Newb b
, which again is very easily done by Node
.
newbmap :: (a->b)-> Newb a -> Newb b
newbmap f a = foldNewb (Leaf . f) Node a
And (although it doesn't make a difference), we can pointfree the final argument.
newbmap :: (a->b)-> Newb a -> Newb b
newbmap f = foldNewb (Leaf . f) Node
So there's a working newbmap
function. Now, as for how I came up with all of those types, if you're using GHC, there's a very helpful feature called type holes that you can use to identify what sort of types you need. So (and I did exactly this when debugging your function) if you write
newbmap :: (a->b)-> Newb a -> Newb b
newbmap f = foldNewb _1 _2
then you'll get very specific GHC messages telling you _1 :: a -> Newb b
and _2 :: [Newb b] -> Newb b
. Then your challenge is simply to find functions with those particular types. And this is where I came up with Leaf . f
and Node
.
Upvotes: 5