匹配数据构造函数

时间:2013-07-19 13:55:45

标签: haskell pattern-matching

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

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,构建一个由某种状态monad处理的want要做的事情列表,以及一系列方法,通过(#>)功能。 runTask函数将运行genericTasks,使用结果列表的待办事项和操作方法,并进行计算。

但是,我在确定如何从(#>)中提取“类型”(TaskTypeA,B)方面遇到了一些麻烦,以便以后可以调用它。如果您执行:t TaskTypeA,则会获得Int -> Task

即,如何撰写(#>)

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

1 个答案:

答案 0 :(得分:2)

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

f (TaskTypeA z) = ...

作为case陈述

的替代方案之一
case tt of
    TaskTypeA z -> ...

或以monadic或模式绑定

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 -> 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可以是函数类型),并且您可以编写在构造函数上运行的函数,组成更简单的函数,以便一般地创建更复杂的函数。

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

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

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

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

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

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