Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 294
o1968673 Avatar asked Apr 05 '17 20:04

o1968673


People also ask

What does map in Haskell do?

map is a function that takes two parameters: a function and a list of elements. The type signature of map is (a -> b) -> [a] -> [b] . The (a -> b) part is the function you pass to map , we will call it f . f takes one value and returns another that may be of a different type.

What is map function programming?

In many programming languages, map is the name of a higher-order function that applies a given function to each element of a collection, e.g. a list or set, returning the results in a collection of the same type. It is often called apply-to-all when considered in functional form.

What does in do in Haskell?

in goes along with let to name one or more local expressions in a pure function.

What is show in Haskell?

The shows functions return a function that prepends the output String to an existing String . This allows constant-time concatenation of results using function composition.


2 Answers

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.

like image 53
Alec Avatar answered Oct 30 '22 15:10

Alec


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)

like image 23
Pedro Castilho Avatar answered Oct 30 '22 13:10

Pedro Castilho