此实例似乎表现不正常:
> guard True <|> guard False
> guard False <|> guard False
*** Exception: user error (mzero)
有人可能会说这不会导致任何其他问题。但为什么要首先定义这样的实例呢?每当评估没有意义时,是否有充分的理由导致_|_
?
答案 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; empty
是Alternative
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
只有empty
,False
时empty <|> pure ()
empty <|> empty
。这意味着您的示例实际上等同于以下内容:
empty
在第一个示例中,(<|>)
会抛出,pure ()
会抓住它并返回()
,这显然会产生empty
。在第二个例子中,同样的事情发生,除了第二个参数也是src="{{ asset('images/background.jpg') }}"
,因此表达式的结果也会引发异常。