我有一份关系清单,我希望打印所有父亲的名字。由于没有else
条件,以下代码不起作用:
relations = [("father", "arushi", "anandan"), ("mother", "arushi", "abigale"), ("father", "anandan", "ayuta"), ("mother", "anandan", "akanksha")]
father ((r, c, f):xs) = if r == "father" then print(f)
main = do
father (relations)
我不希望在else
之后发表任何声明。
答案 0 :(得分:20)
太糟糕了,所有if
都带有else
个。但是没关系,这是一项杰出的无所事事IO
行动。
father ((r, c, f):xs) = if r == "father" then print f else return ()
还有很多其他方法可以让这只猫受伤。一个是模式匹配。
father (("father", c, f):xs) = print f
father ((r, c, f):xs) = return ()
另一个特定于monadic动作的是使用when
。
father ((r, c, f):xs) = when (r == "father") (print f)
当然,那只是隐藏了else
,这又是return ()
:
when p act = if p then act else pure () -- okay, it's actually pure, not return
答案 1 :(得分:11)
解决此类问题的惯用Haskell方法是尽可能避免混合计算和I / O.
在这种情况下,您可以先“计算所有父亲的名字”(这里没有I / O),然后“打印计算名称”(这里是I / O),而不是“打印所有父亲的名字”。
relations =
[ ("father", "arushi", "anandan")
, ("mother", "arushi", "abigale")
, ("father", "anandan", "ayuta")
, ("mother", "anandan", "akanksha")
]
-- compute only the fathers
fathers = [ f | ("father", _, f) <- relations ]
-- print them
main :: IO ()
main = mapM_ putStrLn fathers
不需要if
,因为mapM_
会为我们遍历列表,并且必须打印所有列表条目。
答案 2 :(得分:6)
每个if
必须有else
。
father ((r, c, f):xs) =
if r == "father"
then print f
else _what
如果你试图编译它,你会被告知有一个漏洞
_what :: IO ()
所以你需要制造那种类型的东西。幸运的是,这很简单:
father ((r, c, f):xs) =
if r == "father"
then print f
else pure ()
pure x
不执行任何操作并返回x
。
由于您要做的事情非常普遍,因此有两个专门为此任务设计的功能:
when :: Applicative f => Bool -> f () -> f ()
when b m = if b then m else pure ()
unless :: Applicative f => Bool -> f () -> f ()
unless = when . not
您可以在Control.Monad
。
father ((r, c, f):xs) =
when (r == "father") $ print f
答案 3 :(得分:6)
您可以编写始终写入名称的函数,但确保仅在包含father
的值上调用它。
relations :: [(String,String,String)]
relations = [("father", "arushi", "anandan")
,("mother", "arushi", "abigale")
,("father", "anandan", "ayuta")
,("mother", "anandan", "akanksha")
]
printName :: (String,String,String) -> IO ()
printName (_, _, name) = print name
printFathers :: [(String,String,String)] -> [IO ()]
printFathers = fmap printName . filter (\(f, _, _) -> f == "father")
main = sequence (printFathers relations)
filter
的定义隐藏了跳过列表中某些元素的逻辑。 filter
的参数始终返回True
或False
,但filter
的结果仅包含您要为其调用print
的元素。
(sequence
,在这里,只需将IO
值列表转换为IO
必须通过&#34;交换&#34; {main
的单个IO
值{1}}和[]
。您可以将其定义为printName
,将其定义为sequence . fmap printName . ...
,并将sequence . fmap foo
替换为traverse foo
。)
请注意,if foo then bar else baz
是完整case
表达式的语法糖
case foo of
True -> foo
False -> baz
但是,case
表达式不必处理foo
参数的每个可能值。你可以写
father ((r, c, f):xs) = (case r of "father" -> print f) : father xs
但是,如果r
没有&#em>匹配"father"
,会发生什么事情会很有启发性。
答案 4 :(得分:1)
我认为需要解释为什么如果必须在Haskell中有一个else。
Haskell是类型化lambda演算的一种实现,在lambda演算中,我们只有表达式和值。 在其中,我们评估/减少表达式的值或表达式,不能进一步减少。
现在在类型化的lambda演算中,我们添加了类型和抽象,但我们仍然要评估其中一个表达式为if predicate then value else value
的值和表达式。
此if
表达式必须减少为一个值,因此if表达式的两个分支必须减少为相同类型的值。
如果我们有一个&#34; if谓词那么值&#34;这意味着我们将拥有一个不会降低到某个值的分支。
您可以在此答案的上下文中互换使用运行, reduce 和评估。
当我们运行Haskell代码时,我们将lambda术语减少为不能进一步减少的值或表达式。 编译器的存在是为了帮助我们编写有效的lambda术语。
通过lambda演算,我们看到if语句在评估时必须减少到一个值(或者能够这样做),并且因为Haskell实现了类型化的lambda演算,如果Haskell中的表达式没有其他的话就不会出现这种情况。 t有可能一直评估值。
TL; DR
&#34; if ... then ... else&#34;声明应当在评估时减少到值。 只要if语句的两个分支都评估为相同的类型,它就会正确计算。
如果任何分支没有评估某个值,或者要评估不同类型的值,这些值不是有效的lambda术语,并且代码不会进行类型检查。