如何在Haskell中筛选求和类型

h7appiyu  于 2022-11-14  发布在  其他
关注(0)|答案(2)|浏览(186)

例如,

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 ConstructorApple/Table

5vf7fwbs

5vf7fwbs1#

问题是,不饱和构造函数不能通过值进行比较。你唯一能做的就是调用它们或对它们进行模式匹配。所以,如果你想要一个测试苹果的函数,它必须与测试香蕉的函数完全不同--它们不能共享任何代码,因为它们必须与一组不同的模式进行比较。
如果你重构你的类型以去除明显的重复,这一切都会变得容易得多,只剩下饱和的值构造函数。生成的Eq示例是你比较类型所需要的全部:

data StuffType = Apple | Banana | Pineapple | Table | Chairs deriving Eq
data CampingStuff = Stuff { stuffType :: StuffType
                          , owner :: String
                          , quantity :: Int
                          }

然后,您可以通过组合几个函数轻松编写CampingStuff -> Bool类型的函数。

hasType :: StuffType -> CampingStuff -> Bool
hasType t s = stuffType s == t

并使用它来筛选列表:

pickStuff :: StuffType -> [CampingStuff] -> [CampingStuff]
pickStuff = filter . hasType

在评论中,你问:如果我的构造函数不是统一的,所以我不能把所有的东西都提取到一个包含枚举的产品类型中,那该怎么办?
我认为,在这种情况下,无论如何实现pickStuff函数,您都不会对它的结果感到满意。让我们想象一个更简单的类型:

data Color = Red | Green
data Light = Off | On Color

现在,你可能希望过滤一个[Light],使它只包含那些亮着的灯,而不管它们的颜色。好的,我们可以实现它。我们甚至不用担心泛化,因为类型太小了:

ons :: [Light] -> [Light]
ons = filter on
  where on Off = False
        on (On _) = True

现在你有了lights :: [Light],你可以得到onLights = ons lights :: [Light]。太神奇了。接下来你会用onLights做什么呢?也许你想数一下每种颜色有多少种:

import qualified Data.Map as M

colorCounts :: [Light] -> M.Map Color Int
colorCounts = M.fromListWith (+) . map getColor
  where getColor (On c) = (c, 1)

colorCounts有一个问题:它假设所有的灯都是开的,但是在类型系统中并不能保证这一点,所以你可能不小心调用了colorCounts ls而不是colorCounts (ons ls),它会编译,但是在运行时会给予你一个错误。
更好的做法是在知道如何处理结果时进行模式匹配。这里,这是colorCounts的内部:只需为Off添加一个case,并使用mapMaybe而不是map,这样您就有机会丢弃不喜欢的值:

colorCounts' :: [Light] -> M.Map Color Int
colorCounts' = M.fromListWith (+) . mapMabye getColor
  where getColor (On c) = Just (c, 1)
        getColor Off = Nothing

对于更复杂的类型,同样的参数也适用:在您准备好处理可能找到的 * 所有 * 信息之前,不要对值进行模式匹配。
当然,处理此类信息的一种方法是将其放入一个新的类型中,该类型只包含您想要的信息。

colorsOfOnLights :: [Light] -> [Color]
colorsOfOnLights = mapMaybe go
  where go Off = Nothing
        go (On c) = Just c

这样,您就不可能混淆“filter”函数的输入和输出:输出与原始的Light类型明显分离,其值只能来自On lights。通过为每个构造函数提取一个新的产品类型,可以对CampingStuff类型执行同样的操作:

data CampingStuff = Apple AppleData
                  | Banana BananaData
                  -- ...

data AppleData = AppleData String Int Bool
data BananaData = BananaData String Int
-- ...

asApple :: CampingStuff -> Maybe AppleData
asApple (Apple ad) = Just ad
asApple _ = Nothing

apples :: [CampingStuff] -> [AppleData]
apples = mapMaybe asApple

你需要为asAppleasBanana等分别使用不同的函数。这看起来很麻烦,我并不完全反对,但实际上人们并不需要像这样的大量函数。通常情况下,最好按照我之前描述的那样做:延迟模式匹配直到您知道如何处理结果。

clj7thdc

clj7thdc2#

对于您想要的函数,可以创建如下函数

isApple :: CampingStuff -> Bool
isApple Apple{} = True
isApple _ = False

然后使用filter isApple。当您想按Jane进行筛选时,您可以为每种类型添加另外5个函数,例如isAppleFrom :: String -> CampingStuff -> Bool并执行filter (isAppleFrom "Jane")
另一种方法如下:

data StuffType = AppleT | BananaT | PineappleT | TableT | ChairsT deriving Eq
data Query = ByStuff StuffType | ByName String deriving Eq

pickStuff :: [CampingStuff] -> [Query] -> [CampingStuff]
pickStuff xs qs = filter cond xs
  where
    cond :: CampingStuff -> Bool
    cond x = all (\q -> case (q, x) of
      (ByStuff AppleT, Apple{}) -> True
      ...other pairs...
      (ByName name1, Apple name2 _) -> name1 == name2
      ...
      _ -> False) qs

也就是说,从数据类型中分离查询。上面是一个例子,可能写得更好。

相关问题