Haskell Forth口译员
我最近开始学习Haskell,我正试图用Haskell为第四语言编写一个解释器。但我在尝试运行最基本的操作时遇到了问题。例如,FORTH中的一个简单程序(在Haskell中作为字符串)如下:“12+”返回一个包含整数3的堆栈,在Haskell中,整数3可以表示为整数:[3]的列表 我还具有从堆栈中推、放和弹出元素的功能。我有一个加法函数,可以从堆栈中添加两个元素 现在,有了这些函数,我如何解析简单的程序“12+”以返回[3]?我希望示例如下所示:Haskell Forth口译员,haskell,Haskell,我最近开始学习Haskell,我正试图用Haskell为第四语言编写一个解释器。但我在尝试运行最基本的操作时遇到了问题。例如,FORTH中的一个简单程序(在Haskell中作为字符串)如下:“12+”返回一个包含整数3的堆栈,在Haskell中,整数3可以表示为整数:[3]的列表 我还具有从堆栈中推、放和弹出元素的功能。我有一个加法函数,可以从堆栈中添加两个元素 现在,有了这些函数,我如何解析简单的程序“12+”以返回[3]?我希望示例如下所示: forthInterpreter "1 2 +"
forthInterpreter "1 2 +"
[3]
任何帮助都将不胜感激。如果您有任何澄清问题,请告诉我。
谢谢。您拥有的每个命令都是来自
Stack->Stack
的函数。这使得解释命令变得容易。我使用[Int]
作为堆栈的类型,堆栈顶部的数字位于列表的顶部
interpretCommand :: String -> [Int] -> [Int]
interpretCommand "+" = lift2 (+)
interpretCommand "-" = lift2 (-)
interpretCommand "*" = lift2 (*)
interpretCommand "/" = lift2 div
interpretCommand "MOD" = lift2 mod
interpretCommand number = \zs -> read number : zs
其中lift2
将2参数函数提升为列表前2个元素上的函数。参数x
和y
被交换,因为推到堆栈上的第一个参数是函数的第一个参数,堆栈顶部的参数是函数的第二个参数
lift2 :: (a -> a -> a) -> [a] -> [a]
lift2 f (x:y:zs) = f y x:zs
lift2 f _ = error "not enough data on the stack"
为了解释多个命令,我们用单词
将它们分开,在空白处拆分一个字符串,解释每个命令,然后将它们组合在一起
composition :: [a -> a] -> a -> a
composition = foldl (flip (.)) id
interpretCommands :: String -> [Int] -> [Int]
interpretCommands = composition . map interpretCommand . words
然后我们可以通过调用interpretactcommands
并将初始(空)堆栈传递给它来解释一个命令字符串。下面是你的两个例子
main = do
print $ interpretCommands "1 2 +" []
print $ interpretCommands "1 2 + 4 - 3" []
print $ interpretCommands "10 5 / 3" []
print $ interpretCommands "13 7 MOD 2 / 4 *" []
输出是
[3]
[3,-1]
[3,2]
[12]
请注意,[3,-1]
的顺序与您建议的相反,因为3
位于堆栈顶部,因此位于列表的开头。(这是您在fourthadd
中的注释中预测的行为)您拥有的每个命令都是Stack->Stack
中的函数。这使得解释命令变得容易。我使用[Int]
作为堆栈的类型,堆栈顶部的数字位于列表的顶部
interpretCommand :: String -> [Int] -> [Int]
interpretCommand "+" = lift2 (+)
interpretCommand "-" = lift2 (-)
interpretCommand "*" = lift2 (*)
interpretCommand "/" = lift2 div
interpretCommand "MOD" = lift2 mod
interpretCommand number = \zs -> read number : zs
其中lift2
将2参数函数提升为列表前2个元素上的函数。参数x
和y
被交换,因为推到堆栈上的第一个参数是函数的第一个参数,堆栈顶部的参数是函数的第二个参数
lift2 :: (a -> a -> a) -> [a] -> [a]
lift2 f (x:y:zs) = f y x:zs
lift2 f _ = error "not enough data on the stack"
为了解释多个命令,我们用单词
将它们分开,在空白处拆分一个字符串,解释每个命令,然后将它们组合在一起
composition :: [a -> a] -> a -> a
composition = foldl (flip (.)) id
interpretCommands :: String -> [Int] -> [Int]
interpretCommands = composition . map interpretCommand . words
然后我们可以通过调用interpretactcommands
并将初始(空)堆栈传递给它来解释一个命令字符串。下面是你的两个例子
main = do
print $ interpretCommands "1 2 +" []
print $ interpretCommands "1 2 + 4 - 3" []
print $ interpretCommands "10 5 / 3" []
print $ interpretCommands "13 7 MOD 2 / 4 *" []
输出是
[3]
[3,-1]
[3,2]
[12]
请注意,[3,-1]
的顺序与您建议的相反,因为3
位于堆栈顶部,因此位于列表的开头。(这是您在fourthadd
中的评论中预测的行为)与Cirdec的答案相同,但对于初学者来说,这可能更容易阅读:
-- Data type to represent a command.
data Command = Push Int | Add | Subtract deriving Show
-- Type synonym: a Program is a list of commands.
type Program = [Command]
-- Type synonym: a Stack is a list of Int.
type Stack = [Int]
-- The interpreter entry point.
interpretCommands :: String -> Int
interpretCommands str = runProgram (parseProgram str)
-- Parse a string and turn it into the corresponding Program.
parseProgram :: String -> Program
parseProgram str = map toCommand (words str)
where toCommand "+" = Add
toCommand "-" = Subtract
toCommand x = Push (read x)
-- Run a Program on the empty stack, return the result at the top of the result stack.
runProgram :: Program -> Int
runProgram program = head (runProgram' program [])
-- Run a program on a given stack, return the result stack.
runProgram' :: Program -> Stack -> Stack
runProgram' [] stack = stack
runProgram' (command:rest) stack = runProgram' rest (runCommand command stack)
-- Run an individual command.
runCommand :: Command -> Stack -> Stack
runCommand (Push n) stack = n:stack
runCommand Add (n:m:stack) = n+m:stack
runCommand Subtract (n:m:stack) = n-m:stack
我将程序分为两部分:parser
和runProgram
。第一种方法获取字符串并将其转换为要运行的程序的抽象表示形式。第二种解释这种抽象的表达。在这样的问题中,这是一个很好的分离;例如,当您希望编写更好的解析逻辑时,您可以修改解析器
,而不必担心破坏运行程序
逻辑。与Cirdec的答案相同,但对于初学者来说,这可能更容易阅读:
-- Data type to represent a command.
data Command = Push Int | Add | Subtract deriving Show
-- Type synonym: a Program is a list of commands.
type Program = [Command]
-- Type synonym: a Stack is a list of Int.
type Stack = [Int]
-- The interpreter entry point.
interpretCommands :: String -> Int
interpretCommands str = runProgram (parseProgram str)
-- Parse a string and turn it into the corresponding Program.
parseProgram :: String -> Program
parseProgram str = map toCommand (words str)
where toCommand "+" = Add
toCommand "-" = Subtract
toCommand x = Push (read x)
-- Run a Program on the empty stack, return the result at the top of the result stack.
runProgram :: Program -> Int
runProgram program = head (runProgram' program [])
-- Run a program on a given stack, return the result stack.
runProgram' :: Program -> Stack -> Stack
runProgram' [] stack = stack
runProgram' (command:rest) stack = runProgram' rest (runCommand command stack)
-- Run an individual command.
runCommand :: Command -> Stack -> Stack
runCommand (Push n) stack = n:stack
runCommand Add (n:m:stack) = n+m:stack
runCommand Subtract (n:m:stack) = n-m:stack
我将程序分为两部分:parser
和runProgram
。第一种方法获取字符串并将其转换为要运行的程序的抽象表示形式。第二种解释这种抽象的表达。在这样的问题中,这是一个很好的分离;例如,当您希望编写更好的解析逻辑时,您可以修改解析器
,而不必担心破坏运行程序
逻辑。为什么需要组合中的翻转
?与此相关的是,您将如何扩展(或修改)代码以使division和mod操作正常工作?在上面,您必须使用substract
来获得正确的减法行为。感谢您将两个函数组合在一起。g
,g
首先应用,然后是f
foldl
按该顺序将函数重复应用于累积值和列表的第一个值。例如,foldl op 0[1,2,3]
是((0'op'1)'op'2)'op'3
。如果我们有一个函数列表[f,g,h,i,j]
,我们想把它们组合在一起,首先执行f
,那就是j。我HGf
,顺序相反flip
将参数的顺序翻转到
@user178332如何添加除法取决于操作数被推送到堆栈的顺序。您希望它们的顺序是什么?因此“10 5/3”
应该返回[3,2]您似乎忘记了实际交换lift2
中的参数。为什么需要组合中的翻转
?与此相关的是,您将如何扩展(或修改)代码以使division和mod操作正常工作?在上面,您必须使用substract
来获得正确的减法行为。感谢您将两个函数组合在一起。g
,g
首先应用,然后是f
foldl
对累积值重复应用该函数