Combining and splitting assignment in Haskell IO do block

北战南征 提交于 2019-12-13 07:09:27

问题


I /think/ I have a similar misunderstanding of the language in two places involving how variable assignment works in do blocks, involving the IO monad. Could you help me understand (1) is it the same misunderstanding, (2) how to clear it up (in an answer, and maybe specifically if you have a favorite reference on the subject)?

I find that I can perform an operation successfully when it is all one line, but not when I try to split into 2 for readability.

Part I: Turning 1 line into 2

Why does this work?

ipg :: IO ()
ipg = do
  conn <- connect defaultConnectInfo { connectHost = "0.0.0.0"}
  res <- execute conn "INSERT INTO test (num, data) VALUES (?, ?)" $ MyRecord (Just 200) (Just"Test")
  print res

But this not work

ipg :: IO ()
ipg = do
  conn <- connect defaultConnectInfo { connectHost = "0.0.0.0" }
  q <- "INSERT INTO test (num, data) VALUES (?, ?)" $ MyRecord (Just 200) (Just"Test")
  res <- execute conn q
  print res

Gives me:

Couldn't match expected type ‘IO a0’
            with actual type ‘q0 -> IO GHC.Int.Int64’
Probable cause: ‘execute’ is applied to too few arguments
In a stmt of a 'do' block: res <- execute conn q

The difference between the first and second being trying to store the query portion in q.

Part II: Turning 2 lines into 1

Why does this work:

myinput :: IO ()
myinput = do
  putStrLn "Please input a number."
  mynum :: Int  <- readLn  
  print mynum

But this not work?

myinput :: IO ()
myinput = do
  mynum :: Int <- readLn $ putStrLn "Please input a number."
  print mynum

Gives me

Couldn't match expected type ‘IO () -> IO Int’
            with actual type ‘IO a0’
The first argument of ($) takes one argument,

回答1:


In

execute conn "INSERT INTO test (num, data) VALUES (?, ?)" $ MyRecord (Just 200) (Just "Test")

, the left-hand side of the $ operator is execute conn "INSERT…", and the right-hand side is MyRecord …. That is, you're calling execute with three arguments: the connection, the query, and the parameters. That’s the first problem:

q <- "INSERT INTO test (num, data) VALUES (?, ?)" $ MyRecord (Just 200) (Just "Test")
res <- execute conn q

Here, the left-hand side of the $ operator is the string "INSERT…", and the right-hand side is the parameters. You're trying to call a string, then pass the result as the second argument to execute.

If q could be some strange type that represented two arguments, though, it probably wouldn’t be an IO a. You’re looking to just name a value with let, not run an action.

This should work:

ipg :: IO ()
ipg = do
  conn <- connect defaultConnectInfo { connectHost = "0.0.0.0" }
  let query = "INSERT INTO test (num, data) VALUES (?, ?)"
  let parameters = MyRecord (Just 200) (Just "Test")
  res <- execute conn query parameters
  print res

myinput :: IO ()
myinput = do
  mynum :: Int <- readLn $ putStrLn "Please input a number."
  print mynum

This one just doesn’t make a lot of sense. Maybe it’s a misunderstanding of the $ operator? $ is mostly just a way to write expressions without using parentheses; f $ x is equivalent to f x, but the $ has low precedence, so you can write f $ 1 + 2 instead of f (1 + 2).

Anyway, I’ll loosely translate it into Python for you, if that helps:

def myinput():
    mynum = int(input(print("Please input a number.")))
    print(mynum)

If you want to sequence the readLn and putStrLn actions, you can use the >> operator (which is what do is doing behind the scenes):

myinput :: IO ()
myinput = do
  mynum :: Int <- putStrLn "Please input a number." >> readLn
  print mynum

That’s not very good for readability most of the time, though. (a >> b will also discard the result of a without complaint, whereas do { a; b } will give a compiler warning if discarding something that isn’t ().)



来源:https://stackoverflow.com/questions/39193147/combining-and-splitting-assignment-in-haskell-io-do-block

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!