【问题标题】:In Haskell how to pass a constructor as a parameter and match over it在 Haskell 中如何将构造函数作为参数传递并匹配它
【发布时间】:2014-02-12 07:56:18
【问题描述】:

我有这个代码

data Container = Box Int | Bag Int


inBox :: [Container] -> Int
inBox [] = 0
inBox (x:ls) | (Box i) <- x = i + inBox ls
             | otherwise = inBox ls


inBag :: [Container] -> Int
inBag [] = 0
inBag (x:ls) | (Bag i) <- x = i + inBag ls
             | otherwise = inBag ls

显然InBoxInBag 具有相同的结构。我想制作一个包含它们的功能。我不知道如何将构造函数(BoxBag)作为参数传递。

理想情况下,通用函数应如下所示:

inSome :: Constructor -> [Container] -> Int
inSome con [] = 0
inSome con (x:ls) | (con i) <- x = i + inSome con ls
                  | otherwise = inSome con ls

显然这不起作用,因为构造函数不是这里定义的类型。我该怎么做?

一个想法是像这样将它作为函数传递:

inSome :: (Int -> Container) -> [Container] -> Int
inSome _ [] = 0
inSome con (x:ls) | (con i) <- x = i + inSome ls
                  | otherwise = inSome ls

然后我得到错误:

模式中的解析错误:con

因为它无法匹配这样的功能。

我想这样做的原因是因为我有一个复杂的数据类型,其中包括二进制操作(例如 +、#、:: 等...)我有几个函数对于这些构造函数几乎相同。我不想把它们都写下来,然后一起修改。我必须有一种方法可以在函数中做到这一点。也许有人可以在 cmets 中提出另一种方法?

【问题讨论】:

  • 你为什么要这样做?
  • 定义unContainer :: (Int-&gt;a)-&gt;(Int-&gt;a)-&gt;Container-&gt;a,然后定义inBox (x:ls) = unContainer (+(inBox ls)) (const (inBox ls)) x,和inBag 相同,但交换unContainer 参数。如果你愿意,你可以概括更多,所以实际上 inBox 和 inBag 会相差flip,但在这种特殊情况下,我不明白你为什么要这样做。
  • 已编辑以解释我为什么要这样做,希望对您有所帮助。

标签: haskell constructor


【解决方案1】:

你可以完全避免在这里使用模式匹配。

data Container = Box Int | Bag Int

unBox, unBag :: Container -> Maybe Int

unBox (Box i) = Just i
unBox _       = Nothing

unBag (Bag i) = Just i
unBag _       = Nothing

这些函数的类型满足了在打开Container 的结构时取出包含的Int 的需要。然后可以使用它来构建您想要的功能。

inSome :: (Container -> Maybe Int) -> [Container] -> Int
inSome get []     = 0
inSome get (x:ls) = fromMaybe 0 (get x) + inSome ls

inBag = inSome unBag
inBox = inSome unBox

正如 leftroundabout 所指出的,“获得或失败”的模式(大量)概括为 Lens 的概念,或者在本例中为 Prism。一般情况下,Prisms can form a weak kind of first-class pattern,但在这里使用它们会有些矫枉过正。

【讨论】:

  • 打败我 - 这正是你应该做的,除非你有一些特别迫切的需要能够传递模式。
  • 似乎是一个完美的解决方法。但是要确保,没有使用 Lens 就没有直接的方法将构造函数作为参数传递吗?是否存在限制的根本原因?
  • 你可以传递一个构造函数——毕竟它们只是函数——你不能参数化模式匹配,它们必须是具体的。也就是说,这些限制将在 GHC 7.10 中取消。
【解决方案2】:

您可能会喜欢first-class-patterns,这是一个可以让您传递和学习模式的软件包。

【讨论】:

    【解决方案3】:

    你想要一个lensׅׅׅׅׅׅׅׅׅׅׅׅׅׅׅׅׅׅׅׅ

    【讨论】:

      【解决方案4】:

      如果您希望函数根据参数仅计算一种类型,则可以使用普通枚举类型来完成这项工作。本质上只需要一个标签来区分您想要的操作类型。传递构造函数或包装器,例如 make_bagmake_box 并没有更好的效果,并且它们不能按原样在模式匹配中使用。

      data ConType = ON_BAG | ON_BOX
      inCon :: ConType -> [Container] -> Int
      inCon _ [] = 0 
      inCon t (x:ls) | ON_BAG <-t,(Bag i) <- x = i + (inCon t ls)
                     | ON_BOX <-t,(Box i) <- x = i + (inCon t ls)
                     | otherwise = inCon t ls
      

      如果你想把盒子和袋子里的数字分别相加,我想你可以这样做:

      inCont :: [Container] -> (Int,Int)
      inCont [] = (0,0)
      inCont (x:ls) | (Bag i) <- x = addP (i,0) (inCont ls)
                    | (Box i) <- x = addP (0,i) (inCont ls)
                    | otherwise = inCont ls
      

      将它们一起计算更容易:

      inCont2 :: [Container] -> Int
      inCont2 [] = 0
      inCont2 (x:ls) | (Bag i) <- x = i + (inCont2 ls)
                    | (Box i) <- x = i + (inCont2 ls)
                    | otherwise = inCont2 ls
      

      【讨论】:

      • 我需要的是一个泛化这两个函数的函数。您的解决方案很有用,但不是我想要的。我希望函数只计算一个(即盒子 xor 袋子),但哪一个取决于函数的参数。
      【解决方案5】:

      将容器拆分为两种数据类型:

      data Sort = Bag | Box deriving (Eq, Show)
      data Container = Container Sort Int deriving (Show)
      

      定义Sort 允许我们谈论容器的种类,而无需实际提及任何特定的容器。这让我们可以做更多有趣的事情:

      import Data.Maybe (fromMaybe)
      
      --  Pull out the value regardless of sort
      getVal :: Container -> Int
      getVal (Container _ val) = val
      
      --  If the sort passes a predicate, we get just the value
      unwrapIf :: (Sort -> Bool) -> Container -> Maybe Int
      unwrapIf p (Container b v) = if p b then Just v else Nothing
      
      --  If a given sort matches the container, we get just the value
      unwrap :: Sort -> Container -> Maybe Int
      unwrap p = unwrapIf (p ==)
      
      --  Unwrap with a default value if one was not found
      unwrapDefault :: Int -> Sort -> Container -> Int
      unwrapDefault def p = fromMaybe def . unwrap p
      
      --  Unwrap with a default value of 0
      unwrapValue :: Sort -> Container -> Int
      unwrapValue = unwrapDefault 0
      
      --  Unwrap Bag values, else 0
      unbag :: Container -> Int
      unbag = unwrapValue Bag
      
      --  Unwrap Box values, else 0
      unbox :: Container -> Int
      unbox = unwrapValue Box
      
      --  sum up the values in a list of containers
      sumAll :: [Container] -> Int
      sumAll = sum . map getVal
      
      --  sum up the values in a list of one sort of container
      sumContainer :: Sort -> [Container] -> Int
      sumContainer s = sum . map (unwrapValue s)
      
      --  sum up the bag values in a list of containers
      sumBag :: [Container] -> Int
      sumBag = sumContainer Bag
      
      --  sum up the box values in a list of containers
      sumBox :: [Container] -> Int
      sumBox = sumContainer Box
      

      这里sumContainer相当于你理想的inSome函数,sumBag/sumBox分别是inBag/inBox

      如果您想更进一步,请尝试泛化 Container 以允许任何值:

      data Container a = Container Sort a deriving (Show)
      

      希望这会有所帮助!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-02
        • 2016-07-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多