J'essaie de concevoir une bibliothèque en F#. La bibliothèque doit être facile à utiliser à partir de à la fois F# et C# .
Et c'est là que je suis un peu coincé. Je peux le rendre convivial pour F#, ou je peux le rendre convivial pour C#, mais le problème est de savoir comment le rendre convivial pour les deux.
Voici un exemple. Imaginons que j'ai la fonction suivante en F# :
let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a
Ceci est parfaitement utilisable à partir de F# :
let useComposeInFsharp() =
let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
composite "foo"
composite "bar"
En C#, le compose
a la signature suivante :
FSharpFunc<T, Unit> compose<T, TResult>(FSharpFunc<T, TResult> f, FSharpFunc<TResult, Unit> a);
Mais bien sûr, je ne veux pas FSharpFunc
dans la signature, ce que je veux c'est Func
y Action
à la place, comme ceci :
Action<T> compose2<T, TResult>(Func<T, TResult> f, Action<TResult> a);
Pour cela, je peux créer compose2
comme ceci :
let compose2 (f: Func<'T, 'TResult>) (a : Action<'TResult> ) =
new Action<'T>(f.Invoke >> a.Invoke)
Maintenant, ceci est parfaitement utilisable en C# :
void UseCompose2FromCs()
{
compose2((string s) => s.ToUpper(), Console.WriteLine);
}
Mais maintenant nous avons un problème en utilisant compose2
de F# ! Maintenant, je dois envelopper tous les standards de F#. funs
en Func
y Action
comme ceci :
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 question : Comment obtenir une expérience de première classe pour la bibliothèque écrite en F# pour les utilisateurs de F# et de C# ?
Jusqu'à présent, je n'ai rien trouvé de mieux que ces deux approches :
- Deux assemblages distincts : l'un destiné aux utilisateurs de F#, le second aux utilisateurs de C#.
- Un seul assemblage mais des espaces de noms différents : un pour les utilisateurs de F#, et le second pour les utilisateurs de C#.
Pour la première approche, je ferais quelque chose comme ceci :
-
Créez un projet F#, appelez-le FooBarFs et compilez-le dans FooBarFs.dll.
- Cibler la bibliothèque uniquement sur les utilisateurs de F#.
- Cachez tout ce qui n'est pas nécessaire dans les fichiers .fsi.
-
Créez un autre projet F#, appelez-le FooBarCs et compilez-le dans FooFar.dll.
- Réutiliser le premier projet F# au niveau de la source.
- Créer un fichier .fsi qui cache tout de ce projet.
- Créer un fichier .fsi qui expose la bibliothèque en C#, en utilisant les idiomes C# pour les noms, les espaces de noms, etc.
- Créer des enveloppes qui délèguent à la bibliothèque centrale, en effectuant la conversion si nécessaire.
Je pense que la deuxième approche avec les espaces de noms peut être déroutante pour les utilisateurs, mais vous avez alors une seule assemblée.
La question : Aucune d'entre elles n'est idéale, peut-être que je manque une sorte de drapeau/interrupteur/attribut du compilateur. ou une sorte d'astuce et qu'il existe une meilleure façon de procéder ?
La question : Est-ce que quelqu'un d'autre a essayé de réaliser quelque chose de similaire et si oui, comment l'avez-vous fait ?
EDIT : pour clarifier, la question ne concerne pas seulement les fonctions et les délégués mais l'expérience globale d'un utilisateur C# avec une bibliothèque F#. Cela inclut les espaces de noms, les conventions de nommage, les idiomes et autres qui sont natifs de C#. En gros, un utilisateur de C# ne devrait pas être capable de détecter que la bibliothèque a été créée en F#. Et vice versa, un utilisateur de F# devrait avoir l'impression de traiter avec une bibliothèque C#.
EDIT 2 :
Je peux voir, à partir des réponses et des commentaires jusqu'à présent, que ma question n'a pas la profondeur nécessaire, peut-être surtout en raison de l'utilisation d'un seul exemple où des problèmes d'interopérabilité entre F# et C# la question des valeurs de fonction. Je pense qu'il s'agit de l'exemple le plus évident, ce qui m'a conduit à l'utiliser pour poser la question. m'a conduit à l'utiliser pour poser la question, mais par la même occasion a donné l'impression que c'est le seul problème qui me concerne. l'impression que c'est le seul problème qui me préoccupe.
Permettez-moi de vous donner des exemples plus concrets. J'ai lu les plus excellents Directives pour la conception de composants F# (merci beaucoup à @gradbot pour cela !). Les directives du document, si elles sont utilisées, traitent de certains des problèmes, mais pas tous.
Le document est divisé en deux parties principales : 1) les directives pour cibler les utilisateurs de F# ; et 2) les directives pour cibler les utilisateurs de C#. ciblant les utilisateurs de C#. Nulle part il n'est question de prétendre qu'il est possible d'avoir une approche uniforme. uniforme, ce qui fait exactement écho à ma question : nous pouvons cibler F#, nous pouvons cibler C#, mais quelle est la solution pratique pour cibler les deux ? solution pratique pour cibler les deux ?
Pour rappel, le but est de disposer d'une bibliothèque rédigée en F#, et qui puisse être utilisée idiomatiquement de des langages F# et C#.
Le mot clé ici est idiomatique . Le problème n'est pas l'interopérabilité générale où il est juste possible d'utiliser des bibliothèques dans différents langages.
Passons maintenant aux exemples, que j'emprunte directement à l'anglais. Directives pour la conception de composants F# .
-
Modules+fonctions (F#) vs Namespaces+Types+fonctions
-
F# : Utilisez des espaces de noms ou des modules pour contenir vos types et modules. L'utilisation idiomatique est de placer les fonctions dans des modules, par ex :
// library module Foo let bar() = ... let zoo() = ... // Use from F# open Foo bar() zoo()
-
C# : Utilisez les espaces de noms, les types et les membres comme structure organisationnelle principale pour vos composants (par opposition aux modules). composants (par opposition aux modules), pour les API vanille .NET.
Ceci est incompatible avec la directive F#, et l'exemple aurait besoin de réécrire l'exemple pour l'adapter aux utilisateurs de C# :
[<AbstractClass; Sealed>] type Foo = static member bar() = ... static member zoo() = ...
En faisant cela, nous brisons l'utilisation idiomatique de F# car nous ne pouvons plus utiliser
bar
yzoo
sans le préfixer avecFoo
.
-
-
Utilisation des tuples
-
F# : Utilisez les tuples lorsque cela est approprié pour les valeurs de retour.
-
C# : Évitez d'utiliser des tuples comme valeurs de retour dans les API vanille de .NET.
-
-
Async
-
F# : Utilisez Async pour la programmation asynchrone aux limites de l'API F#.
-
C# : Exposez les opérations asynchrones en utilisant soit le modèle de programmation asynchrone de .NET (BeginFoo, EndFoo), soit comme des méthodes retournant des tâches .NET (Task), plutôt que comme des objets F# Async F#.
-
-
Utilisation de
Option
-
F# : envisager d'utiliser des valeurs d'option pour les types de retour au lieu de lever des exceptions (pour le code orienté F#).
-
Envisagez d'utiliser le motif TryGetValue au lieu de renvoyer les valeurs des options F# (option) dans vanilla NET, et préférez la surcharge des méthodes à la prise en compte des valeurs d'option F# comme arguments.
-
-
Syndicats discriminés
-
F# : Utiliser les unions discriminées comme alternative aux hiérarchies de classes pour créer des données structurées en arbre.
-
C# : Il n'y a pas de directives spécifiques à ce sujet, mais le concept d'unions discriminées est étranger au C#.
-
-
Fonctions au curry
-
F# : les fonctions curry sont idiomatiques pour F#
-
C# : N'utilisez pas le currying des paramètres dans les API vanille .NET.
-
-
Vérification des valeurs nulles
-
F# : ce n'est pas idiomatique pour F#
-
C# : Envisagez de vérifier les valeurs nulles sur les limites de l'API vanilla .NET.
-
-
Utilisation des types F#
list
,map
,set
etc.-
F# : il est idiomatique de les utiliser en F#
-
C# : Envisagez d'utiliser les types d'interface de collection .NET IEnumerable et IDictionary pour les paramètres et les valeurs de retour dans les API vanille de .NET. ( c'est-à-dire ne pas utiliser F#
list
,map
,set
)
-
-
Types de fonctions (le plus évident)
-
F# : l'utilisation de fonctions F# comme valeurs est idiomatique pour F#, évidemment
-
C# : Utilisez les types de délégués .NET de préférence aux types de fonctions F# dans les API vanille .NET.
-
Je pense que cela devrait suffire à démontrer la nature de ma question.
D'ailleurs, les lignes directrices ont également une réponse partielle :
... une stratégie de mise en œuvre commune lors de l'élaboration d'une d'ordre supérieur pour les bibliothèques vanille de .NET est d'écrire toute l'implémentation en utilisant des types de fonctions F#, puis de créer l'API publique en utilisant des délégués comme mince façade. puis de créer l'API publique en utilisant des délégués comme une façade mince au-dessus de l'implémentation F# réelle.
Pour résumer.
Il y a une réponse définitive : il n'y a pas d'astuces de compilateur que j'ai manquées .
D'après la documentation sur les directives, il semble qu'il faille d'abord créer pour F# et ensuite créer un wrapper de façade pour .NET est une stratégie raisonnable.
La question reste alors de savoir comment mettre cela en pratique :
-
Des assemblages séparés ? ou
-
Des espaces de noms différents ?
Si mon interprétation est correcte, Tomas suggère que l'utilisation d'espaces de noms séparés devrait être suffisante, et devrait être une solution acceptable.
Je pense que je serai d'accord avec cela, étant donné que le choix des espaces de noms est tel qu'il qu'il ne surprenne pas ou n'embrouille pas les utilisateurs de .NET/C#. pour eux devrait probablement ressembler à l'espace de noms primaire. Les utilisateurs de F# devront assumer la charge de choisir l'espace de noms spécifique à F#. Par exemple :
-
FSharp.Foo.Bar -> espace de nom pour F# face à la bibliothèque
-
Foo.Bar -> espace de nom pour le wrapper .NET, idiomatique pour C#
8 votes
Assez pertinent research.microsoft.com/fr/us/um/cambridge/projects/fsharp/
0 votes
À part distribuer des fonctions de premier ordre, quelles sont vos préoccupations ? F# contribue déjà largement à fournir une expérience transparente à partir d'autres langages.
0 votes
En ce qui concerne votre modification, je ne comprends toujours pas pourquoi vous pensez qu'une bibliothèque créée en F# ne serait pas standard par rapport au C#. Pour la plupart, F# fournit un sur-ensemble de la fonctionnalité C#. Faire en sorte qu'une bibliothèque semble naturelle est principalement une question de convention, ce qui est vrai quel que soit le langage que vous utilisez. Tout cela pour dire que F# fournit tous les outils dont vous avez besoin pour le faire.
0 votes
Honnêtement, même si vous incluez un échantillon de code, vous posez une question assez ouverte. Vous demandez "l'expérience globale d'un utilisateur C# avec une bibliothèque F#", mais le seul exemple que vous donnez est quelque chose qui n'est vraiment pas bien supporté en cualquier langage impératif. Traiter les fonctions comme des citoyens de première classe est un principe central de la programmation fonctionnelle, qui s'applique mal à la plupart des langages impératifs.
0 votes
@gradbot Je pense que votre commentaire méritait vraiment d'être une réponse.
0 votes
@OnorioCatenacci J'ai fourni une variété d'exemples au-delà des fonctions, voir EDIT 2.
0 votes
@Daniel s'il vous plaît voir EDIT 2, j'espère que cela clarifie la clarification :)
2 votes
@KomradeP. : concernant votre EDIT 2, FSharpx fournit des moyens de rendre l'interopérabilité plus transparente. Vous pouvez trouver des conseils dans cet excellent article de blog .
0 votes
@KomradeP. : J'ai modifié ma réponse. J'espère que cela vous aidera.
0 votes
FsCheck comme fs dans le nom, c'est plus F# orienté, mais je pense que ça pourrait être un bon exemple. Je concevrais tout de manière fonctionnelle, puis j'ajouterais une fine couche d'OO pour les autres langages .NET (en prenant soin de la plupart de tous les aspects de l'OO). C# y VB.NET ). Pour être totalement compatible avec tous les langages .NET, la meilleure option est toujours, selon moi, la suivante C# .
0 votes
CitationFs est mon expérience (au moment de la rédaction limitée).