Предположим, мы хотим написать нашу собственную sum функцию в Haskell:

sum' :: (Num a) => [a] -> a
sum' [] = 0
sum' (x:xs) = x + sum' xs

Почему мы не можем сделать что-то вроде:

sum' :: (Num a) => [a] -> a
sum' [] = 0
sum' (xs++[x]) = x + sum' xs

Другими словами, почему мы не можем использовать ++ в сопоставлении с образцом?

10
Abhisek 26 Фев 2018 в 19:12

4 ответа

Лучший ответ

Вы можете сопоставлять шаблоны только с конструкторами , но не с общими функциями.

Математически конструктор - это инъективная функция: каждая комбинация аргументов дает одно уникальное значение, в данном случае список. Поскольку это значение уникально, язык может деконструировать его снова в исходные аргументы. То есть, когда вы сопоставляете шаблон с :, вы по существу используете функцию

uncons :: [a] -> Maybe (a, [a])

Которая проверяет, имеет ли список форму, которую вы могли бы построить с помощью : (то есть, если она не пустая), и если да, возвращает вам голову и хвост.

++ не является инъективным, например,

Prelude> [0,1] ++ [2]
[0,1,2]
Prelude> [0] ++ [1,2]
[0,1,2]

Ни одно из этих представлений не является правильным , так как же следует снова деконструировать список?

Однако вы можете определить новый «виртуальный» конструктор, который действует как : в том смысле, что он всегда отделяет ровно один элемент от остальной части списка (если это возможно), но делает это справа:

{-# LANGUAGE PatternSynonyms, ViewPatterns #-}

pattern (:>) :: [a] -> a -> [a]
pattern (xs:>ω) <- (unsnoc -> Just (xs,ω))
 where xs:>ω = xs ++ [ω]

unsnoc :: [a] -> Maybe ([a], a)
unsnoc [] = Nothing
unsnoc [x] = Just x
unsnoc (_:xs) = unsnoc xs

Затем

sum' :: Num a => [a] -> a
sum' (xs:>x) = x + sum xs
sum' [] = 0

Обратите внимание, что это очень неэффективно, потому что шаблон-синоним :> на самом деле должен просматривать весь список, поэтому sum' имеет квадратичную, а не линейную сложность.

Контейнер, который позволяет сопоставлять шаблоны с левой и правой стороны, является Data.Sequence с синонимами шаблонов :<| и :|>.

15
leftaroundabout 26 Фев 2018 в 16:30

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

Мы можем сказать, что это за правила, но большинство объяснений, почему правила являются такими, какими они являются, начинаются с чрезмерного обобщения вопроса, касающегося того, почему мы не можем сопоставить шаблон с какой-либо старой функцией (бормоча Пролог). Это должно игнорировать тот факт, что ++ не является какой-либо старой функцией: это (пространственно) линейная функция pluging-stuff-Вместе, вызванная структурой zipper списков. Сопоставление с образцом - это то, что нужно разбирать вещи, и, на самом деле, отмечать процесс с точки зрения подключаемых устройств и переменных шаблона, обозначающих компоненты. Его мотивация - ясность. Так что я хотел бы

lookup :: Eq k => k -> [(k, v)] -> Maybe v
lookup k (_ ++ [(k, v)] ++ _) = Just v
lookup _ _                    = Nothing

И не только потому, что это напомнило бы мне о забаве, которую я имел тридцать лет назад, когда я реализовал функциональный язык, сопоставление с образцом которого предлагало именно это

Возражение, что это неоднозначно, является законным, но не нарушителем. Pluget-togetherers, такие как ++, предлагают только конечное число разложений с конечным вводом (и если вы работаете с бесконечными данными, это ваш собственный взгляд), так что, скорее всего, в худшем случае search чем magic (придумывая произвольные входные данные, которые могли бы выбросить произвольные функции). Поиск требует некоторых способов определения приоритетов, но также и наши упорядоченные правила соответствия. Поиск также может привести к сбою, но, опять же, может совпадение.

У нас есть разумный способ управления вычислениями, предлагающими альтернативы (сбой и выбор) через абстракцию Alternative, но мы не привыкли думать о сопоставлении с образцом как о форме такого вычисления, поэтому мы используем {{X1} } структура только на языке expression . Благородное, если и не смешное, исключение - это ошибка совпадения в нотации do, которая вызывает соответствующий fail, а не обязательно вылетает. Сопоставление с образцом - это попытка вычислить среду, подходящую для оценки выражения «с правой стороны»; Неспособность вычислить такую среду уже обработана, так почему бы не сделать выбор?

( Редактировать . Разумеется, я должен добавить, что поиск действительно нужен только в том случае, если в шаблоне имеется более одной растянутой вещи, поэтому предлагаемый шаблон xs++[x] не должен вызывать никаких вариантов выбора. Конечно, требуется время, чтобы найти конец списка.)

Представьте, что есть какая-то забавная скобка для написания Alternative вычислений, например, (|) означает empty, (|a1|a2|) означает (|a1|) <|> (|a2|) и обычный старый {{ X5}} означает pure f <*> s1 .. <*> sn. Можно также представить себе (|case a of {p1 -> a1; .. pn->an}|), выполняющий разумный перевод поисковых шаблонов (например, включающий ++) в терминах Alternative комбинаторов. Мы могли бы написать

lookup :: (Eq k, Alternative a) => k -> [(k, v)] -> a k
lookup k xs = (|case xs of _ ++ [(k, v)] ++ _ -> pure v|)

Мы можем получить разумный язык шаблонов поиска для любого типа данных, генерируемого точками фиксирования дифференцируемых функторов: символическое дифференцирование - это именно то, что превращает кортежи структур в выбор возможных подструктур. Старый добрый ++ - это просто пример с подсписками списков (который сбивает с толку, потому что список с дырой для подсписка очень похож на список, но это не так для другие типы данных).

Весело, с пятном LinearTypes, мы могли бы даже удерживать дырявые данные за их дыры, а также за их корень, а затем разрушительно уничтожать в постоянном времени. Это скандальное поведение, только если вы не замечаете, что делаете это.

13
pigworker 27 Фев 2018 в 17:27

Вы можете сопоставлять шаблоны только с конструкторами данных, и ++ является функцией, а не конструктором данных.

Конструкторы данных являются постоянными; значение типа 'c':[] не может быть дополнительно упрощено, поскольку оно является фундаментальным значением типа [Char]. Выражение типа "c" ++ "d", однако, может быть заменено его эквивалентом "cd" в любое время и, таким образом, не может быть надежно использовано для сопоставления с образцом.

(Вы можете утверждать, что "cd" всегда можно заменить на "c" ++ "d", но в общем случае нет однозначного сопоставления между списком и разложением через ++. Is {{ X3}} эквивалентно "c" ++ "de" или "cd" ++ "e" для целей сопоставления с образцом?)

9
chepner 26 Фев 2018 в 16:16

++ не конструктор, это просто простая функция. Вы можете соответствовать только конструкторам.

Вы можете использовать ViewPatterns или PatternSynonyms, чтобы расширить ваши возможности сопоставления с образцом (спасибо @luqui).

6
Carcigenicate 26 Фев 2018 в 22:32