2010-11-13 17 views
15

F # me está dando algunos problemas con sus reglas de inferencia tipo. Estoy escribiendo un simple generador de cálculos pero no puedo obtener mis restricciones de tipo genérico correctas.¿Cómo puedo traducir una restricción de parámetro de tipo genérico `where T: U` de C# a F #?


El código que me gustaría que se ve de la siguiente manera en 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 mejor (pero no compilar código) me ha ocurrido con el F # la versión hasta ahora es:

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, no tengo ni idea de cómo traduciría la restricción de tipo where TA : TZ en el método Bind. Pensé que debería ser algo así como ′a when ′a :> ′z, pero al compilador de F # no le gusta esto en cualquier lugar y siempre termino con una variable de tipo genérico restringida a otra.

¿Podría alguien mostrarme el código F # correcto?


Antecedentes: Mi objetivo es ser capaz de escribir un flujo de trabajo # encargo F así:

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! 
} 

Respuesta

8

No creo que sea posible escribir una restricción como esta en F # (aunque no estoy exactamente seguro de por qué). De todos modos, syntacticalaly, que te gustaría escribir algo como esto (como sugiere 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) 

Desafortunadamente, esto da el siguiente error:

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

Este parece ser el mismo caso que el uno discutido en this mailing list. Donde Don Syme dice lo siguiente:

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.

Siempre se puede resolver esto mediante el uso obj en la función pasada a su constructor.
EDITAR: Incluso cuando se utiliza obj, los valores ligados usando let! tendrá tipos más específicos (al llamar finallyAction, F # lanzará automáticamente el valor de algún parámetro de tipo de 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, estoy bastante convencido de que no hay una solución clara para lo que me gustaría hacer. Especificar 'obj' para' finallyAction' tiene el desagradable efecto secundario de reducir todos mis valores de límite personalizado ('let!') En el flujo de trabajo personalizado para escribir 'obj', lo que significa que no puedo hacer un trabajo sensato con ellos. más. Tendrá que pensar un poco más sobre cómo implementar ese generador de forma diferente. Pero espero que lo solucionen en una versión futura del lenguaje F # ... – stakx

+1

@stakx: incluso si usa 'obj', el tipo de valores vinculados con' let! 'Debería ser el tipo real (más específico) . Edité la respuesta para incluir un ejemplo que demuestre esto (finalmente lo probé también :-)). –

+0

¡eres un mago! :) Supongo que todas esas anotaciones tipo se interpusieron en el camino, entonces. ¡Gracias! – stakx

3

Será algo así como

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

pero permítanme codificaré para asegurarse de que sea exactamente así ...

Ah, mira s que iba a ser la siguiente:

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 

excepto que

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

señala que F # no hace contraints de la forma "T1:> T2", donde ambos son variables de tipo (que asume T1 = T2). Sin embargo, esto podría estar bien para su caso, ¿qué planea usar exactamente como instanciaciones concretas de Z? Probablemente exista una solución simple o algún código menos genérico que satisfaga el escenario. Por ejemplo, me pregunto si esto funciona:

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

Parece que:

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, ya veo, pero d y ahora tienen c tipo Animal. Hm, a ver si hay alguna inteligencia que queda en mí ...

Bueno, es obvio que usted puede hacer

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() =() 

la que tira a la basura la seguridad de tipos (arrojará una excepción de difusión en tiempo de ejecución si la cosa es no finalmente seleccionable).

o se puede hacer de tipos específicos de constructores:

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

Pero creo que estoy fuera de otras ideas inteligentes.

+0

_ "Esta construir causa que el código sea menos genérico que lo indicado por las anotaciones de tipo. La variable de tipo ''a' se ha restringido a ser tipo''z'. "_ :-( – stakx

+0

Gracias por su respuesta. Tal vez pueda resolver mi problema arreglando algún tipo de variable a un tipo concreto. Estoy un poco decepcionado al descubrir que F #, que tiene toda esta maravillosa inferencia de tipo, no puede hacer algo que C# ca n hacerlo fácilmente ... eso fue inesperado. – stakx

+0

_ "Pero creo que no tengo otras ideas inteligentes". _ De todos modos, estoy agradecido por sus esfuerzos. ¡Debes de haber estado intrigado por esto! ;-) He jugado todo el día con esto y no he encontrado una solución agradable, por lo que ver a otros luchar con esto al menos me asegura que no me he perdido algo muy básico. – stakx

Cuestiones relacionadas