2010-10-11 12 views
7

Problema SommarioCome convertire implicitamente in tipi di super comuni in F # pattern matches?

Nel momento in cui utilizza f # devo costringere in modo esplicito un valore per il tipo di genitore di questo tipo al fine di ottenere di pattern corrispondenti espressioni di digitare controlla correttamente. Preferirei un modo più ordinato di fare.

Esempio

Supponiamo che io sono un po 'di classe gerarchia:

type Foo() = 
    abstract member Value : unit -> string 

type A (i:int) = 
    inherit Foo() 
     override this.Value() = i.ToString() 

type B (s:string) = 
    inherit Foo() 
     override this.Value() = s 

Idealmente, e in alcuni linguaggi di programmazione in Normalmente, vorrei scrivere l'equivalente di quanto segue:

let bar (i:int) : Foo = 
    match i with 
     | 1 -> B "one" 
     | _ -> A i 

Tuttavia non riesce a digitare correttamente il check, dandomi l'errore, "Questa espressione avrebbe dovuto avere tipo Foo ma lei e ha tipo B ". Non capisco perché il compilatore non abbia abbastanza informazioni per dedurre un super tipo comune per l'espressione della corrispondenza e quindi verificare che il super tipo comune sia 'Foo'.

Allo stato attuale sono costretto a fornire una coercizione esplicita per tutti i casi nella partita modello:

let bar2 (i:int) : Foo = 
    match i with 
     | 1 -> (B "one") :> Foo 
     | _ -> (A i) :> Foo 

vorrei evitare questo.

Ulteriori avvertenze

  • L'intuizione suggerisce che questo è il risultato di un problema più generale. Avrei pensato però che qualcosa di così comune come la corrispondenza dei pattern, o se le istruzioni che esibiscono la stessa proprietà, avrebbero una regola di controllo del tipo per tenere conto dei super-tipi comuni.
  • Prima che qualcuno mi suggerisca - Apprezzo che se A o B fossero espressioni oggetto funzionerebbe, ma il mio vero esempio è la creazione di istanze di classi C# in cui sono classi normali.
  • C'è un modo per me di dichiarare le funzioni per convertire implicitamente i tipi, come ad esempio scala ha, quindi posso applicare le conversioni automatiche per il modulo in cui sto facendo questa generazione?

Grazie per qualsiasi aiuto in merito.

risposta

7

userei upcast, alla

[<AbstractClass>] 
type Foo() = 
    abstract member Value : unit -> string 

type A (i:int) = 
    inherit Foo() 
    override this.Value() = i.ToString() 

type B (s) = 
    inherit Foo() 
    override this.Value() = s 

let bar2 i : Foo = 
    match i with 
    | 1 -> upcast B "one" 
    | _ -> upcast A i 

Hai ancora aggiungere a ogni ramo, ma questo è spesso preferibile alla fusione al tipo, dal momento che spesso il typename è come una lunghezza di 20 o 30 caratteri (MyNamespace.ThisThingy), mentre upcast ha solo 6 caratteri.

Ma, in breve, le regole del linguaggio non consentono nient'altro, i tipi di tutti i rami devono essere uguali.

+2

Il motivo per cui il correttore di tipi non esegue alcuna shenanigans con tipi derivati ​​è che in primo luogo renderebbe più complicata la fase di controllo del tipo e in secondo luogo consentirebbe di introdurre bug sottili. Ad esempio, considerare quanto segue: corrispondenza i con | 1 -> B ("uno") | 2 -> "stuff" Bene, sia System.String che B condividono il tipo di base comune System.Object, quindi il risultato di tale espressione di corrispondenza è di tipo obj. Il 99,9% non è quello che volevi. –

+1

È vero, anche se nell'istanza specifica che l'OP sta chiedendo, ha già dichiarato la funzione return type come ': Foo', quindi penso che forse non sarebbe del tutto irragionevole per l'inferitore utilizzarlo come un più grande- legato o qualcosa. Non ho pensato a tutte le implicazioni di ciò, però. – Brian

+1

Penso che 'x:> _' sia migliore di' upcast x', ma forse sono solo io. I rami della partita non si allineano, però. – kvb

5

Ho visto questa domanda un paio di volte prima, ma mi sono appena reso conto che c'è un modo abbastanza interessante per risolvere il problema (senza effetti negativi significativi come il grande sovraccarico di runtime).

È possibile utilizzare un'espressione di calcolo molto semplice che dispone solo di membro Return. Il builder avrà un parametro di tipo e Return aspetterà valori di questo tipo. Il trucco è che F # fa inserisce automaticamente i richiami quando si chiama un membro. Ecco la dichiarazione:

type ExprBuilder<'T>() = 
    member x.Return(v:'T) = v 

let expr<'T> = ExprBuilder<'T>() 

Per scrivere un semplice pattern matching che restituisce nulla come obj, è ora possibile scrivere:

let foo a = expr<obj> { 
    match a with 
    | 0 -> return System.Random() 
    | _ -> return "Hello" } 

Hai ancora essere espliciti circa il tipo di ritorno (al momento della creazione espressione di calcolo), ma trovo la sintassi abbastanza accurata (ma è sicuramente un uso complicato che potrebbe confondere le persone che lo vedranno per la prima volta).

+0

Questa è una soluzione interessante, ma devo ancora avere la sintassi su ogni riga della corrispondenza del modello. Ancora - questa è sicuramente la soluzione migliore che abbia mai visto! –

+2

-1, a mio parere, questo è "troppo intelligente". – Brian

+0

@Brian: lo prendo come un complimento :-), ma hai ragione, probabilmente è troppo complicato (e l'ho scritto nell'ultima frase) anche se dipende sempre dal tipo di progetto - È un po ' Codice impianto idraulico C#/F # o parte centrale di un sistema F # complesso? In alcuni casi, dovrebbero essere consentite soluzioni ingannevoli :-). E dopotutto, i flussi di lavoro asincroni sono troppo complicati (in un certo senso). –

Problemi correlati