Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Haskell how to pass a constructor as a parameter and match over it

I have this code

data Container = Box Int | Bag Int


inBox :: [Container] -> Int
inBox [] = 0
inBox (x:ls) | (Box i) <- x = i + inBox ls
             | otherwise = inBox ls


inBag :: [Container] -> Int
inBag [] = 0
inBag (x:ls) | (Bag i) <- x = i + inBag ls
             | otherwise = inBag ls

Clearly InBox and InBag have the same structure. I would like to make a function that encompass both of them. What I don't know how to get is to pass the constructor (either Box or Bag) as a parameter.

Ideally, the general function woul look something like this:

inSome :: Constructor -> [Container] -> Int
inSome con [] = 0
inSome con (x:ls) | (con i) <- x = i + inSome con ls
                  | otherwise = inSome con ls

Obviously that doesn't work because constructor is not a defined type here. How can I do it?

One idea is to pass it as a function like so:

inSome :: (Int -> Container) -> [Container] -> Int
inSome _ [] = 0
inSome con (x:ls) | (con i) <- x = i + inSome ls
                  | otherwise = inSome ls

But then I get the error:

Parse error in pattern: con

Because it cannot match on functions like that.

The reason I want to do this is because I have a complicated data type that includes binary operations (e.g. +, #, ::, etc...) I have several functions that are almost identical for these constructors. I would hate to write all of them and modify them all together. There must me a way to do it in a function. Maybe someone can suggest another approach in the comments?

like image 498
Skuge Avatar asked Dec 01 '22 17:12

Skuge


2 Answers

You can avoid using pattern matching here entirely.

data Container = Box Int | Bag Int

unBox, unBag :: Container -> Maybe Int

unBox (Box i) = Just i
unBox _       = Nothing

unBag (Bag i) = Just i
unBag _       = Nothing

The type of these functions captures the need to get the contained Int out while switching on the structure of the Container. This can then be used to build the functions you desire.

inSome :: (Container -> Maybe Int) -> [Container] -> Int
inSome get []     = 0
inSome get (x:ls) = fromMaybe 0 (get x) + inSome ls

inBag = inSome unBag
inBox = inSome unBox

As leftroundabout noted, the pattern of "get or fail" is (massively) generalized in the concept of the Lens, or, in this case, the Prism. In general, Prisms can form a weak kind of first-class pattern, but their use here would be certain overkill.

like image 59
J. Abrahamson Avatar answered Dec 15 '22 23:12

J. Abrahamson


You might like first-class-patterns, a package that lets you pass and munge patterns.

like image 41
Daniel Wagner Avatar answered Dec 16 '22 00:12

Daniel Wagner