Кажется, что код F # часто сопоставляется с типами. Конечно
match opt with
| Some val -> Something(val)
| None -> Different()
кажется обычным.
Но с точки зрения ООП это выглядит очень похоже на поток управления, основанный на проверке типа во время выполнения, которая обычно вызывает недовольство. Чтобы объяснить это, в ООП вы, вероятно, предпочтете использовать перегрузку:
type T =
abstract member Route : unit -> unit
type Foo() =
interface T with
member this.Route() = printfn "Go left"
type Bar() =
interface T with
member this.Route() = printfn "Go right"
Это, конечно, больше кода. OTOH, мне кажется, у OOP-y есть структурные преимущества:
- расширение к новой форме
T
легко; - Мне не нужно беспокоиться об обнаружении дублирования потока управления выбором маршрута; и
- Выбор маршрута является неизменным в том смысле, что, как только у меня есть
Foo
в руках, мне не нужно беспокоиться оBar.Route()
реализации
Есть ли какие-то преимущества в сопоставлении с образцом для типов, которые я не вижу? Это считается идиоматическим или это способность, которая обычно не используется?
object-oriented
functional-programming
f#
pattern-matching
Ларри Обриен
источник
источник
But from an OOP perspective, that looks an awful lot like control-flow based on a runtime type check, which would typically be frowned on.
звучит слишком догматично. Иногда вы хотите отделить свои операции от своей иерархии: возможно, 1) вы не можете добавить операцию в иерархию, потому что у вас нет этой иерархии; 2) классы, которые вы хотите иметь, не соответствуют вашей иерархии; 3) вы можете добавить опцию в вашу иерархию, но не хотите, чтобы вы не хотели загромождать API своей иерархии кучей дерьма, которое не использует большинство клиентов.Some
аNone
не типы. Оба они являются конструкторами, чьи типыforall a. a -> option a
иforall a. option a
(извините, не уверен, какой синтаксис для аннотаций типов в F #).Ответы:
Вы правы в том, что иерархии классов ООП очень тесно связаны с различимыми объединениями в F #, и что сопоставление с образцом очень тесно связано с тестами динамических типов. Фактически, именно так F # компилирует разрозненные объединения в .NET!
Что касается расширяемости, есть две стороны проблемы:
Тем не менее, F # выдаст вам предупреждение, когда вы пропустите случай при сопоставлении с образцом, поэтому добавление новых случаев объединения на самом деле не так уж и плохо.
Относительно поиска дубликатов в выборе корня - F # выдаст вам предупреждение, если у вас есть совпадение, которое дублируется, например:
Тот факт, что «выбор маршрута является неизменным» также может быть проблематичным. Например, если вы хотите поделиться реализацией функции между
Foo
иBar
случаями, но сделать что-то еще дляZoo
случая, вы можете легко это кодировать, используя сопоставление с образцом:В целом, FP больше ориентирован на разработку типов, а затем на добавление функций. Таким образом, он действительно выигрывает от того, что вы можете разместить ваши типы (модель домена) в несколько строк в одном файле, а затем легко добавить функции, которые работают в модели домена.
Два подхода - ОО и ФП достаточно дополняют друг друга, и оба имеют свои преимущества и недостатки. Хитрость (с точки зрения ОО) заключается в том, что F # обычно использует стиль FP по умолчанию. Но если действительно нужно добавить новые подклассы, вы всегда можете использовать интерфейсы. Но в большинстве систем вам в равной степени необходимо добавлять типы и функции, поэтому выбор действительно не имеет большого значения - и использование различаемых объединений в F # более приятно.
Я бы порекомендовал эту замечательную серию блогов для получения дополнительной информации.
источник
Вы правильно заметили, что сопоставление с образцом (по существу, оператор переключения с наддувом) и динамическая диспетчеризация имеют сходство. Они также сосуществуют на некоторых языках с очень приятным результатом. Тем не менее, есть небольшие различия.
Я мог бы использовать систему типов для определения типа, который может иметь только фиксированное количество подтипов:
Никогда не будет другого подтипа
Bool
илиOption
, поэтому подклассы не кажутся полезными (некоторые языки, такие как Scala, имеют понятие подкласса, которое может с этим справиться - класс можно пометить как «конечный» вне текущей единицы компиляции, но подтипы может быть определено внутри этого модуля компиляции).Поскольку подтипы подобного типа
Option
теперь статически известны , компилятор может предупредить, если мы забудем обработать регистр в нашем сопоставлении с образцом. Это означает, что сопоставление с шаблоном больше похоже на специальное снижение, которое заставляет нас обрабатывать все варианты.Кроме того, динамическая диспетчеризация метода (которая требуется для ООП) также подразумевает проверку типа во время выполнения, но другого типа. Поэтому довольно не имеет значения, если мы делаем эту проверку типа явно через сопоставление с образцом или неявно через вызов метода.
источник
Сопоставление с шаблоном F # обычно выполняется с помощью различаемого объединения, а не классов (и, таким образом, технически вообще не является проверкой типа). Это позволяет компилятору выдавать предупреждение, когда вы пропустили случаи в сопоставлении с образцом.
Следует также отметить, что в функциональном стиле вы организуете вещи по функциональным возможностям, а не по данным, поэтому сопоставления с образцами позволяют объединять различные функциональные возможности в одном месте, а не разбросаны по классам. Это также имеет то преимущество, что вы можете видеть, как обрабатываются другие дела, рядом с тем местом, где вам нужно внести изменения.
Добавление новой опции выглядит следующим образом:
источник
Частично, вы видите это чаще в функциональном программировании, потому что вы используете типы для принятия решений чаще. Я понимаю, что вы, вероятно, просто выбирали примеры более или менее случайным образом, но ООП, эквивалентный вашему примеру сопоставления с образцом, будет чаще выглядеть так:
Другими словами, относительно редко можно использовать полиморфизм, чтобы избежать рутинных вещей, таких как нулевые проверки в ООП. Так же, как ОО-программист не создает нулевой объект в каждой маленькой ситуации, функциональный программист не всегда перегружает функцию, особенно если вы знаете, что ваш список шаблонов гарантированно будет исчерпывающим. Если вы используете систему типов в большем количестве ситуаций, вы увидите, что она используется не так, как вы привыкли.
С другой стороны , идиоматических функциональное программирование эквивалентно вашему примеру ООП, скорее всего , не использовать сопоставление с образцом, но будет иметь
fooRoute
иbarRoute
функции , которые будут получать передаваемые в качестве аргументов в коде вызова. Если бы кто-то использовал сопоставление с образцом в этой ситуации, это обычно считалось бы неправильным, точно так же, как кто-то, переключающий типы, считался бы неправильным в ООП.Итак, когда сопоставление с образцом считается хорошим функциональным программным кодом? Когда вы делаете больше, чем просто смотрите на типы, и при расширении требований не требуется добавлять больше случаев. Например,
Some val
он не просто проверяет,opt
имеет ли типSome
, он также привязываетсяval
к базовому типу для безопасного использования типа на другой стороне->
. Вы знаете, что, скорее всего, вам никогда не понадобится третий случай, так что это хорошее применение.Сопоставление с образцом может внешне напоминать объектно-ориентированный оператор switch, но происходит гораздо больше, особенно с более длинными или вложенными шаблонами. Убедитесь, что вы учитываете все, что он делает, прежде чем объявлять его эквивалентным некачественному ООП-коду. Часто он кратко обрабатывает ситуацию, которая не может быть четко представлена в иерархии наследования.
источник
Some
иNone
не являются типами, так что вы не соответствующий шаблон на типы. Вы шаблон матча на конструкторах этого же типа . Это не то же самое, что спрашивать об instanceof.