Estoy intentando diseñar una biblioteca en F #. La biblioteca debe ser amigable para usar desde tanto F # como C#.El mejor enfoque para diseñar bibliotecas F # para utilizar desde F # y C#
Y aquí es donde estoy estancado un poco. Puedo hacerlo amigable con F #, o puedo hacerlo amigable con C#, pero el problema es cómo hacerlo amigable para ambos.
Aquí hay un ejemplo. Imagino que tengo la siguiente función en C#:
let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a
Esto es perfectamente utilizable a partir de F #:
let useComposeInFsharp() =
let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
composite "foo"
composite "bar"
En C#, la función compose
tiene la siguiente firma:
FSharpFunc<T, Unit> compose<T, TResult>(FSharpFunc<T, TResult> f, FSharpFunc<TResult, Unit> a);
Pero de Por supuesto que no quiero FSharpFunc
en la firma, lo que quiero es Func
y Action
en su lugar, así:
Action<T> compose2<T, TResult>(Func<T, TResult> f, Action<TResult> a);
Para lograr esto, puedo crear compose2
función como esta:
let compose2 (f: Func<'T, 'TResult>) (a : Action<'TResult>) =
new Action<'T>(f.Invoke >> a.Invoke)
Ahora bien, esto es perfectamente utilizable en C#:
void UseCompose2FromCs()
{
compose2((string s) => s.ToUpper(), Console.WriteLine);
}
Pero ahora hemos problema usando compose2
de F # ! Ahora tengo que envolver toda norma F # funs
en Func
y Action
, así:
let useCompose2InFsharp() =
let f = Func<_,_>(fun item -> item.ToString())
let a = Action<_>(fun item -> printfn "%A" item)
let composite2 = compose2 f a
composite2.Invoke "foo"
composite2.Invoke "bar"
La pregunta: ¿Cómo podemos lograr una experiencia de primera clase para la biblioteca escrita en C# para los dos F # y C# usuarios?
Hasta el momento, no podía llegar a nada mejor que estos dos enfoques:
- dos conjuntos separados: uno dirigido a los usuarios de F #, y la segunda a los usuarios de C#.
- Un conjunto pero diferentes espacios de nombres: uno para usuarios F # y el segundo para usuarios C#.
Para el primer enfoque, me gustaría hacer algo como esto:
Crear proyecto F #, lo llaman FooBarFs y compilarlo en FooBarFs.dll.
- Oriente la biblioteca exclusivamente a los usuarios de F #.
- Ocultar todo lo innecesario de los archivos .fsi.
Cree otro proyecto F #, llame si FooBarCs y compile en FooFar.dll
- Reutiliza el primer proyecto F # en el nivel de la fuente.
- Crea el archivo .fsi que oculta todo lo de ese proyecto.
- Crear un archivo .fsi que expone la biblioteca en C#, usando expresiones C# para nombres, espacios de nombres, etc.
- Cree envolturas que deleguen en la biblioteca central, realizando la conversión cuando sea necesario.
creo que el segundo enfoque con los espacios de nombres puede ser confuso para los usuarios, pero entonces usted tiene un ensamblaje.
La pregunta: ninguno de estos son ideales, tal vez me falta algún tipo de indicador del compilador/interruptor/attribte o algún tipo de truco y hay una mejor manera de hacer esto?
La pregunta: ¿Alguien más ha intentado lograr algo similar y, en caso afirmativo, cómo lo hizo?
EDITAR: para aclarar, la pregunta no es solo sobre funciones y delegados, sino sobre la experiencia general de un usuario de C# con una biblioteca F #. Esto incluye espacios de nombres, convenciones de nombres, expresiones idiomáticas y cosas por el estilo que son nativas de C#. Básicamente, un usuario de C# no debería poder detectar que la biblioteca fue creada en F #. Y viceversa, un usuario F # debería sentirse como tratar con una biblioteca C#.
EDIT 2:
puedo ver en las respuestas y comentarios tan lejos que mi pregunta carece de la profundidad necesaria, quizás debido sobre todo al uso de un único ejemplo en el que los problemas de interoperabilidad entre F # y C# surgen, la cuestión de las funciones es un valor. Creo que este es el ejemplo más obvio, así que este me llevó a usarlo para hacer la pregunta, pero por la misma razón daba la impresión de que esto es el único problema que me preocupa.
Permítanme proporcionar ejemplos más concretos. He leído el documento más excelente F# Component Design Guidelines (muchas gracias @gradbot por esto!). Las pautas en el documento, si se usan, hacen referencia a algunos de los problemas, pero no todos.
El documento está dividido en dos partes principales: 1) pautas para la orientación de usuarios F #; y 2) pautas para dirigidas a usuarios de C#. En ninguna parte incluso intenta pretender que es posible tener un enfoque uniforme , lo que hace eco exactamente a mi pregunta: podemos apuntar a F #, podemos apuntar a C#, pero ¿cuál es la solución práctica para apuntar a ambos?
Para recordar, el objetivo es tener una biblioteca de autor en F #, y que puede ser utilizado idiomáticamente de tanto F # y C# idiomas.
La palabra clave aquí es idiomatic. El problema no es la interoperabilidad general donde solo es posible usar bibliotecas en diferentes idiomas.
Ahora a los ejemplos, que tomo directamente de .
Módulos + funciones (F #) vs Espacios de nombres + + Tipos de funciones
F #: Foro de espacios de nombres o módulos de uso para contener sus tipos y módulos. El uso idiomático es colocar funciones en módulos, por ejemplo:
// library module Foo let bar() = ... let zoo() = ... // Use from F# open Foo bar() zoo()
C#: Realice espacios de nombres de uso, tipos y miembros como la estructura organizativa principal para sus componentes (a diferencia de los módulos), de vainilla .NET APIs
Esto es incompatible con el # directriz F, y el ejemplo necesitaría que ser re-escrita para adaptarse a los C# usuarios:
[<AbstractClass; Sealed>] type Foo = static member bar() = ... static member zoo() = ...
Al hacerlo, sin embargo, que rompen el uso idiomático de F #, porque ya no podemos usar
bar
yzoo
sin ponerle el prefijoFoo
.
uso de tuplas
F #: Hacer tuplas uso cuando sea apropiado para los valores de retorno.
C#: Evite usar tuplas como valores de retorno en las API .NET de vanilla.
asíncrono
F #: Hacer uso asíncrono para la programación asincrónica en F # límites de la API.
C#: exponga operaciones asíncronas utilizando ya sea el modelo de programación asíncrona .NET (BeginFoo, EndFoo), o como métodos que regresan tareas .NET (tarea), más que como F # asíncrono objetos.
El uso de
Option
F #: Considere el uso de valores de opciones para los tipos de retorno en lugar de elevar excepciones (para F # Código -frente).
Considere utilizar el patrón TryGetValue en lugar de devolver valores de opción F # (opción) en las API .NET de vainilla, y prefiera sobrecargar el método para tomar valores de opción F # como argumentos.
sindicatos discriminado
F #: Hacer uso discriminado a los sindicatos como una alternativa a las jerarquías de clases para crear datos de estructura de árbol
C#: hay instrucciones específicas para esto, pero el concepto de uniones discriminadas es ajeno a C#
funciones al curry
F #: funciones al curry son idiomática para F #
C#: No utilice currificación de parámetros en las API de vainilla .NET.
Comprobación de valores nulos
F #: esto no es idiomático para F #
C#: Considere la comprobación de valores nulos en los límites de la API de vainilla .NET.
El uso de F # tipos de
list
,map
,set
, etcF #: es idiomática utilizar estos en F #
C#: Considere el uso de .NET los tipos de interfaz de recopilación IEnumerable e IDictionary para los parámetros y los valores de retorno en las API .NET de vanilla. (es decir, no utilizan F #
list
,map
,set
)
tipos de función (la más obvia)
F #: uso de F funciones # como valores es idiomático para F #, Obviamente
C#: Utilice los tipos de delegados .NET en lugar de los tipos de funciones F # en las API .NET de vanilla.
Creo que estos deberían ser suficientes para demostrar la naturaleza de mi pregunta.
Por cierto, las directrices también tienen respuesta parcial:
... una estrategia de aplicación común en el desarrollo de orden superior métodos para bibliotecas de vainilla .NET es al autor toda la aplicación utilizando los tipos de función F #, y luego crea la API pública usando delegados como una fachada delgada encima de la implementación real de F #.
Resumir.
Hay una respuesta definitiva: no hay trucos de compilación que olvidé.
Según el documento de directrices, parece que crear una envoltura de fachada para .NET es la estrategia razonable al crear F # primero y luego crear .
La pregunta entonces permanece sobre la aplicación práctica de esta:
asambleas separadas? o
¿Diferentes espacios de nombres?
Si mi interpretación es correcta, Tomas sugiere que el uso de espacios de nombres separados debería ser suficiente, y debe ser una solución aceptable.
creo que estarán de acuerdo con que, dado que la elección de espacios de nombres es tal que no sorprende o confundir el .NET/C# usuarios, lo que significa que el espacio de nombres para ellos, probablemente, debe parecer que es el principal espacio de nombres para ellos. Los usuarios de F # tendrán que asumir la carga de elegir el espacio de nombres específico de F #. Por ejemplo:
FSharp.Foo.Bar -> espacio de nombres para F # frente biblioteca
Foo.Bar -> espacio de nombres para el envoltorio NET, idiomática para C#
Algo relevante http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/fsharp-component-design-guidelines.pdf – gradbot
Aparte de pasar por las funciones de primera clase, ¿qué ¿tus preocupaciones? F # ya va un largo camino para proporcionar una experiencia fluida de otros idiomas. – Daniel
Re: su edición, todavía no estoy seguro de por qué cree que una biblioteca creada en F # parecería no estándar desde C#. En su mayor parte, F # proporciona un superconjunto de la funcionalidad de C#. Hacer que una biblioteca se sienta natural es principalmente una cuestión de convención, lo cual es cierto sin importar el idioma que esté usando. Todo eso para decir, F # proporciona todas las herramientas que necesita para hacer esto. – Daniel