Доступ к конкретному случаю из F# DU
Предположим, у меня есть следующий DU:
type Something =
| A of int
| B of string * int
Теперь я использую его в такой функции:
let UseSomething = function
| A(i) -> DoSomethingWithA i
| B(s, i) -> DoSomethingWithB s i
Это работает, но мне пришлось деконструировать DU, чтобы передать его функциям DoSomethingWith*. Для меня естественно попытаться определить DoSomethingWithA как:
let DoSomethingWithA (a: Something.A) = ....
но компилятор жалуется, что тип A не определен.
Кажется, что в полном соответствии с философией F# нужно ограничить аргумент быть Something.A, а не просто каким-либо старым int, так что я просто поступаю неправильно?
3 ответа
Важно отметить, что A
а также B
являются конструкторами одного типа Something
, Таким образом, вы получите неисчерпаемое предупреждение о соответствии шаблонов, если вы попытаетесь использовать A
а также B
чехлы отдельно.
IMO, деконструкция всех случаев использования DU - это хорошая идея, так как она безопасна от типов и заставляет вас думать об обработке этих случаев, даже если вы этого не хотите. Проблема может возникнуть, если вам придется повторять декомпозицию DU таким же образом. В этом случае, определяя map
а также fold
функции на DU могут быть хорошей идеей:
let mapSomething fa fb = function
| A(i) -> fa i
| B(s, i) -> fb s i
Пожалуйста, обратитесь к отличной серии катаморфизма @Brian, чтобы узнать о сложении на DU.
Это также говорит, что ваш пример в порядке. Что вы действительно обрабатываете string
а также int
значения после деконструкции.
Вы можете использовать активные шаблоны, чтобы использовать два случая отдельно:
let (|ACase|) = function A i -> i | B _ -> failwith "Unexpected pattern B _"
let (|BCase|) = function B(s, i) -> (s, i) | A _ -> failwith "Unexpected pattern A _"
let doSomethingWithA (ACase i) = ....
но предполагаемый тип doSomethingWithA
все тот же, и вы получаете исключение при прохождении B _
к функции. Так что делать ИМО неправильно.
Другие ответы точны: в F# A
а также B
являются конструкторами, а не типами, и это традиционный подход, принятый строго типизированными функциональными языками, такими как Haskell или другими языками семейства ML. Однако есть и другие подходы - я считаю, что в Scala, например, A
а также B
будет на самом деле подклассами Something
, так что вы можете использовать эти более конкретные типы, когда это имеет смысл. Я не совсем уверен, какие компромиссы связаны с принятием решения о проектировании, но, вообще говоря, наследование делает вывод типов трудным / невозможным (и правда, что вывод стереотипов в Scala намного хуже, чем в языках Haskell или ML).
A
это не тип, это просто конструктор для Something
, Невозможно избежать сопоставления с образцом, что не обязательно плохо.
Тем не менее, F# предлагает то, что называется, например, активные шаблоны
let (|AA|) = function
| A i -> i
| B _ -> invalidArg "B" "B's not allowed!"
который вы можете использовать следующим образом:
let DoSomethingWithA (AA i) = i + 1
Но нет никакой реальной причины, почему вы хотели бы сделать это! Вы все еще делаете то же самое старое сопоставление с образцом под капотом, плюс вы рискуете ошибкой во время выполнения.
В любом случае, ваша реализация UseSomething
совершенно естественно для F#.