F #: как изящно выбирать и группировать дискриминационные союзы?
Скажем, у меня есть список фигур:
type shape =
| Circle of float
| Rectangle of float * float
let a = [ Circle 5.0; Rectangle (4.0, 6.0)]
Как я могу затем проверить, например. Круг существует в a? Я мог бы создать функцию для каждой фигуры
let isCircle s =
match s with
| Circle -> true
| _ -> false
List.exists isCircle a
но я считаю, что в F # должен быть более элегантный способ, кроме необходимости определять такую функцию для каждого типа фигуры. Есть?
Связанный вопрос заключается в том, как сгруппировать список фигур на основе типов фигур:
a |> seq.groupBy( <shapetype? >)
Ответы
Ответ 1
вы можете комбинировать отражение F # с котировками для получения общего решения
type Shape =
| Circle of float
| Rectangle of float * float
let isUnionCase (c : Expr<_ -> 'T>) =
match c with
| Lambdas (_, NewUnionCase(uci, _)) ->
let tagReader = Microsoft.FSharp.Reflection.FSharpValue.PreComputeUnionTagReader(uci.DeclaringType)
fun (v : 'T) -> (tagReader v) = uci.Tag
| _ -> failwith "Invalid expression"
let a =
[ Circle 5.0; Rectangle (4.0, 6.0)]
|> List.filter (isUnionCase <@ Rectangle @>)
printf "%A" a
Ответ 2
Если вас интересуют разные категории фигур, тогда имеет смысл определить другой тип, который их точно фиксирует:
type shapeCategory = Circular | Rectangular
let categorize = function
| Circle _ -> Circular
| Rectangle _ -> Rectangular
List.exists ((=) Circular) (List.map categorize a)
a |> Seq.groupBy(categorize)
Изменить - как предложил Брайан, вы можете использовать активные шаблоны вместо нового типа. Это очень похоже на ваши примеры, но будет лучше распространяться на более сложные шаблоны, в то время как вышеприведенный подход может быть лучше, если вы часто используете код с категориями, и вы хотите использовать для них симпатичный тип объединения, а не тип выбора.
let (|Circular|Rectangular|) = function
| Circle _ -> Circular
| Rectangle _ -> Rectangular
List.exists (function Circular -> true | _ -> false) a
let categorize : shape -> Choice<unit, unit> = (|Circular|Rectangular|)
a |> Seq.groupBy(categorize)
Ответ 3
Вы можете использовать библиотеку отражения F #, чтобы получить тег значения:
let getTag (a:'a) =
let (uc,_) = Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(a, typeof<'a>)
uc.Name
a |> Seq.groupBy getTag
Ответ 4
Я хочу добавить еще одно решение, которое работает с котировками для каждого случая объединения, на основе предоставленного одного desco. Вот оно:
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Reflection
let rec isUnionCase = function
| Lambda (_, expr) | Let (_, _, expr) -> isUnionCase expr
| NewTuple exprs ->
let iucs = List.map isUnionCase exprs
fun value -> List.exists ((|>) value) iucs
| NewUnionCase (uci, _) ->
let utr = FSharpValue.PreComputeUnionTagReader uci.DeclaringType
box >> utr >> (=) uci.Tag
| _ -> failwith "Expression is no union case."
Определенный таким образом isUnionCase работает, как показал desco, но даже в случаях объединения, которые пусты или имеют более одного значения. Вы также можете ввести кортеж из разделенных запятыми случаев объединения. Рассмотрим это:
type SomeType =
| SomeCase1
| SomeCase2 of int
| SomeCase3 of int * int
| SomeCase4 of int * int * int
| SomeCase5 of int * int * int * int
let list =
[
SomeCase1
SomeCase2 1
SomeCase3 (2, 3)
SomeCase4 (4, 5, 6)
SomeCase5 (7, 8, 9, 10)
]
list
|> List.filter (isUnionCase <@ SomeCase4 @>)
|> printfn "Matching SomeCase4: %A"
list
|> List.filter (isUnionCase <@ SomeCase3, SomeCase4 @>)
|> printfn "Matching SomeCase3 & SomeCase4: %A"
Первый isUnionCase я предоставил только для проверки одного случая. Позднее я добавил проверку выражения для NewTuple и подумал, что вам это может понравиться. Просто убедитесь, что если вы измените код, прекомпьютеры все еще работают, поэтому iucs
определяется вне возвращаемой анонимной функции.
Ответ 5
Более элегантным решением может быть следующее:
let shapeExistsInList shapeType list =
List.exists (fun e -> e.GetType() = shapeType) list
let circleExists = shapeExistsInList ((Circle 2.0).GetType()) a
Однако я не очень доволен этим, потому что вам нужно создать экземпляр дискриминационного объединения, чтобы он работал.
Группировка по типу формы может работать аналогичным образом.