o1968673
o1968673

Reputation: 133

Does Haskell provide a way to map a function to a data member?

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

Answers (2)

Alec
Alec

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

Pedro Castilho
Pedro Castilho

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

Related Questions