这是关于编程风格和常见实践的更多问题。但我觉得它不适合代码审查论坛......
我的程序解析正则表达式并处理它们。正则表达式可以包含常用元素(Kleene闭包,连接等),它也可以通过名称引用其他正则表达式,如宏:
data Regex a = Epsilon
| Literal a
| Ranges [(a, a)]
| Ref String
| Then (Regex a) (Regex a)
| Or (Regex a) (Regex a)
| Star (Regex a)
在我处理正则表达式并解析所有宏引用并将Literal
元素转换为Range
元素(这是我的目的所需)后,我最终得到的类型不能也不能有Ref
和Literal
,所以在我使用它的函数中我做了类似的事情:
foo (Literal _) = error "unexpected literal"
foo (Ref _) = error "unexpected reference"
foo (Epsilon) = ...
foo (Star x) = ...
...
这看起来很难看,因为它在编译期间执行运行时检查而不是检查。不是一种非常类似的方法。
那么也许我可以引入另一种与原始数据非常相似的数据类型并使用它?
data RegexSimple a = Epsilon2
| Ranges2 [(a, a)]
| Then2 (Regex a) (Regex a)
| Or2 (Regex a) (Regex a)
| Star2 (Regex a)
这样可行,但在这里我有很多重复,而且现在我还需要构建器的漂亮和描述性名称,我需要发明新的......
专家会在这做什么?我想学习:))
答案 0 :(得分:5)
我不知道你的代码的其余部分是什么样的,所以这个解决方案可能需要你重新思考某些方面,但最重要的是" haskell-ish"这个问题的解决方案可能是使用GADTs和phantom types。它们一起基本上允许您创建任意子类型以实现更灵活的类型安全性。你会重新定义你的类型。
{-# LANGUAGE GADTs #-}
data Literal
data Ref
data Rangeable
data Regex t a where
Epsilon :: Regex Rangeable a
Literal :: a -> Regex Literal a
Ranges :: [(a, a)] -> Regex Rangeable a
Ref :: String -> Regex Ref a
Then :: Regex t' a -> Regex t' a -> Regex Rangeable a
Or :: Regex t' a -> Regex t' a -> Regex Rangeable a
Star :: Regex t' a -> Regex Rangeable
然后你可以定义
foo :: Regex Rangeable a
foo (Epsilon) = ...
foo s@(Star a) = ...
现在,像foo $ Literal 'c'
这样的语句将无法进行编译时类型检查。