generics - objeto - Diferencia entre el tratamiento de interfaces genéricas ambiguas por F#entre VS 2012 y VS 2015 que conduce a errores de compilación en este último
interfaces genericas java (1)
Cambié el orden de las interfaces en el código C # a este y al siguiente código compilado como en VS 2013
public interface IInt : IPrime<int>, IFloat
{
int Salary { get; }
}
let g = fun (itm: Base) ->
match itm with
| :? IInt as i -> i.Value
| :? IFloat as i -> i.Value |> int
| _ -> failwith "error"
Sospecho que uno de los miembros del ''Valor'' está oculto (sombreado por la perspectiva de FSharp) por el otro, según el orden de las interfaces.
En su lugar, codifiqué su coincidencia de patrones como esta y validé que el orden de la interfaz no importaba, en este caso.
let g = fun (itm: Base) ->
match itm with
| :? IPrime<int> as i -> i.Value
| :? IPrime<float> as i -> i.Value |> int
| _ -> failwith "error"
Para mí, esto parece un cambio en los detalles de implementación. No estoy seguro si lo llamaría un error. La especificación F # sería la última palabra si esto es un error.
(Nota: pregunta actualizada con un ejemplo completo reproducible)
Después de migrar un proyecto F # de VS 2012 a VS 2015, recibo un error en ciertos usos de las interfaces. En particular, sucede cuando un tipo implementa dos interfaces genéricas. Sé que esto no está permitido en F # directamente, pero este tipo proviene de C #.
Para reproducir el problema:
1. definición de tipo en C #:
Pegue esto en alguna biblioteca de clases.
public interface Base { }
public interface IPrime<T> : Base
{
T Value { get; }
}
public interface IFloat : IPrime<double>
{
}
public interface IInt : IFloat, IPrime<int>
{
int Salary { get; }
}
public abstract class Prime<T> : IPrime<T>
{
public T Value { get; protected internal set; }
public static implicit operator T(Prime<T> value)
{
return value.Value;
}
}
public class FFloat : Prime<double>, IFloat
{
public FFloat(double value)
{
this.Value = value;
}
public double Salary { get; set; }
}
public class FInt : Prime<int>, IInt
{
public FInt(int value)
{
this.Value = value;
}
public int Salary { get; set; }
int IPrime<int>.Value { get { return this.Value; } }
double IPrime<double>.Value { get { return this.Value; } }
}
2. Uso de tipos en F #:
Uso en Visual Studio 2012, funcionando:
open SomeClassLib
[<EntryPoint>]
let main argv =
let i = new FInt(10)
let f = new FFloat(12.0)
let g = fun (itm: SomeClassLib.Base) ->
match itm with
| :? IInt as i -> i.Value
| :? IFloat as i -> i.Value |> int
| _ -> failwith "error"
Si abre la misma solución en Visual Studio 2015, obtendrá el error
error FS0001: No coincide el tipo. Esperando un
float -> float
pero dado unfloat -> int
. El tipo''float''
no coincide con el tipo''int''
Esto es, por supuesto, fácilmente corregido por un tipo de transmisión, pero luego, sorpresa sorpresa, ya no se cargará en Visual Studio 2012 (bueno, hay formas de hacerlo funcionar en ambos, y en este ejemplo es trivial).
3. Hallazgos
Si pasa el cursor sobre i.Value
obtiene:
Visual Studio 2012
| :? IInt as i -> i.Value // Value is int
| :? IFloat as i -> i.Value |> int // Value is float
Visual Studio 2015
| :? IInt as i -> i.Value // Value is float
| :? IFloat as i -> i.Value |> int // Value is float
Me pregunto de dónde viene esta diferencia.
Preguntas / observaciones
Me parece extraño que el compilador parece "elegir" que una asignación funciona, y otra no, esto invita a errores inadvertidos y, a veces difíciles de obtener. Y francamente, estoy un poco preocupado de que en los lugares donde la inferencia de tipos puede elegir entre int y float, ahora favorecerá float, donde fue int en el pasado.
La diferencia entre 2012 y 2015 parece ser que la primera toma la primera en los encuentros cuando sube la jerarquía, y la segunda parece tomar la última, pero no he podido confirmarlo sin ambigüedades.
¿Es esto un error o una mejora de una función existente? Me temo que tendré que volver a diseñar algo para eliminar la ambigüedad, a menos que alguien sepa de una manera simple de lidiar con esto (sucede en solo unos 50 lugares, puede arreglarse a mano, pero no tan bien) ?
Conclusión preliminar
Para mí es claro que el tipo original se puede considerar ambiguo y posiblemente deficiente, pero los lenguajes .NET lo admiten y también lo hace MSIL.
Sé que F # no es compatible con mezclar tipos genéricos en el mismo método o propiedad, que es una opción de idioma con la que puedo vivir, pero su tipo de inferencia toma una decisión que no puede predecirse, lo cual creo que es un error.
De cualquier manera, creo que esto debería ser un error, similar al que recibes cuando te diriges a miembros sobrecargados en F #, en cuyo caso el error es muy claro y enumera las opciones.