Подтвердить что ты не робот

В Haskell предпочтительны ли защитники или помощники?

Я изучаю Haskell, и мне не всегда ясно, когда использовать помощник и когда использовать охранника. Для определенных сценариев кажется, что помощники и охранники могут использоваться для достижения практически тех же целей. Существуют ли какие-то правила или эвристики, когда лучше использовать спички над стражами или наоборот? Является ли еще один исполнитель, чем другой?

Чтобы проиллюстрировать то, что я получаю, вот несколько глупых примеров, которые я приготовил, которые кажутся эквивалентными, но одна версия использует матчи, а другая использует защитные устройства:

listcheck :: [a] -> String
listcheck [] = "List is null :-("
listcheck a = "List is NOT null!!"

listcheck' a
    | null a = "List is null :-("
    | otherwise = "List is NOT null!!"

и

luckyseven :: Int -> String
luckyseven 7 = "SO LUCKY!"
luckyseven b = "Not so lucky :-/"

luckyseven' c
    | c == 7 = "SO LUCKY!"
luckyseven' c = "Not so lucky :-/"

Спасибо!

4b9b3361

Ответ 1

Они часто могут использоваться взаимозаменяемо, но между ними существуют значительные различия. Согласование шаблонов может возникать только в конструкторах, поэтому вычисления не могут выполняться внутри шаблона, в то время как охранники - это просто многоотводные инструкции if-else. Например, я не могу написать эквивалент шаблона следующего:

func :: Int -> Int
func x
    | even x = 3 * x
    | odd x  = 7 * x        -- alternatively "otherwise = 7 * x" to get rid of all those pesky compiler warnings

Это просто не возможно с помощью простого сопоставления шаблонов. Вы также не можете делать такие вещи, как

func :: Int -> Maybe String
func x
    | x < 0     = Nothing
    | x == 0    = Just "Zero"
    | x < 20    = Just "Small"
    | x < 100   = Just "Big"
    | x < 1000  = Just "Huge"
    | otherwise = Just "How did you count that high?"

И наоборот, охранники, использующие ADT, не предоставляют вам много информации без вспомогательных функций. Если бы у меня был тип

data Expr
    = Literal Int
    | Add  Expr Expr
    | Mult Expr Expr
    | Negate Expr
    deriving (Eq, Show)

Использование охранников для записи эквивалента

eval :: Expr -> Int
eval (Literal i)  = i
eval (Add  e1 e2) = eval e1 + eval e2
eval (Mult e1 e2) = eval e1 * eval e2
eval (Negate e)   = negate (eval e)

было бы намного более многословным, трудным и раздражающим. На самом деле, на каком-то уровне вам придется прибегнуть к сопоставлению с образцом, чтобы сделать что-то вроде

getLiteral :: Expr -> Int
getLiteral (Literal i) = i
getLiteral _           = error "Not a literal"

Что вводит функции, которые могут error, что плохо. В этом случае использование сопоставления шаблонов намного предпочтительнее использования защитных устройств.

Ответ 2

В ваших конкретных примерах я бы пошел с сопоставлением с образцом, но использовал бы там, где это возможно:

listCheck :: [a] -> String
listCheck [] = "List is null :-("
listCheck _  = "List is NOT null!!"

и

luckySeven :: Int -> String
luckySeven 7 = "SO LUCKY!"
luckySeven _ = "Not so lucky :-/"

Это подчеркивает, что если список не пуст или Int не равен 7, ничего больше не имеет значения, и вы не собираетесь использовать его конкретное значение для получения результата функции. bheklilr умело указал места, где один выбор или другой, безусловно, предпочтительнее.