Reputation: 133
I am a Haskell rookie and I often find myself having to decompose a data with pattern matching only to apply a function to one of its member and then reassemble it.
Say I have:
data Car = Car { gas :: Int, licensePlate :: String }
and I want it to halve its gas when it drives, and refuel it, I'm doing:
mapGas:: (Int -> Int) -> Car -> Car
mapGas f (Car aGas aLicensePlate) = Car (f aGas) aLicensePlate
drive:: Car -> Car
drive = mapGas (flip div 2)
refuel:: Int -> Car -> Car
refuel = mapGas . (+)
Is there a way to do just that without having to define the auxiliary function mapGas? Since it can become rather bothersome having to write a map function for every member of the data when it's made of many fields. I know it is possible to assign a value to one of the members with accessors:
runOutOfFuel:: Car -> Car
runOutOfFuel aCar = aCar { gas = 0 }
Is it possible to map a function with accessors too? if so, how?
Upvotes: 12
Views: 887
Reputation: 32309
Using just the core libraries? No. But with the widely used lens
package, yes. Here is what that looks like in your case:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens.TH
import Control.Lens
data Car = Car { _gas :: Int, _licensePlate :: String }
makeLenses ''Car
Now, you can easily get/set/modify fields that are nested in data structures.
runOutOfFuel:: Car -> Car
runOutOfFuel = gas .~ 0
drive:: Car -> Car
drive = gas %~ (`div` 2)
refuel:: Int -> Car -> Car
refuel c = gas +~ c
The magic here is that makeLenses ''Car
generates gas
and licensePlate
functions that are similar (but more powerful) to your mapGas
(in fact, mapGas = (gas %~)
). Getting started with lens
is pretty daunting, but I recommend just reading the examples section.
Upvotes: 13
Reputation: 10512
There is no language feature which does this, but, as with many things in Haskell, the core language is powerful enough that this can be implemented in a simple and elegant way.
The solution for what you are looking for is a kind of value called a lens. A lens does exactly what you want: It allows you to take any kind of abstract data and apply a function on a part of it, getting the entire data value with the modified part included as a result.
There's an introduction to lenses I quite like here. To use the examples included, you'll need the lens
package. (Or this one if you're using Stack)
Upvotes: 4