visual temperatura studio grados farenheit fahrenheit escala convertir conversor conversion centígrados centigrados celsius c# .net f# functional-programming

temperatura - ¿Cuál es la forma más sencilla de acceder a los datos de un tipo de unión discriminado por F#en C#?



grados celsius a fahrenheit c# (6)

Estoy usando los siguientes métodos para interoperar uniones de la biblioteca F # al host C #. Esto puede agregar algo de tiempo de ejecución debido al uso de la reflexión y debe comprobarse, probablemente mediante pruebas unitarias, para manejar los tipos genéricos correctos para cada caso de unión.

  1. En el lado F #

type Command = | First of FirstCommand | Second of SecondCommand * int module Extentions = let private getFromUnionObj value = match value.GetType() with | x when FSharpType.IsUnion x -> let (_, objects) = FSharpValue.GetUnionFields(value, x) objects | _ -> failwithf "Can''t parse union" let getFromUnion<''r> value = let x = value |> getFromUnionObj (x.[0] :?> ''r) let getFromUnion2<''r1,''r2> value = let x = value |> getFromUnionObj (x.[0] :?> ''r1, x.[1] :? ''r2)

  1. En el lado C #

public static void Handle(Command command) { switch (command) { case var c when c.IsFirstCommand: var data = Extentions.getFromUnion<FirstCommand>(change); // Handler for case break; case var c when c.IsSecondCommand: var data2 = Extentions.getFromUnion2<SecondCommand, int>(change); // Handler for case break; } }

Estoy tratando de entender qué tan bien pueden jugar juntos C # y F #. Tomé un código del blog F # for Fun & Profit que realiza una validación básica y devuelve un tipo de sindicato discriminado:

type Result<''TSuccess,''TFailure> = | Success of ''TSuccess | Failure of ''TFailure type Request = {name:string; email:string} let TestValidate input = if input.name = "" then Failure "Name must not be blank" else Success input

Al tratar de consumir esto en C #; la única manera que puedo encontrar para acceder a los valores contra Éxito y Fracaso (el fracaso es una cadena, el éxito es la solicitud de nuevo) es con grandes conversos desagradables (que es mucho escribir y requiere escribir los tipos reales que yo esperaría que fueran inferido o disponible en los metadatos):

var req = new DannyTest.Request("Danny", "fsfs"); var res = FSharpLib.DannyTest.TestValidate(req); if (res.IsSuccess) { Console.WriteLine("Success"); var result = ((DannyTest.Result<DannyTest.Request, string>.Success)res).Item; // Result is the Request (as returned for Success) Console.WriteLine(result.email); Console.WriteLine(result.name); } if (res.IsFailure) { Console.WriteLine("Failure"); var result = ((DannyTest.Result<DannyTest.Request, string>.Failure)res).Item; // Result is a string (as returned for Failure) Console.WriteLine(result); }

¿Hay una mejor manera de hacer esto? Incluso si tengo que lanzar manualmente (con la posibilidad de un error de tiempo de ejecución), espero al menos acortar el acceso a los tipos ( DannyTest.Result<DannyTest.Request, string>.Failure ). ¿Hay alguna manera mejor?


Mauricio Scheffer hizo algunas publicaciones excelentes para interoperabilidad de C # / F #, y uso de técnicas, con y sin las bibliotecas centrales de F # (o bibliotecas de Fsharpx), de manera que se puedan usar los conceptos (simplificados en F #) en DO#.

http://bugsquash.blogspot.co.uk/2012/03/algebraic-data-type-interop-f-c.html

http://bugsquash.blogspot.co.uk/2012/01/encoding-algebraic-data-types-in-c.html

También podría ser útil: ¿Cómo puedo duplicar el tipo de unión discriminado de F # en C #?


Probablemente, una de las formas más simples de lograr esto es creando un conjunto de métodos de extensión:

public static Result<Request, string>.Success AsSuccess(this Result<Request, string> res) { return (Result<Request, string>.Success)res; } // And then use it var successData = res.AsSuccess().Item;

Este artículo contiene una buena visión. Citar:

La ventaja de este enfoque es doble:

  • Elimina la necesidad de nombrar explícitamente los tipos en el código y, por lo tanto, recupera las ventajas de la inferencia de tipos;
  • Ahora puedo usar . en cualquiera de los valores y deje que Intellisense me ayude a encontrar el método apropiado para usar;

El único inconveniente aquí es que la interfaz modificada requeriría refactorizar los métodos de extensión.

Si hay demasiadas clases de este tipo en sus proyectos, considere usar herramientas como ReSharper, ya que no parece muy difícil configurar una generación de código para esto.


Trabajar con sindicatos discriminados nunca será tan sencillo en un lenguaje que no admita la coincidencia de patrones. Sin embargo, su tipo de Result<''TSuccess, ''TFailure> es lo suficientemente simple como para que exista una buena forma de usarlo desde C # (si el tipo fuera algo más complicado, como un árbol de expresiones, entonces probablemente sugeriría usar el Visitante modelo).

Otros ya mencionaron algunas opciones: cómo acceder directamente a los valores y cómo definir el método de Match (como se describe en la publicación del blog de Mauricio). Mi método favorito para las DU simples es definir los métodos TryGetXyz que siguen el mismo estilo de Int32.TryParse . Esto también garantiza que los desarrolladores de C # estarán familiarizados con el patrón. La definición de F # se ve así:

open System.Runtime.InteropServices type Result<''TSuccess,''TFailure> = | Success of ''TSuccess | Failure of ''TFailure type Result<''TSuccess, ''TFailure> with member x.TryGetSuccess([<Out>] success:byref<''TSuccess>) = match x with | Success value -> success <- value; true | _ -> false member x.TryGetFailure([<Out>] failure:byref<''TFailure>) = match x with | Failure value -> failure <- value; true | _ -> false

Esto simplemente agrega las extensiones TryGetSuccess y TryGetFailure que devuelven true cuando el valor coincide con los parámetros de caso y retorno (todos) del caso de unión discriminada a través de parámetros de out . El uso de C # es bastante sencillo para cualquiera que haya usado TryParse :

int succ; string fail; if (res.TryGetSuccess(out succ)) { Console.WriteLine("Success: {0}", succ); } else if (res.TryGetFailure(out fail)) { Console.WriteLine("Failuere: {0}", fail); }

Creo que la familiaridad de este patrón es el beneficio más importante. Cuando usa F # y expone su tipo a los desarrolladores de C #, debe exponerlos de la manera más directa (los usuarios de C # no deben pensar que los tipos definidos en F # no son estándar de ninguna manera).

Además, esto le brinda garantías razonables (cuando se usa correctamente) de que solo tendrá acceso a los valores que realmente están disponibles cuando el DU coincide con un caso específico.


Tuve este mismo problema con el tipo de resultado. ResultInterop<''TSuccess, ''TFailure> un nuevo tipo de ResultInterop<''TSuccess, ''TFailure> y un método auxiliar para hidratar el tipo

type ResultInterop<''TSuccess, ''TFailure> = { IsSuccess : bool Success : ''TSuccess Failure : ''TFailure } let toResultInterop result = match result with | Success s -> { IsSuccess=true; Success=s; Failure=Unchecked.defaultof<_> } | Failure f -> { IsSuccess=false; Success=Unchecked.defaultof<_>; Failure=f }

Ahora tengo la opción de canalizar a través toResultInterop en el límite F # o hacerlo dentro del código C #.

En el límite F #

module MyFSharpModule = let validate request = if request.isValid then Success "Woot" else Failure "request not valid" let handleUpdateRequest request = request |> validate |> toResultInterop

public string Get(Request request) { var result = MyFSharpModule.handleUpdateRequest(request); if (result.IsSuccess) return result.Success; else throw new Exception(result.Failure); }

Después de la interoperabilidad en Csharp

module MyFSharpModule = let validate request = if request.isValid then Success "Woot" else Failure "request not valid" let handleUpdateRequest request = request |> validate

public string Get(Request request) { var response = MyFSharpModule.handleUpdateRequest(request); var result = Interop.toResultInterop(response); if (result.IsSuccess) return result.Success; else throw new Exception(result.Failure); }


Una forma realmente agradable de hacer esto con C # 7.0 es mediante la combinación de patrones de conmutación, es casi como la coincidencia de F #:

var result = someFSharpClass.SomeFSharpResultReturningMethod() switch (result) { case var checkResult when checkResult.IsOk: HandleOk(checkResult.ResultValue); break; case var checkResult when checkResult.IsError: HandleError(checkResult.ErrorValue); break; }

EDIT: C # 8.0 está a la vuelta de la esquina y está trayendo expresiones de conmutación, así que aunque no lo he probado, aún espero que podamos hacer algo como esto:

var returnValue = result switch { var checkResult when checkResult.IsOk: => HandleOk(checkResult.ResultValue), var checkResult when checkResult.IsError => HandleError(checkResult.ErrorValue), _ => throw new UnknownResultException() };

Consulte https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/ para obtener más información.