我正在尝试用一些原语和用户定义的函数创建一个简单的编程语言。
这些是我创建的类型:
data Type = IntT | BoolT
data Value = IntV Int | BoolV Bool | OperatorCall String [Value]
data Expr = LetE String Value | ProcedureCall String [Value]
可以看到,我将函数分为操作符(返回值)和过程(不返回任何东西,充当表达式而不是值)。函数调用包含被调用函数的字符串id和传递的参数列表。另外,程序只是一个表达式列表(为了简单起见,我省略了用户定义的函数)
我的问题来自于我需要写一个函数来解析来自字符串的函数调用:
parseFunctionCall :: String -> ???
...
函数的返回类型可以是Value
(用于操作符调用)或Expr
(用于过程调用)。这个函数相当复杂,我宁愿避免写两次,或者用Either
返回类型污染它。我该怎么办?我怎样才能改变我的类型,从而干净利落地实现这个目标?可能是这样的,但我不认为这是正确的方法:
type FunctionCall = (String, [Value])
data Value = ... | OperatorCall FunctionCall
data Expr = ... | ProcedureCall FunctionCall
parseAsFunctionCall :: String -> FunctionCall
...
您可以让函数调用解析器返回(String, [Value])
,并让调用者将其修复为他们最喜欢的任何数据结构—在您的情况下,如果解析值则应用(s, vs) -> OperatorCall s vs
,如果解析表达式则应用(s, vs) -> ProcedureCall s vs
。
parseFunctionCall :: Parser (String, [Value])
parseLiteralInt :: Parser Int
parseLiteralBool :: Parser Bool
parseLet :: Parser (String, Value)
(parseFunctionCall, parseLiteralInt, parseBool, parseLet) = {- ... -}
parseValue :: Parser Value
parseValue =
(((s, vs) -> OperatorCall s vs) <$> parseFunctionCall)
<|>
(IntV <$> parseLiteralInt)
<|>
(BoolV <$> parseLiteralBool)
parseExpr :: Parser Expr
(((s, vs) -> ProcedureCall s vs) <$> parseFunctionCall)
<|>
(((s, v) -> Let s v) <$> parseLet)