如何在Haskell中使用if-then-else语句而没有其他条件?

时间:2018-05-22 17:14:21

标签: haskell

我有一份关系清单,我希望打印所有父亲的名字。由于没有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之后发表任何声明。

5 个答案:

答案 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的参数始终返回TrueFalse,但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术语,并且代码不会进行类型检查。