使用Haskell记录编写可读代码很麻烦

时间:2018-11-15 21:55:58

标签: haskell

我对Haskell还是很陌生,而我一直在努力的一件事是使用记录编写可读代码。

我的具体问题是:

  1. 我还没有找到一种有效的策略来处理不同记录类型的字段之间的名称冲突。我发现我想要多个不同记录类型中的同一字段,并且名称冲突问题确实令人讨厌。我最终选择在所有字段上加一些前缀,这增加了冗长性并妨碍了可读性。
  2. 使用嵌套记录将导致冗长的代码。我发现
    someFunction(foo.bar, 2 * foo.bar.baz)
    以Java或C ++之类的语言可读性强。在Haskell中,我发现自己写这篇文章是为了完成同样的事情
    someFunction (fooBar foo) (2 * barBaz (fooBar foo))
    这在视觉上很难解析,并且对带有多个参数的函数的调用很快变得难以理解。为了使此内容更具可读性,我发现自己定义了一些中间值,这些中间值将从记录中提取字段,这更具可读性,但是增加了更多行代码,因此以不同的方式损害了可读性。

是否有更好的方法来使用更具可读性的记录,或者我应该做些什么吗?只是使用元组?用大量参数编写函数,而不是将相关值分组到记录中?还有吗?

1 个答案:

答案 0 :(得分:2)

解决该问题的一种方法(如注释中所建议)是使用镜片。使用microlensmicrolens-th包(开始时可能会更简单):

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}

import Data.List (nub)
import Lens.Micro ((^.), (^..))
import Lens.Micro.TH (makeFields)

newtype Name = Name String
  deriving Eq

data Person = Person { _personName :: Name }
makeFields ''Person

data Species = Dog | Cat
  deriving Eq

data Pet = Pet { _petName :: Name, _petSpecies :: Species }
makeFields ''Pet

-- ^. is an infix operator for view
uniquePersonNames :: [Person] -> [Name]
uniquePersonNames ps = nub (map (\p -> p ^. name) ps)

dogs :: [Pet] -> [Pet]
dogs ps = filter (\p -> p ^. species == Dog) ps

data Concert = Concert
  { _concertPerformers :: [Person]
  , _concertAttendees :: [Person]
  }
makeFields ''Concert

-- ^.. is an infix operator for toListOf
performerNames :: Concert -> [Name]
performerNames c = c ^.. performers . traverse . name

data House = House { _housePeople :: [Person], _housePet :: Pet}
makeFields ''House

houseSound :: House -> String
houseSound h = case h ^. pet . species of
    Dog -> "Woof!"
    Cat -> "Meow!"

这里有多种资源,可进一步了解透镜和其他种类的光学器件。一种特别适合初学者的资源Control.Lens.Tutorial

请注意,这种方法会导致难以理解的类型错误(我相信generic-lens库具有更好的错误消息,但我没有使用过),尤其是如果您盲目使用它们的话。我建议坚持使用基础知识(如链接教程中所述)-这将涵盖您的大部分用例。