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.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With