F #: как элегантно выбрать и сгруппировать различимые союзы? - PullRequest
15 голосов
/ 29 июля 2010

Скажем, у меня есть список фигур:

type shape = 
| Circle of float
| Rectangle of float * float

let a = [ Circle 5.0; Rectangle (4.0, 6.0)]

Как я могу проверить, например, существует ли круг в?Я мог бы создать функцию для каждой фигуры

let isCircle s = 
    match s with
    | Circle -> true
    | _ -> false
List.exists isCircle a

, но я чувствую, что в F # должен быть более элегантный способ, кроме необходимости определять такую ​​функцию для каждого типа фигуры.Есть ли?

С этим связан вопрос, как сгруппировать список фигур на основе типов фигур:

a |> seq.groupBy( <shapetype? >)

Ответы [ 5 ]

16 голосов
/ 29 июля 2010

Если вас интересуют разные категории фигур, то имеет смысл определить другой тип, который точно их фиксирует:

type shapeCategory = Circular | Rectangular

let categorize = function
    | Circle _ -> Circular
    | Rectangle _ -> Rectangular

List.exists ((=) Circular) (List.map categorize a)

a |> Seq.groupBy(categorize)

Редактировать - как предложено Брайаном, вы можете альтернативно использовать активные шаблоны вместо нового типа. Он работает аналогично для ваших примеров, но лучше распространяется на более сложные шаблоны, в то время как описанный выше подход может быть лучше, если ваш код часто работает с категориями, и вам нужен хороший тип объединения для них вместо типа Choice .

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)
8 голосов
/ 29 июля 2010

Вы можете использовать библиотеку отражения F #, чтобы получить тег значения:

let getTag (a:'a) = 
  let (uc,_) = Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(a, typeof<'a>)
  uc.Name

a |> Seq.groupBy getTag
7 голосов
/ 29 июля 2010

Вы можете объединить отражение 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
3 голосов
/ 03 августа 2012

Я хочу добавить другое решение, которое работает с цитатами для каждого случая объединения, на основе одного предоставленного деско. Вот оно:

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 определено вне возвращенной анонимной функции.

0 голосов
/ 29 июля 2010

Более элегантное решение может быть следующим:

let shapeExistsInList shapeType list =
    List.exists (fun e -> e.GetType() = shapeType) list

let circleExists = shapeExistsInList ((Circle 2.0).GetType()) a

Однако я сам не очень доволен этим, поскольку вам нужно создать экземпляр различного объединения, чтобы он работал.

Группировка по типу формы может работать аналогичным образом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...