Haskell:如何读取要在 do 块内的函数中使用的用户输入?

Haskell: How to read user input to be used in a function inside of a do block?

我正在尝试 运行 Haskell 中的一个简单的回文程序,但我想请求用户输入以在程序中使用。

代码:

palindrome :: String -> Bool
palindrome x = x == reverse x
main = do
  putStrLn "Brendon Bailey CSCI 4200-DB"
  putStrLn "Enter word to test for Palindrome"
  palindrome <- getLine
  putStrLn "Thanks"

代码有效,但只显示 "thanks"。如果我排除块的最后一行,我会得到 "last line of do block must be expression",如果我在 do 块上方包含实际的回文代码,我会收到错误消息,说我需要一个 "let" 语句,但合并一个语句会导致更多错误。我是 Haskell 的新手(和一般的函数式编程。我做 java、c++ 和 web langs)我认为这里的解释很简单,但是在阅读 [=41= 上的输入输出文档之后] 我还是想不出正确的方法来完成这个。

我这样做是因为我的作业规定我必须在程序 运行 时显示 class 信息。回文部分在没有 do 块的情况下工作正常。我只是想在测试回文之前自动输出我的文本。另外,如何将 haskell 脚本设置为在加载时仅 运行 ?我不希望用户必须输入 "main" 我只想输入 运行.

编辑:

palindrome :: String -> Bool
palindrome x = x == reverse x
main = do
  putStrLn "Brendon Bailey CSCI 4200-DB"
  putStrLn "Enter Word"
  x <- getLine
  palindrome x
  putStrLn "thanks"

这也不行。如何让用户输入在回文中使用???

如代码:

palindrome :: String -> Bool
palindrome x = x == reverse x

在 GHCi 中输入 "palindrome "etc"" 时有效,那为什么我的第二个代码版本不做同样的事情???

编辑 2: 不要忘记将打印语句括在括号中,否则 Haskell 会认为单独的输入是一个单独的参数。愚蠢的错误,但很容易犯。

这道题是多道题合二为一,所以我会尝试解决每道题。我将按照它们在其中陈述的不同顺序来解决这些问题。

在我们开始之前,必须说明您对 Haskell 的工作方式存在严重的误解。我强烈推荐阅读 Learn You a Haskell for Great Good,它可能看起来很幼稚,但实际上是对函数式编程的精彩介绍,实际上也是我们许多人开始学习 Haskell.

的方式

问题 1:编译、GHC 和 Haskell 可执行文件

(...) how can I set up a haskell script to just run when loaded? I don't want the user to have to type "main" I want it to just run.

您混淆了 解释 脚本语言,例如 Python、Javascript 或 Ruby,以及 编译 语言,如 C、Java 或 Haskell。

解释 语言的程序 运行 由计算机上的程序运行 提供,其中包含有关如何解释所写文本的信息,因此得名 'interpreted' .然而,编译语言是写出来的,然后从文本转换成机器码,人类几乎无法阅读,但计算机可以运行 很快。

当我们在 Haskell 中编写完整的可执行程序时,我们希望使用 Glasgow Haskell 编译器 GHC 对其进行编译(不是 GHCi) .这看起来像这样:

$ cat MyProgram.hs
main :: IO ()
main = putStrLn "This is my program!"
$ ghc MyProgram.hs
[1 of 1] Compiling Main             ( MyProgram.hs, MyProgram.o )
Linking MyProgram ...
$ ./MyProgram
This is my program!

现在,MyProgram.hs 已由 GHC 从 Haskell 源代码文件转换为可执行文件 MyProgram。我们定义了一个 IO 操作,称为 main,以告诉编译器我们希望它从哪里开始。这称为 入口点 ,并且是创建独立可执行文件所必需的。 这就是 Haskell 程序的创建方式 运行。

重要的是,GHCi 不是 Haskell 程序 运行。 GHCi 是一个交互式环境试验 Haskell 函数和定义。 GHCi 允许您评估表达式(例如 palindrome "Hello"main),但 不是 旨在作为 运行 Haskell 的方式程序,就像 python 的 IDLE 用于 Python 程序一样。

问题 2:函数与 IO 操作

(...) If you just have the palindrome code outside of the do block and the user enters "palindrome "etc"" in GHCi the program returns the boolean as output (...)

Haskell 确实 而不是 表现得像您可能习惯的语言。 Haskell 中的函数 不是 程序。 当您定义函数 palindrome 时,您为计算机开辟了一条道路将 String 转换为 Bool。你 而不是 告诉它如何以任何方式输出 Bool。因此,还有一个额外的步骤:IO monad.

Haskell 程序在某种程度上表示为数据,因此有点奇怪的类型签名 main :: IO ()。我不会在这里详细解释 monad,但简单地说,在 do 块中,您只能声明 IO a 类型的事物,其中 a 是任何类型。所以,当你写道:

main = do
    -- (...)
    palindrome x
    -- (...)

没有意义。这样想:你怎么能 'run' 或 'execute' 一个 Bool?这是一个布尔值,不是程序!

但是,有一个名为 print 的函数可以让您做到这一点。你的行应该是:

main = do
    -- (...)
    print (palindrome x)
    -- (...)

我不会讨论 print 的类型签名,但请放心,它确实 return 一个 IO (),这允许我们将它放入 [=28] =]-块。顺便说一句,如果你写:

palindrome :: String -> Bool
palindrome x = x == reverse x

main = do
  putStrLn "Brendon Bailey CSCI 4200-DB"
  putStrLn "Enter Word"
  x <- getLine
  print (palindrome x)
  putStrLn "thanks"

...并使用 GHC 编译文件,您将获得预期的效果,假设您确实希望程序输出 TrueFalse。事实上,我将其编译为 Palindromes.hs,结果如下:

$ ghc Palindromes.hs 
[1 of 1] Compiling Main             ( Palindromes.hs, Palindromes.o )
Linking Palindromes ...
$ ./Palindromes
Brendon Bailey CSCI 4200-DB
Enter Word
amanaplanacanalpanama
True
thanks

问题 3:GHCi 作为测试环境

[My code] works when "palindrome "etc"" is entered in GHCi then why doesn't my second code version do the same thing???

GHCi,如前所述,与 GHC 不同,因为它是一个在编写独立程序之前用 Haskell 代码测试和 'play' 的环境。

在 GHCi 中,你编写表达式,然后对它们求值(使用 GHC 的机制),打印结果,然后循环,因此,这种系统通常称为读取-求值-打印-循环,或者一个 REPL.

让我们检查一下 GHCi 中的一些代码:

λ let plusOne n = n + 1
λ plusOne 5
6

第一行定义了一个我们稍后可以使用的值(这里是一个函数)。但是,第二行不是定义,而是表达式。因此,GHCi 对其进行评估,并打印结果。

GHCi 还可以执行 IO 操作:

λ let myProgram = putStrLn "Hello!"
λ myProgram
Hello!

之所以可行,是因为在 Haskell 中评估 IO 操作等同于执行它们:这就是 Haskell 设计的工作方式 - 如果您掌握了它,这将是一个真正绝妙的想法。

尽管我们看到了这些相似之处,但 GHCi 并不等同于 Haskell 程序,正如人们在使用 Python 后所期望的那样。 GHCi 不幸的是与 'real' Haskell 代码几乎没有相似之处,因为 Haskell 与大多数其他语言的工作方式根本不同。

GHCi 只不过是一种快速查看代码结果的有用方法,以及我不会在这里解释的其他有用信息。

其他积分

Monad 是这里问题的关键部分。在 Whosebug 上有很多很多关于 Monad 的问题,因为它们往往是一个症结所在,所以你可能有的任何问题都可能在这里。这使事情变得困难,因为 Haskell 如果不了解 Monad 就无法完全理解 IO。有一个 LYAH chapter on Monads.

因为它们不是一回事Haskell是纯函数式编程。 没有副作用,例如,如果求和函数更改全局变量,或在返回之前打印总和,这些都是副作用。大多数其他语言中的函数经常有副作用,通常使它们难以分析。函数式编程语言尽可能避免副作用。

在这种情况下,读取和写入 IO 是副作用。 有副作用的操作由 Monad (do) == (>>=)

处理

这是有效代码

palindrome :: String -> Bool
palindrome x = x == reverse x
main = do
  putStrLn "Brendon Bailey CSCI 4200-DB"
  putStrLn "Enter Word"
  x <- getLine
  let y = palindrome x
  putStrLn "thanks"
  print y

这里有关于Monads

的很好的解释