下面是一个简单的示例,我遇到了一些问题,包括更大的记录和执行某些数据操作的函数。在我的实际代码中name
是一个访问者,如:
name = snd . fst . fst
但是我不满足于可读性,因为name
并不是我想要检查的唯一一件事。是否有一种更简单,惯用的方法可以根据这个例子将逻辑从字段转换中解耦出来?
> data Athlete = Athlete { name :: String } deriving Show
> let registered = map Athlete ["John", "Mike"]
> let input = map Athlete ["John", "Ann"]
> filter (not . (`elem` (map name registered)) . name) input
[Athlete {name = "Ann"}]
部分问题是谓词不仅依赖于参数本身,也不一定检查Athlete
的相等性。由于我明确地不希望测试相等性,理想情况下,如果input
类型更改为以下内容,逻辑部分应保持不变:
> data AthleteInput = AthleteInput { name :: String } deriving Show
答案 0 :(得分:1)
我建议这样的事情:我们将使用Data.Map
来提供两个共享密钥空间的映射。构建这些Map
将对应于"谓词"部分;这将与计算交叉路口"完全分开。或其他组合功能。我们从与您相同的设置开始:
import Data.Map (Map)
import qualified Data.Map as M
data Athlete = Athlete { name :: String } deriving Show
registered = map Athlete ["John", "Mike"]
input = map Athlete ["John", "Ann"]
现在我们将构建两个地图。为了阐述,我会给他们命名;但在您的代码中,您可以选择内联其定义,以避免选择一个糟糕的名称。
registeredMap :: Map String Athlete
registeredMap = M.fromList [(name athlete, athlete) | athlete <- registered]
inputMap :: Map String Athlete
inputMap = M.fromList [(name athlete, athlete) | athlete <- input]
我在此强调,我们已经发生使用name
作为此处两种情况的选择器,并且发生以使用相同的类型{{1} }作为两个地图的值类型,但下一部分都不需要这些 - 只是巧合。密钥空间相同的关键非巧合位,否则交叉将没有意义。这解决了您以后需要更改一个访问者或另一个访问者或更改一个基础类型或另一个的设计问题。
现在,交集就像使用现有的库函数Athlete
一样简单。
intersection
这具有值registeredInputAthletes :: [Athlete]
registeredInputAthletes = M.elems (M.intersection registeredMap inputMap)
。
如果是需要,也可以使用[Athlete { name = "John" }]
来组合两个包含的值,而不是保留左手参数中的值并忽略右手参数。