Доступ к конкретному случаю из 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#.

Другие вопросы по тегам