Предоставляет ли Haskell способ сопоставить функцию с элементом данных?
Я новичок Haskell, и мне часто приходится разлагать данные с помощью сопоставления с образцом только для применения функции к одному из ее членов, а затем для ее сборки.
Скажем, у меня есть:
data Car = Car { gas :: Int, licensePlate :: String }
и я хочу, чтобы он уменьшил наполовину свой газ, когда он водит, и заправьте его, я делаю:
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 . (+)
Есть ли способ сделать это без определения вспомогательной функции mapGas? Так как может стать довольно назойливым, нужно написать функцию отображения для каждого члена данных, когда она сделана из многих полей. Я знаю, что можно назначить значение одному из членов с помощью аксессуаров:
runOutOfFuel:: Car -> Car
runOutOfFuel aCar = aCar { gas = 0 }
Можно ли также сопоставить функцию с аксессуарами? если да, то как?
Ответы
Ответ 1
Использование только основных библиотек? Нет. Но с широко используемым пакетом lens
, да. Вот что это выглядит в вашем случае:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens.TH
import Control.Lens
data Car = Car { _gas :: Int, _licensePlate :: String }
makeLenses ''Car
Теперь вы можете легко получить/установить/изменить поля, вложенные в структуры данных.
runOutOfFuel:: Car -> Car
runOutOfFuel = gas .~ 0
drive:: Car -> Car
drive = gas %~ (`div` 2)
refuel:: Int -> Car -> Car
refuel c = gas +~ c
Магия здесь заключается в том, что makeLenses ''Car
генерирует функции gas
и licensePlate
, которые схожи (но более мощные) с вашим mapGas
(фактически, mapGas = (gas %~)
). Начало работы с lens
довольно сложно, но я рекомендую просто прочитать раздел examples.
Ответ 2
Не существует языковой функции, которая делает это, но, как и во многих вещах в Haskell, основной язык достаточно мощный, что это можно реализовать простым и элегантным способом.
Решение для того, что вы ищете, - это своего рода ценность, называемая объективом. Объектив делает именно то, что вы хотите: он позволяет вам брать любые абстрактные данные и применять функцию на части, получая в результате все значение данных с измененной частью.
Там введение в линзы мне очень нравится здесь. Чтобы использовать приведенные примеры, вам понадобится пакет lens
. (Или это, если вы используете Stack)