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

8oomwypt  于 2022-12-13  发布在  其他
关注(0)|答案(6)|浏览(156)

我有一个关系列表,我希望打印所有父亲的名字。由于没有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之后作任何发言。

woobm2wo

woobm2wo1#

很糟糕,所有的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 ()

另一个特定于一元动作的方法是使用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
fwzugrvs

fwzugrvs2#

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_为我们遍历列表,并且必须打印所有列表条目。

unguejic

unguejic3#

每个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
vlju58qv

vlju58qv4#

您可以编写一个函数,它 * 总是 * 写入名称,但要确保它只在包含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值,通过“交换”IO[]main必须是IO值。您可以将其合并到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 * 与"father" * 不匹配时会发生什么,这将是有启发性的。

fd3cxomn

fd3cxomn5#

我觉得有必要解释一下为什么一个if在Haskell里必须有一个else。
Haskell是类型化lambda演算的一个实现,在lambda演算中我们有表达式没有其他东西。在它中,我们将表达式求值/约简为值或不能进一步约简的表达式。
在类型化lambda演算中,我们增加了类型和抽象,但是我们仍然要计算值和表达式,其中一个表达式是if predicate then value else value。这个if表达式必须约简为一个值,因此if表达式的两个分支都必须约简为相同类型的值。如果我们有一个“if predicate then值”,这意味着我们有一个分支不会约简为一个值。
您可以在此答案的上下文中交替使用 runreduceevaluate
当我们运行Haskell代码时,我们将lambda项简化为无法进一步简化的值或表达式。编译器的存在是为了帮助我们编写有效的lambda项。
通过lambda演算,我们看到if语句在求值时必须化简为一个值(或者能够这样做),因为Haskell是实现类型化lambda演算的,Haskell中的if表达式如果没有else,就不可能 * 一直 * 求值为一个值。
TL;DR
“if... then... else”陈述式在评估时应该减少为value。只要if陈述式的两个分支评估为相同的型别,它就会正确评估。
如果任何分支没有计算出一个值,或者将要计算出不同类型的值,这不是一个有效的lambda项,代码将不会进行类型检查。

lskq00tm

lskq00tm6#

我也在为这种风格和Haskell格式要求而挣扎。但我发现,与其强调必需的[else],不如使用[else do]来包括所有后续代码行,而无需在并发行上添加额外的缩进,例如..

main = do
    --if conditions exit main, 
    if t < 1 || t > 100000 
    then return ()
    else
        do
    --code here, even with the else, 
    -- is only run when if condition above -> is false

证明代码可以采用更简单的形式

if True
    return ()
else
    do
-- add code below this line to prove return works

相关问题