在Haskell中使用防护比使用模式进行递归功能更好吗?

问题描述

| 我只是想知道我在Haskell中布置的递归函数。使用防护比使用模式进行递归功能通常更好吗? 我只是不确定最佳布局是什么,但我确实知道在定义如下函数时模式会更好:
units :: Int -> String

units 0 = \"zero\"
units 1 = \"one\"
相对于
units n
    | n == 0 = \"zero\"
    | n == 1 = \"one\"
我只是不确定,到底是相同还是不同。 只是不太确定术语:我使用的是这样的东西:
f y [] = [] 
f y (x:xs) 
    | y == 0 = ...... 
    | otherwise = ...... 
还是会更好?
f y [] = [] 
f 0 (x:xs) = 
f y (x:xs) =
    

解决方法

我的一般经验法则是这样的: 当防护罩很简单地通过“ѭ4”检验时,请使用模式匹配。 使用递归时,通常会检查基本情况。因此,如果您的基本情况是简单的“ 4”格检查,请使用模式匹配。 因此,我通常会这样做:
map f [] = []
map f (x:xs) = f x : map f xs
而不是这样(
null
只是检查列表是否为空。基本上是
== []
):
map f xs | null xs   = []
         | otherwise = f (head xs) : map f (tail xs)
模式匹配旨在使您的生活更轻松,恕我直言,因此最终您应该做对您有意义的事情。如果您与一个小组一起工作,请对小组有意义。 [更新] 对于您的特殊情况,我会执行以下操作:
f _ []      = []
f 0 _       = ...
f y (x:xs)  = ...
模式匹配(如守卫)从上到下下降,在与输入匹配的第一个定义处停止。我使用下划线符号表示对于第一个模式匹配,我不在乎
y
参数是什么,对于第二个模式匹配,我不在乎list参数是什么(尽管在该计算中使用列表,则不应使用下划线)。由于它仍然是相当简单的类似于“ 4”字样的检查,因此我个人坚持使用模式匹配。 但是我认为这是个人喜好问题;您的代码是完全可读且正确的。如果我没记错的话,则在编译代码时,防护和模式匹配最终都会变成case语句。     ,一个简单的规则 如果要递归数据结构,请使用模式匹配 如果递归条件比较复杂,请使用防护措施。 讨论区 从根本上讲,这取决于您希望进行递归保护的测试。如果这是对数据类型的结构的测试,请使用模式匹配,因为它比冗余的相等测试更有效。 对于您的示例,整数上的模式匹配显然更清洁,更高效:
units 0 = \"zero\"
units 1 = \"one\"
对于任何数据类型的递归调用也是如此,您可以通过数据的形状区分大小写。 现在,如果您具有更复杂的逻辑条件,那么警卫就有意义。     ,对此并没有严格的规定,这就是为什么您得到的答案有些朦胧的原因。有些决定很容易,例如在ѭ14上进行模式匹配而不是用ѭ15进行保护,或者天堂禁止
f xs | length xs == 0 = ...
,这在多种方面都非常糟糕。但是,当没有引人注目的实际问题时,请使用使代码更清晰的任何一种。 作为示例,请考虑以下功能(实际上并没有做任何有用的工作,仅用作说明):
f1 _ [] = [] 
f1 0 (x:xs) = [[x],xs]
f1 y (x:xs) = [x] : f1 (y - 1) xs

f2 _ [] = []
f2 y (x:xs) | y == 0    = calc 1 : f2 (- x) xs
            | otherwise = calc (1 / y) : f2 (y * x) xs
  where calc z = x * ...
在《 18》中,单独的模式强调了递归有两个基本情况。在ѭ19中,警卫队强调,0只是某些计算的特殊情况(其中大部分由
calc
完成,在in警卫两个分支共享的
where
子句中定义),并且不会改变计算的结构。     ,@Dan是正确的:这基本上是个人喜好,不会影响所生成的代码。该模块:
module Test where

units :: Int -> String
units 0 = \"zero\"
units 1 = \"one\"

unitGuarded :: Int -> String
unitGuarded n
  | n == 0 = \"zero\"
  | n == 1 = \"one\"
产生了以下核心:
Test.units =
  \\ (ds_dkU :: GHC.Types.Int) ->
    case ds_dkU of _ { GHC.Types.I# ds1_dkV ->
    case ds1_dkV of _ {
      __DEFAULT -> Test.units3;
      0 -> Test.unitGuarded2;
      1 -> Test.unitGuarded1
    }
    }

Test.unitGuarded =
  \\ (n_abw :: GHC.Types.Int) ->
    case n_abw of _ { GHC.Types.I# x_ald ->
    case x_ald of _ {
      __DEFAULT -> Test.unitGuarded3;
      0 -> Test.unitGuarded2;
      1 -> Test.unitGuarded1
    }
    }
完全相同,不同的默认情况除外,这两种情况都是模式匹配错误。 GHC甚至为匹配的案例共享了字符串。     ,到目前为止的答案没有提到模式匹配的优点,这对我来说最重要:安全实现全部功能的能力。 在进行模式匹配时,您可以安全地访问对象的内部结构,而不必担心该对象是其他东西。万一您忘记了某些模式,编译器会警告您(不幸的是,默认情况下,此警告在GHC中处于关闭状态)。 例如,编写此代码时:
map f xs | null xs   = []
         | otherwise = f (head xs) : map f (tail xs)
您被迫使用非总计函数
head
tail
,从而冒着程序生命的危险。如果您在保护条件上犯了一个错误,编译器将无济于事。 另一方面,如果在模式匹配中出错,则编译器会根据错误的严重程度为您提供错误或警告。 一些例子:
-- compiles,crashes in runtime
map f xs | not (null xs)   = []
         | otherwise = f (head xs) : map f (tail xs)

-- does not have any way to compile
map f (h:t) = []
map f [] = f h : map f t


-- does not give any warnings
map f xs = f (head xs) : map f (tail xs)

-- can give a warning of non-exhaustive pattern match
map f (h:t) = f h : map f t