2010-07-29 4 views
15

Скажем, у меня есть список форм: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? >) 
+2

(немного OT) Это напоминает мне, что время [подсветка кода поддерживается для F #] (http://meta.stackexchange.com/questions/58934/hight-time-for-code-highlighting-f-snippets) (!) – Abel

+0

См. Http://meta.stackexchange.com/questions/981/syntax-highlighting-hints, на языке SO нет языковой подсветки. – Brian

ответ

7

вы можете объединить 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 
+0

Реализация isUnionCase намного выше моей головы, но выглядит очень умно. И использование этого по строкам я подозревал, что это может выглядеть. Благодаря! – Emile

+1

Я не могу найти правильное пространство имен. Для Expr требуется Microsoft.FSharp.Quotations, но я не могу найти Lambdas где-нибудь – Wouter

+0

'Lambda' (примечание, нет ** s **) и' NewUnionCase' можно найти в пространстве имен 'Microsoft.FSharp.Quotations.Patterns'. – Ruxo

0

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

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

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

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

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

+2

Это работает в этом случае, но не для более простых типов типа 'type T = A | B', где случаи * не * реализованы как разные типы. – Mau

+0

В стороне: та же проблема - создать экземпляр класса для проверки экземпляра класса - появляется все время. Было бы интересно увидеть некоторые общие решения проблемы. Возможно, стоит начать нить или вики. – TechNeilogy

+0

Не знал, что .. –

16

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

type shapeCategory = Circular | Rectangular 

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

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

a |> Seq.groupBy(categorize) 

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

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) 
+7

Альтернативно, активный шаблон. – Brian

+0

Не могли бы вы привести пример, как это будет выглядеть? – Emile

+1

Я добавил версию с Активными шаблонами и краткое сравнение. – RD1

8

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

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

a |> Seq.groupBy getTag 
+0

Очень приятное и универсальное решение для группировки. Благодаря! – Emile

3

Я хочу добавить еще решение, которое работает с котировками для каждого случая объединения, на основе одного предоставленного 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 определяется вне возвращаемой анонимной функции.

Смежные вопросы