在递归函数中遇到类型匹配和IO问题

时间:2015-01-14 02:08:41

标签: haskell recursion io pattern-matching type-mismatch

我遇到了一些问题而无法找到原因。我目前正在使用GHCi便携式计算机的最新版本 - 但要面对事实:这是我第一次使用Haskell,因此通常问题可能是用户而不是系统......

出现的问题包括:

  • 我不完全确定我在let x = 0x <- 0之间有所区别。我知道let将与纯函数一起使用,<-具有副作用(IO)?有人请再次向我解释一下。
  • 我的类型之间存在不匹配,即String(String,[Char])(有时候还有其他......)。编译器告诉我String是预期的,尽管我明确将函数定义为(String,String)。这是怎么回事?我在哪里弄错了模式匹配?
  • 递归不能按预期工作(即根本不起作用)。如果有人可以帮助我,我将非常感激。

这是我想做的事:
我正在尝试编写一个实现接受单词的有限状态机的小程序。这意味着它需要一组状态,其中一个是开始状态,一个接受状态列表和一些转换规则。 (表示可能的输入和状态的字母表有些含蓄。)我不想在这里详细介绍FSM。

然而,这就是我想办法定义这样一个FSM的方式:

"a(b+|c+)"
"start"
["b","c"]
[
("start",   [('a',"a"),                         ('_',"reject")]),
("a",       [           ('b',"b"),  ('c',"c"),  ('_',"reject")]),
("b",       [           ('b',"b"),              ('_',"reject")]),
("c",       [                       ('c',"c"),  ('_',"reject")]),
("reject",  [                                   ('_',"reject")])
]

在第一行中,我们简要描述了FSM应该接受的内容(在这种情况下以正则表达式的形式)。它仅用于显示一次。
第二行定义了开始状态,第三行定义了接受状态列表 以下所有行都是转换规则。在这个例子中,如果我们处于状态“start”并读取输入'a',则下一个状态是“a”,如果我们读取其他内容,则它是“拒绝”。 (我知道我还没有实现'_'表示其他的,如果读取的输入没有定义转换,程序将崩溃。)

所以程序来了:

module FSM where

import System.IO

main :: IO ()
main = do
    putStr "Enter file name: "
    fileName <- getLine
    (description, startState, acceptingStates, stateTransitions) <- (readDef fileName)
    putStrLn ("FSM description: " ++ description)
    putStr "Enter FSM input: "
    input <- getLine
    let input = reverse input
    putStrLn "----------------"
    let (finalState, oldStates) = changeState input startState stateTransitions
    putStrLn (oldStates ++ finalState)
    checkAcception finalState acceptingStates

--reads the specified .fsm file and returns
--  the description of the FSM (first line),
--  the start state (second line),
--  the list of accepting states (third line),
--  and the list of tuples containing all states and transitions (remaining lines)
readDef :: String -> IO (String, String, [String], [(String, [(Char,String)])])
readDef fileName = do
    contents <- readFile (fileName ++ ".fsm")
    let lineList = lines contents
    let description = read (head lineList)
    let startState = read (lineList !! 1)
    let acceptingStates = read (lineList !! 2)
    let stateTransitions = read (filter (/='\t') (concat (drop 3 lineList)))
    return (description, startState, acceptingStates, stateTransitions)

--recursive function that takes the input, start state, and state transitions
--and computes the new state by a call to itself with the old state and a part of the input
changeState :: String -> String -> [(String, [(Char,String)])] -> (String, String)

changeState startState [] _ = (startState, "")

changeState startState (x:xs) stateTransitions = do
    let (currentState, oldStates) = changeState xs startState stateTransitions
    let newState = findKey x (findKey currentState stateTransitions)
    let oldStates = (oldStates ++ currentState ++ " -(" ++ [x] ++ ")-> ")
    return (newState, oldStates)

--helper function to find a key in a list of tuples and return the corresponding value
--(because we are not using the map notation in the .fsm file)
findKey :: (Eq k) => k -> [(k,v)] -> v
findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs

--checks for a given state whether or not it is in the list of accepting states
checkAcception :: String -> [String] -> IO ()
checkAcception finalState acceptingStates = do
    let accept = any (==finalState) acceptingStates
    if accept
        then putStrLn "Input accepted!!"
        else putStrLn "Input rejected!!"

这个想法是让用户选择一个加载定义的文件(readDef,就像一个魅力)。然后提示他输入FSM工作的一些输入。
然后递归changeState执行实际工作(也不起作用......) 最后,显示状态和转换的序列,并检查最终状态是否为接受状态(checkAcceptance)。


现在,不要试图优化我所写的内容。我知道,定义建模的方式可以改进,我编写的许多行可以使用一些高阶Haskell foo写得更短。但请帮助我解决上面列出的问题(当然,这有助于我使其发挥作用) 非常感谢。

最后一件事:我正在为我的大学的研讨会尝试一些Haskell,所以如果来自软件架构小组的人用Google搜索我的代码并阅读:嗨:)

2 个答案:

答案 0 :(得分:0)

只需将changeState函数的第二个子句更改为:

即可编译
changeState startState (x:xs) stateTransitions =
    let (currentState, oldStates) = changeState xs startState stateTransitions
        newState = findKey x (findKey currentState stateTransitions)
        oldStates2 = (oldStates ++ currentState ++ " -(" ++ [x] ++ ")-> ")
    in (newState, oldStates2)

我们已经1)删除 do ,2)结合 let 子句和3)重命名第二次出现的 oldState 变量到 oldState2 。在Haskell中,我们不重新定义变量 - 我们只是创建一个具有新名称的变量。完整代码可在此处获取:http://lpaste.net/118404

当你写:

(new, old) = changeState ...

你说的是changeState是一个纯函数。如果您使用changeState定义do ... return (...),则说这是一个monadic计算,当您调用它时,您需要在<-块中使用箭头do

(new, old) <- changeState ...

由于changeState是纯函数(不需要执行IO),因此您可以将其保留为纯函数,因此没有理由使用do和{{1} }。

答案 1 :(得分:0)

问题是do符号和return函数不符合您的想法。在Haskell中:return并不表示函数应该结束(即使它最常见于函数的末尾);它只是意味着参数应该包含在Monad中。因为应用了所有参数的函数类型是(String,String),编译器认为你试图使用这样的东西:(如果没有GHC扩展,实际上不会编译,并且如果使用的话会抛出异常,因为我使用{{1 }})

undefined

但是编译器已经知道instance Monad ((,) String) where (>>=) = undefined :: (String,a) -> (a -> (String,b)) -> (String,b) return = undefined :: a -> (String,a) (String,String) -> (String,String)不匹配,所以它没有检查实例是否存在。 修复此问题揭示了另一个问题:在同一个函数中定义a -> (String,a)两次,除非两个定义位于不同的范围内,否则在Haskell中不起作用。

这是你的函数被修改为正确编译,但我还没有测试过。

oldStates