Как защитные функции Haskell могут работать с другими значениями, чем параметры функций?

В http://lisperati.com/haskell/ht4.html автор показывает функции, которые читают полигоны из простого файла SVG. Я понимаю большую часть кода, однако я задавался вопросом, можно ли переписать функцию

  let readPoint :: String -> Point
      readPoint s | Just [x,y] <- matchRegex (mkRegex "([0-9.]+),([0-9.]+)") s = (read x,read y)

в более понятной форме. Я обнаружил, что эта строка немного озадачена, так как охранники должны работать с параметрами функции (в данном случае "readPoint" ), но здесь защита, очевидно, работает с результатом matchRegex.

Так может кто-нибудь объяснить магию за этим?

И можно ли это переписать в более понятную форму?

Ответ 1

Вы можете думать о стражах как о синтаксическом сахаре для утверждения if. Выражение в guard может быть любым допустимым булевым выражением, как и в if-statements. Это означает, что вы можете использовать любые значения и функции в области.

Например, вы можете переписать следующее:

foo x | abc = ...
      | def = ...
      | otherwise = ...

как

foo x = if abc then ... else if def then ... else ...

Мы также можем записать это немного более подробно с помощью case, а не if:

foo x = case abc of
  True -> ...
  False -> case def of
    True -> ...
    False -> ...

В конце концов, if сам по себе является просто синтаксическим сахаром для случая! Написание всего в терминах case упрощает просмотр того, как разные функции являются просто синтаксическим сахаром для одной и той же вещи.

Второе выражение явно имеет смысл, хотя условия ссылаются на существующие переменные (abc и def), а не на параметр функции x; охранники работают одинаково.

Пример немного сложнее, потому что он использует расширение, называемое "защитой шаблонов" . Это означает, что защита может быть больше, чем просто логическая - она ​​также может попытаться сопоставить шаблон. Если шаблон соответствует, защита будет успешной (например, это то же самое, что защита с True); в противном случае защитник не может соответствовать (как получение False).

Мы могли бы переписать его следующим образом:

readPoint s | Just [x, y] <- matchRegex (mkRegex "...") s = ...
            | otherwise = ...

а

readPoint s = case matchRegex (mkRegex "...") s of
                Just [x, y] -> ...
                _ -> ...

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

Опять же, я уверен, вы согласитесь, что имеет смысл разрешить любое выражение в инструкции case - нет причин ограничивать его использованием аргументов функции. То же самое верно для охранников, потому что они действительно просто синтаксический сахар.

Ответ 2

Это называется защитой шаблона - прочитайте об этом здесь.

Это эквивалентно

readPoint :: String -> Point
readPoint s = case matchRegex (mkRegex "([0-9.]+),([0-9.]+)") s of
                   Just [x,y] -> (read x,read y) 

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

readPoint :: String -> Point
readPoint s | Just [x,y] <- matchRegex (mkRegex "([0-9.]+),([0-9.]+)") s = (read x,read y)
            | Just [x] <- matchRegex (mkRegex "([0-9.]+)") s = (read x,0)
            | otherwise = (0,0)

чтобы заменить более подробные

readPoint :: String -> Point
readPoint s  = case matchRegex (mkRegex "([0-9.]+),([0-9.]+)") s  of 
                    Just [x,y] -> (read x,read y)
                    Nothing -> case matchRegex (mkRegex "([0-9.]+)") s of
                                    Just [x] -> (read x,0)
                                        Nothing -> (0,0)