Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A more succinct way to map functions onto fields of an algebraic datatype?

I have a datatype with lots of fields:

data ManyFields a b c d .. = MF { f1 :: a, f2 :: b, f3 :: c .. }

Problem One

How do I map function onto each field while avoiding implementing a map function for each one. For example, this looks very tedious and non-idiomatic:

-- | Note I am explicitly constructing ManyField after mapping a function onto the field
-- | This looks bad
mapf1 :: (a -> a1) -> ManyFields a b c ..  -> ManyFields a1 b c ..
mapf1 g mf = MF (g . f1 $ mf) (f2 mf) ..

-- | Repeat for each field
mapf2 :: (b -> b1) -> ManyFields a b c .. -> ManyFields a b1 c ...

I think some sort of higher-order function that abstract out the pattern of constructor . (mapfunction f) would cut down on the boilerplate, but is there a better solution?

Problem Two

If I want to zip many ManyFields together and map a function of arbitrary arity onto each field, does this sound like it could be an instance of some type class?

Use case:

(==) `mapFunction` mf1 `pairWiseZipField` mf2 

It kind of looks like an applicative to me, but again I'm not sure how to implement fmap onto this type.

like image 546
xiaolingxiao Avatar asked Jun 07 '13 01:06

xiaolingxiao


Video Answer


1 Answers

You can't really do this with a standard function. The best approach is to have a map function for each field. Happily, you can generate these automatically with a bit of template haskell from the lens library. It would look something like this:

data ManyFields a b c d = MF { _f1 :: a, _f2 :: b, _f3 :: c, _f4 :: d }

makeLenses ''ManyFields

This generates a lens for each field of ManyFields. The lens is a simple construct that allows you to both access and change the value there--the changes can even be polymorphic, just like map! Note how each field is prefixed with an underscore: the lens has the same name as the field minus the underscore.

You can now access values like this:

> foo = MF 'a' "b" 3 False
> foo^.f1
'a'

You can set values using the set operator. When used with a lens, it creates a setter function:

> :t set f1
set f1 :: a' -> ManyFields a b c d -> ManyFields a' b c d

To actually use it, you could do this:

> set f1 () foo
MF () "b" 3 False

Since you have a getter and a setter, writing a map function is pretty trivial. Happily, we don't even have to do this: the library provides a function called over:

> :t over f1 
over f1 :: (a -> a') -> ManyFields a b c d -> ManyFields a' b c d

If you like infix operators more, set can also be called .~ and over can be called %~. (The latter has a mnemonic: % is mod, or "modify" :P.) This is also useful with the & operator which is just $ flipped. So the following two versions are equal:

> over f1 ord foo
MF 97 "b" 3 False
> foo & f1 %~ ord
MF 97 "b" 3 False

I personally think the operators are a bit much. Unless you're going to be using lenses everywhere, I would stick to set and over.

I don't know of a good solution for zipping functions the way you described. But do take a look through the rest of the lens library--it's pretty large, and you never know what you'll find!

like image 182
Tikhon Jelvis Avatar answered Nov 24 '22 03:11

Tikhon Jelvis