Почему активные шаблоны требуют специального синтаксиса?
Если обычные функции можно использовать в качестве шаблонов, это избавит от необходимости писать тривиальные активные шаблоны, такие как
let (|NotEmpty|_|) s = Seq.tryPick Some s
и, гипотетически, позволит
let s = seq []
match s with
| Seq.tryPick Some -> ...
| _ -> //empty
Это сделало бы функции более пригодными для повторного использования, избавив от необходимости "шаблонизировать" функции, которые вы хотите использовать при сопоставлении:
let f x = if x then Some() else None
let (|F|_|) = f
Я знаю, что активные шаблоны могут быть вызваны как функции, поэтому предыдущий пример можно упростить, определив только шаблон. Но отказ от специального синтаксиса шаблонов упрощает это.
Каковы причины специального синтаксиса?
РЕДАКТИРОВАТЬ
В следующем примере активный шаблон затеняет литерал.
[<Literal>]
let X = 1
let (|X|_|) x = if x = 0 then Some() else None
match 0 with //returns true
| X -> true
| _ -> false
Почему это не работает и для вызовов функций внутри шаблонов?
РЕДАКТИРОВАТЬ 2
Я нашел сценарий, который был бы неоднозначным
let zero n = if n = 0 then Some() else None
match 0 with
| zero -> //function call or capture?
Это, на мой взгляд, проясняет, почему активный шаблон должен начинаться с заглавной буквы - это делает намерение более ясным и делает затенение, как в моем предыдущем примере, гораздо менее вероятным.
2 ответа
Шаблоны и переменные с привязкой по допустимому значению имеют разные пространства имен, что имеет смысл большую часть времени, учитывая, как часто происходит теневое копирование и как часто используются короткие идентификаторы. Например, вы можете определить x
на первой линии вашей программы, а затем 200 строк match ... with | (x,y) -> x + y
в этом случае вы почти наверняка хотите x
быть новым идентификатором.
Если вы хотите использовать произвольные функции, просто используйте параметризованный активный шаблон:
let (|Id|_|) f x = f x
match seq [] with
| Id (Seq.tryPick Some) _ -> ...
РЕДАКТИРОВАТЬ
Посмотрите Разрешение имени для Образцов в спецификации для деталей относительно разрешения имени. Ключ заключается в том, что существует логическая таблица PatItems, которая отличается от таблицы ExprItems, которая используется для имен в выражениях. В конкретном случае, который вы добавили к редактированию в вашем вопросе, последнее определение X
выигрывает, так что в этом случае он рассматривается как активный паттерн (эффективно X
появляется в шаблоне).
В дополнение к проблемам со столкновением имен / теневым копированием, я подозреваю, что есть также способы, которые позволили бы более широкому диапазону выражений в образцах привести к неоднозначным анализам, хотя я не могу думать ни о чем из рук в руки.
Один из способов ответить на этот вопрос - сказать, что активные шаблоны (как особая синтаксическая категория) позволяют использовать одно и то же имя для конструирования значения в выражении и деконструкции его в шаблоне.
Например, предположим, что мы используем тип Info
представлять некоторую информацию:
type Info = I of string * int
Вы можете написать две функции для создания и уничтожения значений этого типа:
val constructInfo : string * int -> Info
val destructInfo : Info -> string * int
В этом случае реализовать две функции тривиально, но интересным моментом является то, что сигнатуры их типов являются двойственными. Конструкция принимает значения и создает наш (абстрактный) тип, а разрушение принимает тип и возвращает отдельные значения.
Используя активные шаблоны, мы можем использовать то же имя, Info
для обеих целей. Это делает его совместимым с остальной частью языка - например, x::xs
а также (a, b)
и конструкторы и шаблоны. Библиотека F# делает то же самое для цитат F# (т.е. Lambda
это шаблон и конструктор для типа Expr
,
Таким образом, вместо написания функций конструкции и уничтожения, мы можем определить функцию и шаблон:
let Info (a, b) = I (a, b)
let (|Info|) (I (a, b)) = (a, b)
В результате тот же синтаксис, Info(a, b)
, может появляться как шаблон и выражение.