【问题标题】:Does Haskell provide a way to map a function to a data member?Haskell 是否提供了一种将函数映射到数据成员的方法?
【发布时间】:2017-04-05 20:42:54
【问题描述】:

我是一名 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 }

是否也可以使用访问器映射函数?如果有,怎么做?

【问题讨论】:

  • 我觉得你应该看看lens的一些教程hackage.haskell.org/package/lens
  • 只是为了记录,没有镜头,我认为你能做的最好的是mapGas f = (\car -> car {gas = f (gas car)})。这至少避免提及其他领域。
  • @chi 在这些情况下我通常会尝试保持对称性:mapGas f = (\car@Car{ gas = g } -> car {gas = f g })。更长,但可以看到->两侧的gas字段。

标签: haskell functional-programming records


【解决方案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 生成的gaslicensePlate 函数与您的mapGas(实际上是mapGas = (gas %~))相似(但更强大)。开始使用lens 非常令人生畏,但我建议您只阅读examples 部分。

【讨论】:

  • Data.DataGHC.Genericsbasic 中可以用于此目的,但我同意 lens 通常是自然选择
  • 我也会推荐这个lens 教程(如果你想更好地理解lens):artyom.me/lens-over-tea-1
  • 我完全同意“相当令人生畏”的说法。有时我认为它有点过于笼统,超出了实际的常见情况。
【解决方案2】:

没有语言特性可以做到这一点,但是,与 Haskell 中的许多东西一样,核心语言足够强大,可以以简单而优雅的方式实现。

您正在寻找的解决方案是一种称为镜头的值。镜头完全可以满足您的需求:它允许您获取任何类型的抽象数据并在其中的一部分上应用函数,从而获得包含修改部分的整个数据值。

我很喜欢here的镜头介绍。要使用包含的示例,您需要 lens 包。 (Or this one if you're using Stack)

【讨论】:

  • 我不知道我是否会说镜头的存在证明了核心语言足够强大。镜头创建是在核心语言之外显式完成的,使用模板 haskell 生成一堆代码,因为核心语言不够强大,无法让这些代码以通用方式编写一次然后重用。
  • 您实际上可以在不使用模板的情况下创建镜头,只是这样做有点乏味,因此人们使用模板来简化它。至于强大,我的意思是很多需要硬编码到其他编程语言中的东西都可以在 Haskell 中定义。
  • @amalloy 镜头创建仅通过模板完成以减少样板。您可以手动编写所有镜头功能,这很糟糕。
  • @PedroCastilho @Daenyth 你错过了@amalloy 的观点。他知道镜头是如何工作的,他只是说核心语言不够强大,无法完成与 TH 相同的工作。可以想象让镜头自动工作(与定义记录时生成吸气剂的方式相同) - 但事实并非如此。希望一旦OverloadedLabels 支持设置器,这将改变。
猜你喜欢
  • 1970-01-01
  • 2011-01-25
  • 1970-01-01
  • 2018-10-17
  • 1970-01-01
  • 1970-01-01
  • 2021-07-06
  • 2020-05-16
相关资源
最近更新 更多