例如,
data CampingStuff = Apple String Int Bool
| Banana String Int
| Pineapple String String
| Table String Bool Int
| Chairs String Int Int Int
我想有一个查询功能
pickStuff :: [CampingStuff] -> ??? -> [CampingStuff]
我想传递给Apple
,然后pickStuff
会过滤掉所有的东西,比如
Apple "Jane" ...
Apple "Jack" ...
Apple "Lucy" ...
我能想到的是
pickStuffs stuffs dummyStuff
= filter
(\x ->
(x == dummyStuff)
stuffs
pickStuffs stuffs (Apple "" 0 False)
instance Eq CampingStuff where
compare (Apple name1 number1 b1) (Apple name2 number2 b2)
= True
其缺点是:
- 将额外的参数传递给伪值是不优雅的,并且没有任何意义
"" 0 False
- 它必须实现Eq类型类中的所有值构造函数(Apple、Table、Chair)
- 它是不可扩展的,因为在未来,我想过滤掉所有的苹果从简
- 像这样
(Apple "Jane" _ _)
感谢您阅读这篇文章,并感谢任何帮助如何过滤这个[CampingStuff]
的Data Constructor
像Apple/Table
?
2条答案
按热度按时间5vf7fwbs1#
问题是,不饱和构造函数不能通过值进行比较。你唯一能做的就是调用它们或对它们进行模式匹配。所以,如果你想要一个测试苹果的函数,它必须与测试香蕉的函数完全不同--它们不能共享任何代码,因为它们必须与一组不同的模式进行比较。
如果你重构你的类型以去除明显的重复,这一切都会变得容易得多,只剩下饱和的值构造函数。生成的Eq示例是你比较类型所需要的全部:
然后,您可以通过组合几个函数轻松编写
CampingStuff -> Bool
类型的函数。并使用它来筛选列表:
在评论中,你问:如果我的构造函数不是统一的,所以我不能把所有的东西都提取到一个包含枚举的产品类型中,那该怎么办?
我认为,在这种情况下,无论如何实现
pickStuff
函数,您都不会对它的结果感到满意。让我们想象一个更简单的类型:现在,你可能希望过滤一个
[Light]
,使它只包含那些亮着的灯,而不管它们的颜色。好的,我们可以实现它。我们甚至不用担心泛化,因为类型太小了:现在你有了
lights :: [Light]
,你可以得到onLights = ons lights :: [Light]
。太神奇了。接下来你会用onLights
做什么呢?也许你想数一下每种颜色有多少种:colorCounts
有一个问题:它假设所有的灯都是开的,但是在类型系统中并不能保证这一点,所以你可能不小心调用了colorCounts ls
而不是colorCounts (ons ls)
,它会编译,但是在运行时会给予你一个错误。更好的做法是在知道如何处理结果时进行模式匹配。这里,这是
colorCounts
的内部:只需为Off
添加一个case,并使用mapMaybe
而不是map
,这样您就有机会丢弃不喜欢的值:对于更复杂的类型,同样的参数也适用:在您准备好处理可能找到的 * 所有 * 信息之前,不要对值进行模式匹配。
当然,处理此类信息的一种方法是将其放入一个新的类型中,该类型只包含您想要的信息。
这样,您就不可能混淆“filter”函数的输入和输出:输出与原始的
Light
类型明显分离,其值只能来自On
lights。通过为每个构造函数提取一个新的产品类型,可以对CampingStuff类型执行同样的操作:你需要为
asApple
和asBanana
等分别使用不同的函数。这看起来很麻烦,我并不完全反对,但实际上人们并不需要像这样的大量函数。通常情况下,最好按照我之前描述的那样做:延迟模式匹配直到您知道如何处理结果。clj7thdc2#
对于您想要的函数,可以创建如下函数
然后使用
filter isApple
。当您想按Jane进行筛选时,您可以为每种类型添加另外5个函数,例如isAppleFrom :: String -> CampingStuff -> Bool
并执行filter (isAppleFrom "Jane")
。另一种方法如下:
也就是说,从数据类型中分离查询。上面是一个例子,可能写得更好。