【问题标题】:Match Data constructor functions匹配数据构造函数
【发布时间】:2013-07-19 13:55:45
【问题描述】:

我正在尝试以通用方式匹配数据构造函数,以便执行特定类型的任何任务。

data Task = TaskTypeA Int | TaskTypeB (Float,Float)

genericTasks :: StateLikeMonad s
genericTasks = do
   want (TaskTypeA 5)

   TaskTypeA #> \input -> do 
       want (TaskTypeB (1.2,4.3))
       runTaskTypeA input

   TaskTypeB #> \(x,y) -> runTaskTypeB x y

main = runTask genericTasks

在此,genericTasks 函数通过 do-instructions,从 want 构建由某种状态单子处理的待办事项列表,以及通过 @987654324 处理的方法列表@ 功能。 runTask 函数将运行 genericTasks,使用生成的待办事项和操作方法列表,并进行计算。

但是,我在弄清楚如何从 (#>) 中提取“类型”(TaskTypeA,B) 以便以后调用它时遇到了一些麻烦。如果你做一个:t TaskTypeA,你会得到一个Int -> Task

(#>)怎么写?

我也不完全相信有可能以如此通用的方式来做我在想的事情。作为参考,我正在尝试构建类似于Shake 库的东西,其中(#>) 类似于(*>)。但是 Shake 使用字符串作为 (*>) 的参数,因此匹配完全使用字符串匹配完成。我想在不需要字符串的情况下这样做。

【问题讨论】:

  • 这是我第一次尝试 SO,如果有什么需要澄清的,请告诉我。我也是 Haskell 的新手,但这让我比平时更难受——不幸的是,这通常意味着它不像我想象的那样完全可行。

标签: haskell pattern-matching


【解决方案1】:

你的直觉是正确的,不可能像你指定的那样写(#>)。数据构造函数作为模式的唯一时间是在模式位置中,即作为函数的参数出现

f (TaskTypeA z) = ...

作为case 语句的替代方案之一

case tt of
    TaskTypeA z -> ...

或在单子或模式绑定中

do TaskTypeA z <- Just tt
   return z

当用于值位置时(例如,作为函数的参数),它会失去其模式性质并成为常规函数。不幸的是,这意味着您不能这么容易地抽象模式。

不过,有一个简单的模式形式化:

type Pattern d a = d -> Maybe a

制作它们需要一点点工作。

taskTypeA :: Pattern Task Int
taskTypeA (TaskTypeA z) = Just z
taskTypeA _ = Nothing

如果您还需要使用构造函数“forwards”(即a -&gt; d),那么您可以将两者配对(加上一些与之配合使用的函数):

data Constructor d a = Constructor (a -> d) (d -> Maybe a)

apply :: Constructor d a -> a -> d
apply (Constructor f _) = f

match :: Constructor d a -> d -> Maybe a
match (Constructor _ m) = m

taskTypeA :: Constructor Task Int
taskTypeA = Constructor TaskTypeA $ \case TaskTypeA z -> Just z
                                          _ -> Nothing

这被称为“棱镜”,并且(一种非常通用的形式)在lens 中实现。

使用这样的抽象有很多好处——也就是说,您可以构造棱镜,其结构可能比数据类型所允许的更多(例如,d 可以是函数类型),并且您可以编写函数对构造函数进行操作,将更简单的构造函数组合成更复杂的构造函数。

但是,如果您使用的是普通数据类型,那么必须像我在上面为 TaskTypeA 所做的那样,为每个构造函数实现 Constructor 对象是很痛苦的。如果您有很多这些需要使用,您可以使用Template Haskell 为您编写样板文件。必要的 Template Haskell 例程是 lens 中的already implemented——因此学习如何使用 lens 库可能是值得的。 (但导航可能有点令人生畏)

(样式说明:上面的第二个Constructor 及其两个辅助函数可以使用一个小技巧等效地编写:

data Constructor d a = Constructor { apply :: a -> d, match :: d -> Maybe a }

)

有了这个抽象,现在可以编写(#&gt;)。一个简单的例子是

(#>) :: Constructor d a -> (a -> State d ()) -> State d ()
cons #> f = do
    d <- get
    case match cons d of
        Nothing -> return ()
        Just a  -> f a

或者更复杂的东西,这取决于你想要什么。

【讨论】:

  • 感谢您的帮助!我正在研究你解释的内容,但我不太明白第二个taskTypeA 构造函数的符号,特别是:$\case TaskTypeA z -&gt; Just z, _ -&gt; Nothing。我猜你是在解释,但我不太确定你的确切意思。
  • @maximilian 哦,那是LambdaCase,一个 GHC 扩展。本来可以写Constructor TaskTypeA $ \x -&gt; case x of ...
  • 好的,所以我想出了如何编写第二个“构造函数”,但是当我编译时,它不需要任何参数?我不太确定如何使用第二个构造函数来做某事——我想我应该对它进行模式匹配? (为了得到它的功能来做某事?
  • 是的——我意识到这有点晚了。一旦我重写它以使用“x”,我意识到可能有一个扩展来摆脱多余的“x”。
  • @maximilian 我编辑了答案以介绍一些与Constructor一起使用的功能
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-29
  • 1970-01-01
  • 2022-01-15
  • 1970-01-01
  • 1970-01-01
  • 2017-08-04
  • 2022-11-29
相关资源
最近更新 更多