Soy un novato de Haskell y a menudo me encuentro teniendo que descomponer datos con coincidencia de patrones solo para aplicar una función a uno de sus miembros y luego volver a ensamblarla.

Digamos que tengo:

data Car = Car { gas :: Int, licensePlate :: String }

Y quiero que reduzca a la mitad su gas cuando conduzca, y lo cargue de combustible, estoy haciendo:

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 . (+)

¿Hay alguna manera de hacer eso sin tener que definir la función auxiliar mapGas? Dado que puede ser bastante molesto tener que escribir una función de mapa para cada miembro de los datos cuando se compone de muchos campos. Sé que es posible asignar un valor a uno de los miembros con accesores:

runOutOfFuel:: Car -> Car
runOutOfFuel aCar = aCar { gas = 0 }

¿Es posible mapear una función con accesores también? ¿si es así, cómo?

12
o1968673 5 abr. 2017 a las 23:42

2 respuestas

La mejor respuesta

¿Usando solo las bibliotecas principales? No. Pero con el ampliamente utilizado lens, sí. Así es como se ve en su caso:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens.TH
import Control.Lens

data Car = Car { _gas :: Int, _licensePlate :: String }

makeLenses ''Car

Ahora, puede obtener / establecer / modificar fácilmente campos que están anidados en estructuras de datos.

runOutOfFuel:: Car -> Car
runOutOfFuel = gas .~ 0

drive:: Car -> Car
drive = gas %~ (`div` 2)

refuel:: Int -> Car -> Car
refuel c = gas +~ c

La magia aquí es que makeLenses ''Car genera gas y licensePlate funciones que son similares (pero más poderosas) a su mapGas (de hecho, mapGas = (gas %~)). Comenzar con lens es bastante desalentador, pero recomiendo leer la sección ejemplos.

13
Alec 5 abr. 2017 a las 21:11

No existe una función de idioma que haga esto, pero, como ocurre con muchas cosas en Haskell, el lenguaje central es lo suficientemente potente como para que esto se pueda implementar de una manera simple y elegante.

La solución para lo que está buscando es un tipo de valor llamado lente . Una lente hace exactamente lo que desea: le permite tomar cualquier tipo de datos abstractos y aplicar una función en una parte de ellos, obteniendo como resultado todo el valor de los datos con la parte modificada incluida.

Hay una introducción a los lentes que me gusta bastante aquí. Para usar los ejemplos incluidos, necesitará el paquete lens. (O este si está utilizando Stack)

4
Pedro Castilho 5 abr. 2017 a las 20:53