Domanda

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.

È stato utile?

Soluzione

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!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top