我正在尝试以通用方式匹配数据构造函数,以便执行某种类型的任何任务。
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匹配完成。我想在不需要字符串的情况下这样做。
答案 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
或者更复杂的东西,取决于你想要的东西。