在Haskell中,用户如何将项添加到列表中

时间:2011-11-15 15:35:55

标签: haskell

我理解如何使用递归数据结构来管理一系列内容:

data Thingy a = NoThingy | Thingy a a (Thingy a) deriving (Show, Read)

firstThingsFirst :: a -> a -> (Thingy a)
firstThingsFirst a b = Thingy a b NoThingy

andAnotherThing :: a -> a -> Thingy a -> Thingy a
andAnotherThing a b NoThingy = Thingy a b NoThingy
andAnotherThing a b things = Thingy a b things

在ghci我可以做类似的事情:

let x=andAnotherThing "thing1" "thing2" NoThingy
let y=andAnotherThing "thing3" "thing4" x

但是,我不知道如何使这个工作用于需要用户输入的编译程序。换句话说,我想让用户填满结构。类似的东西:

import System.IO
allThings=NoThingy
main = do
  putStrLn "First Thing"
  first<-getLine
  putStrLn "Second Thing"
  second<-getLine
  let allThings=Thingy first second allThings
  print allThings
  main

4 个答案:

答案 0 :(得分:11)

Haskell中的值是不可变的,因此如果您“将项目添加到列表中”,您将获得一个新列表。所以在上面的代码中,

let allThings = Thingy first second allThings

不符合您的期望。顶级allThings的值为NoThingy且无法更改。 let-binding中的名称allThings不引用顶级实体,它引入了一个新的绑定,遮蔽了顶级名称​​,并且右侧也引用了新名称的约束力。 所以该行和以下内容相当于

let theThings = Thingy first second theThings
print theThings

let-binding创建一个循环结构,将自身称为其组件之一。这当然意味着打印它永远不会完成。

您(可能)想要做的事情要求将要更新的结构作为参数传递

loop things = do
        putStrLn "First Thing"
        ...
        let newThings = Thingy first second things
        print newThings
        loop newThings

当然,就像尼古拉斯所说的那样,您可能希望将输入字符串转换为适当类型的值。

答案 1 :(得分:2)

你创造的是一种无限自我指导的“allThings”,它被打印出来。

您绑定名称allThings两次。第一次在主要之前和第二次在打印之前。

第二个绑定是指右侧末端的所有事件。此引用不是第一个绑定。这个引用是第二个绑定本身。

如果更改第二个绑定和打印的名称:

main = do
  putStrLn "First Thing" 
  first <- getLine
  putStrLn "Second Thing"
  second <- getLine
  let allThings2 = Thingy (read first) (read second) allThings
  print allThings2
  main
然后你会在每个主循环上打印一个Thingy。由于你想积累答案,你可以定义一个尾递归“查询”,如下所示:

query old = do
  putStrLn "First Thing"
  first<-getLine
  putStrLn "Second Thing"
  second<-getLine
  let new=Thingy first second old
  print new
  query new

main = query NoThingy

以上可能会做你想要的。

答案 2 :(得分:1)

听起来您要求的方法是将String类型的用户输入转换为类型Thingy的值。由于您的Thingy类型已有Read的实例,因此您可以使用read函数:

read :: Read a => String -> a

这使您的main功能更像:

main = do
  putStrLn "First Thing" 
  first <- getLine
  putStrLn "Second Thing"
  second <- getLine
  let allThings = Thingy (read first) (read second) allThings
  print allThings
  main

当然,如果您输入的字符串与Thingy不对应,那么您将收到错误消息,因此您可能希望使用safe package中的readMay代替:

readMay :: Read a => String -> Maybe a

另一个问题是,类型推断无法猜出a的类型是什么,所以你也必须给它一个提示,可能有类似的东西:

let allThings = Thingy (read first :: Int) (read second :: String) allThings

值得注意的是,在这个定义中,allThings是一个无限结构,将永远打印:永远不会达到对main的最后一次调用。

答案 3 :(得分:1)

问题是你正在尝试使用变量来保持可变状态(就像在命令式语言中那样)但你不能在Haskell中这样做。 必须将所有州明确作为参数传递给您的函数。正如其他答案中所指出的,main allThings实际上是一个与全局范围内的allThings分开的变量(它只是隐藏了相同的名称)

以下示例显示如何构建一个在构建数字列表时永远循环的程序。我认为这是你想要做的事情,并不应该很难适应“Thingies”

在我们的例子中,我们必须为循环保留的状态是要输入的下一个数字的索引(第1个,第2个等)以及我们已经读取的数字列表。因此,我们的循环函数将接收此状态作为参数并返回IO操作。

module Main where

-- Explicit signature so readLn and show don't complain...
loop_step :: (Int, [Int]) -> IO ()
loop_step (i,xs) = do
    putStrLn ("Enter the " ++ show i ++ "th number:")
    n <- readLn
    let newList = n : xs
    print newList
    loop_step (i+1, newList)

main :: IO ()
main = do
    loop_step (1, [])