替代IO实例的目的是什么?

时间:2018-01-25 19:51:40

标签: haskell

此实例似乎表现不正常:

> guard True <|> guard False

> guard False <|> guard False
*** Exception: user error (mzero)

有人可能会说这不会导致任何其他问题。但为什么要首先定义这样的实例呢?每当评估没有意义时,是否有充分的理由导致_|_

3 个答案:

答案 0 :(得分:7)

来自Data.Foldable

http://sqlfiddle.com/#!18/b979c/1/0可以多次重复IOException - 投掷操作,直到它成功或完全失败为止:

import Data.Foldable (asum)
import Control.Monad
import Control.Exception
import System.Random -- from the "random" package

diceRoll :: IO Int
diceRoll = do
    putStrLn "hi"
    r <- randomRIO (0,20)
    if r < 18
        then throwIO (userError (show r))
        else return r

main :: IO ()
main = do
    r <- asum $ take 7 $ repeat diceRoll
    print r

鉴于&#34;返回第一个没有抛出的动作的结果&#34;语义,empty必须是抛出异常的动作。否则它不会作为中性元素工作,例如empty <|> return 4

这不是 Alternative Maybe实例的行为方式不同。在那里,asum会返回Nothing s序列中的第一个非Maybe值。

(另一个&#34;奇怪&#34; emptyAlternative asum<|>实体,它只是永远等待。{{1}}比赛两个动作互相攻击。)

答案 1 :(得分:7)

Alternative的{​​{1}}实例的目的是将可能失败的IO个操作(通过导致IO错误或以其他方式抛出异常)合并为一个IO &#34;尝试&#34;反过来,接受第一个成功的行动,或者 - 如果所有行动都失败 - 自身失败。

所以,这样的东西可以用来从标准输入读取一行或多行(使用IO),否则(使用some)抱怨如果没有行可用:

<|>

或者你可以这样写:

main = (print =<< some getLine) <|> putStrLn "No input!"

鉴于此,完全合理的是:

readConfig :: IO Config
readConfig = readConfigFile "~/.local/myapp/config"
         <|> readConfigFile "/etc/myapp/config"
         <|> return defaultConfig

表示在执行时必须通过生成异常而失败的操作。如果没有,正如@danidaz指出的那样,那么执行动作:

guard False <|> guard False

不会执行第三个操作。由于guard False <|> guard False <|> putStrLn "success!" 是左关联的并且在其右边之前尝试其左操作,因此执行此表达式的值将只执行代表的任何成功的动作<|>(例如,guard False <|> guard False或其他)并且从不尝试return ()

这里有一个微妙的可能会让你失望。与首次出现相反,价值:

putStrLn "success!"
通常意义上来说,不是guard False <|> guard False 。相反,它是一个非常明确定义的IO动作,如果执行将无法终止抛出异常。但是,这种类型的非终止仍然有用,因为我们可以捕获它(例如,通过添加另一个_|_备选方案!)。

另请注意,由于您未提供更好的异常,因此会抛出<|>的默认异常。如果您通过以下方式导致失败:

userError "mzero"

您会看到,如果所有操作都失败,则抛出的最后一个异常是复合操作引发的异常。

答案 2 :(得分:2)

虽然没有明确记录Alternative,但实例应基本遵守以下法律:

pure x <|> y = pure x
empty <|> x = x

你可以直截了当地实现一些“真实性”和“虚假性”的概念,其中pure x总是真实的,empty总是假的。

为了使IO更有意义,我们需要一些真实性的概念。没有很多好的,但是IO能够处理异常,所以我们可以将truthy IO动作定义为产生值的动作和伪造IO动作作为抛出的动作例外。因此,(<|>) IO运行其第一个参数,如果它生成一个值而不抛出异常,则返回该值;否则,它返回第二个参数。

我们现在对(<|>)IO的定义,但empty应该是什么?好吧,empty必须是假的,我们在IO上将falsiness定义为“抛出异常”。因此,empty必须是引发异常的操作。

guard函数非常简单,因为在给定pure ()True只有emptyFalseempty <|> pure () empty <|> empty 。这意味着您的示例实际上等同于以下内容:

empty

在第一个示例中,(<|>)会抛出,pure ()会抓住它并返回(),这显然会产生empty。在第二个例子中,同样的事情发生,除了第二个参数也是src="{{ asset('images/background.jpg') }}" ,因此表达式的结果也会引发异常。