能否实现类型为`a -> <pattern> -> Bool`的模式匹配函数?
模式匹配函数
matches的可行性与实现 这种实现完全可行,但得借助Haskell的元编程扩展——因为Haskell的模式是静态语法元素,不是可以直接传递的一等公民,标准Haskell里没法直接把模式当参数传给函数。
为什么直接写xs matches [_]不行
在普通Haskell代码里,[_]是模式匹配语法,不是合法的表达式。编译器会把它当成一个包含未绑定变量_的列表字面量,直接这么写会报错——毕竟下划线在表达式里只能用来忽略变量,不能代表“任意元素”的模式。
用Template Haskell实现matches
Template Haskell是Haskell的元编程工具,能在编译阶段把模式转换成对应的匹配函数,完美解决这个问题。具体步骤如下:
- 先启用Template Haskell扩展,导入相关模块:
{-# LANGUAGE TemplateHaskell #-} import Language.Haskell.TH
- 写一个模板函数,把输入的模式转换成判断值是否匹配的函数:
-- 接收模式的AST,生成返回Bool的匹配函数 mkMatcher :: PatQ -> ExpQ mkMatcher pat = [| \x -> case x of $pat -> True _ -> False |]
- 定义
matches中缀函数,用来把值和匹配函数结合:
matches :: a -> (a -> Bool) -> Bool matches = flip ($)
- 现在就能按需求改写
isSingleton了——用$(...)在编译时把模式[_]转成匹配函数:
isSingleton :: [a] -> Bool isSingleton xs = xs `matches` $(mkMatcher [p| [_] |])
嫌麻烦的话,也可以直接生成isSingleton函数,不用绕matches:
isSingleton :: [a] -> Bool isSingleton = $(mkMatcher [p| [_] |])
不用元编程的替代方案:泛型匹配
要是不想碰Template Haskell,也可以用Haskell的Generic类型类,把值和模式都转成泛型结构来对比。这种方式不用元编程,但得给类型加Generic实例,而且模式的表达能力受限于泛型结构:
{-# LANGUAGE DeriveGeneric #-} import GHC.Generics -- 定义列表的模式结构 data ListPattern a = Single a | Multiple a (ListPattern a) | Empty deriving (Generic, Eq) -- 把普通列表转成模式结构 listToPattern :: [a] -> ListPattern a listToPattern [] = Empty listToPattern [x] = Single x listToPattern (x:xs) = Multiple x (listToPattern xs) -- 实现泛型层面的匹配 matches :: (Generic (ListPattern a), Eq (Rep (ListPattern a) ())) => [a] -> ListPattern a -> Bool matches xs pat = to (listToPattern xs) == to pat -- 使用示例:用Single undefined代表单元素列表模式 isSingleton xs = xs `matches` Single undefined
不过这种方式需要手动定义模式结构,不如Template Haskell贴近原生的模式语法。
内容的提问来源于stack exchange,提问作者Good Night Nerd Pride
相关产品推荐
相关产品推荐

