Haskell IO (String) and String

7
我希望编写函数并将结果放入字符串中。
我需要的函数是:
read' :: FilePath -> String

我使用:

:t readFile
readFile :: FilePath -> IO String

我做:

read' :: IO ()
read' = do
     str <- readFile "/home/shk/workspace/src/test.txt" 
     putStrLn str

我想问 str 是字符串还是其他类型?

我们知道:

:t putStrLn
putStrLn :: String -> IO ()

那为什么我不能:
read' :: String
read' = do
     str <- readFile "/home/shk/workspace/lxmpp/src/test.txt" 
     str

我得到了这样的错误信息:

出现错误:

 Couldn't match expected type `[t0]' with actual type `IO String'
    In the return type of a call of `readFile'
    In a stmt of a 'do' expression:
        str <- readFile "/home/shk/workspace/lxmpp/src/test.txt"
    In the expression:
      do { str <- readFile "/home/shk/workspace/src/test.txt";
           str }

谢谢。

4
在 do-notation 中使用 readFile 暗示你正在使用 IO monad,并且无法逃脱 IO monad! - is7s
除非你使用unsafePerformIO,否则无法@is7s! - alternative
10
unsafePerformIO 的第一条规则是不要告诉任何人你正在使用 unsafePerformIO! - Thomas M. DuBuisson
@Thomas 哎呀,看起来《Real World Haskell》违反了那个规则。 - alternative
1
@Thomas 除非他理解广义的prepohistozygomorphisms。 - fuz
我们真的希望有问题的抽象重复,这应该是一个TFAQ,其中T =“太”。 - yatima2975
4个回答

10

稍微挑剔一下,虽然其他答案完全正确,但我想强调一些内容:类型为 IO String 的东西不仅仅是类型系统不允许您直接获取的字符串。它是一个进行 I/O 操作以获取字符串的计算过程。将 readFile 应用于文件路径并不会返回 String 值,就像将牛排放在绞肉机旁边就会神奇地变成汉堡包一样。

当您有以下代码:

foo = do let getStr = readFile "input.txt"
         s1 <- getStr
         s2 <- getStr
         -- etc.

这并不意味着你“两次取出了 getStr 字符串”。它意味着你执行了两次计算,可能会在两次计算之间轻松获得不同的结果。


神奇汉堡的好处在于你可以放心食用,不必担心副作用! - pat

6

我认为还没有人回答这个非常重要的问题:

我想问一下,str是字符串吗?

我来试试。

变量 str 的类型是 String,是的。 然而,这个变量的作用范围非常有限。为了理解,我认为需要展开 do-notation:

read' = readFile "/home/shk/workspace/src/test.txt" >>= (\str -> putStrLn str)

我认为在这里更清楚的原因是为什么 str 不够好。它是您传递给 >>= 函数的一个 参数。只有当调用您的函数时,其值才会变得可用,这只会在执行包含它的 IO 操作时发生。
此外,read' :: IO () 的类型不是由 putStrLn str 决定的,而是由运算符 >>= 的返回类型决定的。看一下它(专为 IO 单子特别化):
(>>=) :: IO a -> (a -> IO b) -> IO b

您可以看到,结果始终是一个IO b动作,因此尝试更改任何参数都无济于事。
如果您想了解为什么类型是这样的,可以阅读一些单子教程。其背后的直觉是:没有执行操作就不能执行操作。
在实际问题的实践方面,要使用某些操作返回的值,而不是尝试执行use(extractValue inputAction),因为extractValue是不可能的,请尝试inputAction >>= use(如果您的use涉及I / O),或者fmap use inputAction(如果它不涉及I / O)。

4
如果您希望在read'中返回str而不是(),则应使用return str。由于它不是一个纯函数,因此您不能从read'的类型中删除IO。为了更好地掌握Haskell中的输入/输出工作原理,我建议您阅读教程

3
虽然这是正确的,但请注意,绑定 <- 然后立即 return 它是没有意义的。你可以直接写成 read' = readFile "/path/to/test.txt"。这是第二个单子律 - hammar

1
作为更详细的原因:它允许不纯。
在纯操作期间绝对不能执行IO,否则它将完全破坏引用透明性。从技术上讲,您可以使用unsafePerformIO,但在这种情况下它会破坏引用透明性 - 只有在您可以保证结果始终相同的情况下才应该使用它。

1
结果相同的保证足以声称纯洁吗?我的意思是,unsafePerformIO (putStrLn "hello!") 可以被称为纯洁的吗? - Rotsor
1
@Rotsor:不要忘记它也必须是线程安全的。手动保证纯洁性很困难! - C. A. McCann
@camccann,我不确定如何解释你的评论。你是在扩展单子用户定义的纯度概念,还是在暗示实现它的实际困难?如果没有扩展,那么可以通过safePerformIO = unsafePerformIO :: IO () -> ()来实现,对吧?这是不好的...还是说可以接受呢? - Rotsor
@Rotsor:啊,抱歉表达不够清晰。我回答你的问题时说“不行,这还不够”,并且增加了一个额外的要求,即使它只使用ST风格的包含杂质,它也必须独立于可能的并发访问资源。在存在unsafePerformIO的情况下确保纯度非常困难。 - C. A. McCann
@Rotsor 我认为将结果打印到屏幕上是函数的一个结果。但是,是的,通常这是它不是纯的标志^_^。基本上,您不应该关心先前计算的值是否被使用。 - alternative

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接