2010-11-13 21 views
15

F # mi sta dando qualche problema con le sue regole di inferenza di tipo. Sto scrivendo un semplice costruttore di calcoli ma non riesco a ottenere il mio tipo generico di vincoli di variabile.Come si traduce un vincolo di parametro generico `dove T: U` da C# a F #?


Il codice che vorrei si rappresenta come segue C#:

class FinallyBuilder<TZ> 
{ 
    readonly Action<TZ> finallyAction; 

    public FinallyBuilder(Action<TZ> finallyAction) 
    { 
     this.finallyAction = finallyAction; 
    } 

    public TB Bind<TA, TB>(TA x, Func<TA, TB> cont) where TA : TZ 
    {          //  ^^^^^^^^^^^^^ 
     try        // this is what gives me a headache 
     {         //  in the F# version 
      return cont(x); 
     } 
     finally 
     { 
      finallyAction(x); 
     } 
    } 
} 

La migliore (ma non la compilazione di codice) mi è venuta in mente per il F # versione finora è:

type FinallyBuilder<′z> (finallyAction : ′z -> unit) = 

    member this.Bind (x : ′a) (cont : ′a -> ′b) = 
     try  cont x 
     finally finallyAction (x :> ′z) // cast illegal due to missing constraint 

// Note: ' changed to ′ to avoid bad syntax highlighting here on SO. 

Unfortu nately, non ho idea di come tradurrei il vincolo di tipo where TA : TZ nel metodo Bind. Ho pensato che dovrebbe essere qualcosa come ′a when ′a :> ′z, ma il compilatore F # non mi piace da nessuna parte e finisco sempre con qualche variabile di tipo generico vincolata a un'altra.

Qualcuno potrebbe mostrarmi il codice F # corretto?


Background: Il mio obiettivo è quello di essere in grado di scrivere un F flusso di lavoro # personalizzato come questo:

let cleanup = new FinallyBuilder (fun x -> ...) 

cleanup { 
    let! x = ... // x and y will be passed to the above lambda function at 
    let! y = ... // the end of this block; x and y can have different types! 
} 

risposta

8

Non credo sia possibile scrivere un vincolo come questo in F # (anche se non sono esattamente sicuro del perché). In ogni caso, syntacticalaly, che ci si vuole scrivere qualcosa del genere (come suggerisce Brian):

type FinallyBuilder<'T> (finallyAction : 'T -> unit) = 
    member this.Bind<'A, 'B when 'A :> 'T>(x : 'A) (cont : 'A -> 'B) = //' 
    try cont x 
    finally finallyAction (x :> 'T) 

Purtroppo, questo dà il seguente errore:

error FS0698: Invalid constraint: the type used for the constraint is sealed, which means the constraint could only be satisfied by at most one solution

Questo sembra essere lo stesso caso come il uno discusso in this mailing list. Dove Don Syme dice quanto segue:

This is a restriction imposed to make F# type inference tractable. In particular, the type on the right of a subtype constraint must be nominal. Note constraints of the form 'A :> 'B are always eagerly solved to 'A = 'B, as specified in section 14.6 of the F# specification.

Si può sempre risolvere questo problema utilizzando obj in funzione passata al costruttore.
EDIT: anche quando si utilizza obj, i valori legati usando let! avrà tipi più specifici (quando si chiama finallyAction, F # lancerà automaticamente il valore di alcuni parametri di tipo a obj):

type FinallyBuilder(finallyAction : obj -> unit) = 
    member x.Bind(v, f) = 
    try f v 
    finally finallyAction v 
    member x.Return(v) = v 

let cleanup = FinallyBuilder(printfn "%A") 

let res = 
    cleanup { let! a = new System.Random() 
      let! b = "hello" 
      return 3 } 
+1

OK, sono abbastanza convinto ora che non esiste una soluzione chiara per quello che mi piacerebbe fare. Specificare 'obj' per' finallyAction' ha il brutto effetto collaterale di ridurre tutti i miei valori personalizzati ('let!') Nel flusso di lavoro personalizzato per digitare 'obj', il che significa che non posso davvero fare un buon lavoro con loro più a lungo. Dovrà pensare ancora un po 'su come implementare quel costruttore in modo diverso. Ma spero che risolveranno tutto ciò in una versione futura del linguaggio F # ... – stakx

+1

@stakx: Anche se usi 'obj', il tipo di valori associati usando' let! 'Dovrebbe essere il tipo effettivo (più specifico) . Ho modificato la risposta includendo un esempio che dimostra ciò (l'ho finalmente testato anche io :-)). –

+0

sei un mago! :) Immagino che tutte quelle annotazioni di tipo siano d'intralcio, quindi. Grazie! – stakx

3

Sarà qualcosa di simile

...Bind<'A when 'A :> 'Z>... 

ma lasciatemi codificarlo per garantire che sia esattamente giusto ...

Ah, sembra s come sarebbe questo:

type FinallyBuilder<'z> (finallyAction : 'z -> unit) = 
    member this.Bind<'a, 'b when 'a :> 'z> (x : 'a, cont : 'a -> 'b) : 'b = 
     try  cont x 
     finally finallyAction x //(x :> 'z)// illegal 

salvo che

http://cs.hubfs.net/forums/thread/10527.aspx

sottolinea che F # non fa vincoli della forma "T1:> T2" in cui entrambi sono variabili di tipo (si presuppone T1 = T2). Tuttavia questo potrebbe essere ok per il tuo caso, che cosa avevi esattamente intenzione di utilizzare come istanziazioni concrete di Z? C'è probabilmente una soluzione semplice o un codice meno generico che soddisferà lo scenario. Per esempio, mi chiedo se questo funziona:

type FinallyBuilder<'z> (finallyAction : 'z -> unit) = 
    member this.Bind<'b> (x : 'z, cont : 'z -> 'b) : 'b = //' 
     try  cont x 
     finally finallyAction x 

Sembra:

type FinallyBuilder<'z> (finallyAction : 'z -> unit) = 
    member this.Bind<'b> (x : 'z, cont : 'z -> 'b) : 'b = // ' 
     try  cont x 
     finally finallyAction x 
    member this.Zero() =() 

[<AbstractClass>] 
type Animal() = 
    abstract Speak : unit -> unit 

let cleanup = FinallyBuilder (fun (a:Animal) -> a.Speak()) 

type Dog() = 
    inherit Animal() 
    override this.Speak() = printfn "woof" 

type Cat() = 
    inherit Animal() 
    override this.Speak() = printfn "meow" 

cleanup { 
    let! d = new Dog() 
    let! c = new Cat() 
    printfn "done" 
} 
// prints done meow woof 

Oh, capisco, ma d e c ora hanno tipo Animal. Hm, fammi vedere se c'è qualche intelligenza rimanendo in me ...

Beh, ovviamente si può fare

type FinallyBuilder<'z> (finallyAction : 'z -> unit) = 
    member this.Bind<'a,'b> (x : 'a, cont : 'a -> 'b) : 'b = // ' 
     try  cont x 
     finally finallyAction (x |> box |> unbox) 
    member this.Zero() =() 

che butta via la sicurezza di tipo (sarà un'eccezione getto in fase di esecuzione, se la cosa è non finalmente applicabile).

o si può fare tipo-specifici costruttori:

type FinallyBuilderAnimal (finallyAction : Animal -> unit) = 
    member this.Bind<'a,'b when 'a:>Animal>(x : 'a, cont : 'a -> 'b) : 'b = //' 
     try  cont x 
     finally finallyAction x 
    member this.Zero() =() 

let cleanup = FinallyBuilderAnimal (fun a -> a.Speak()) 

ma penso che io sono fuori altre idee intelligenti.

+0

_ "Questo costrutto fa sì che il codice sia meno generico di quanto indicato dalle annotazioni di tipo La variabile di tipo ''a' è stata vincolata per essere di tipo'' z'. "_ :-( – stakx

+0

Grazie per la tua risposta. aggiustando qualche tipo di variabile ad un tipo concreto. Sono solo un po 'deluso nel constatare che F #, che ha tutta questa meravigliosa inferenza di tipo, non può fare qualcosa che C# ca Fai facilmente ... è stato inaspettato. – stakx

+0

_ "Ma penso di non avere altre idee intelligenti." _ Sono comunque grato per i tuoi sforzi. Devi essere stato incuriosito da questo! ;-) Ho giocato con questo tutto il giorno e non ho trovato una soluzione piacevole, quindi vedere gli altri lottare con questo almeno mi rassicura che non mi sono perso qualcosa di molto semplice. – stakx

Problemi correlati