Und hier stecke ich ein wenig fest. Ich kann es F#-freundlich oder C#-freundlich machen, aber das Problem besteht darin, es für beide freundlich zu machen.
Hier ist ein Beispiel. Stellen Sie sich vor, ich habe die folgende Funktion in F#:
Code: Select all
let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a
Code: Select all
let useComposeInFsharp() =
let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
composite "foo"
composite "bar"
Code: Select all
FSharpFunc compose(FSharpFunc f, FSharpFunc a);
Code: Select all
Action compose2(Func f, Action a);
Code: Select all
let compose2 (f: Func) (a : Action(f.Invoke >> a.Invoke)
Code: Select all
void UseCompose2FromCs()
{
compose2((string s) => s.ToUpper(), Console.WriteLine);
}
Code: Select all
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"
Bisher konnte ich mir nichts Besseres als diese beiden Ansätze vorstellen:
- Zwei separate Assemblys: eine für F#-Benutzer und die zweite für C#-Benutzer.
- Eine Assembly, aber anders Namespaces: einer für F#-Benutzer und der zweite für C#-Benutzer.
- Erstellen Sie ein F#-Projekt, nennen Sie es FooBarFs und kompilieren Sie es in FooBarFs.dll.
- Zielen Sie die Bibliothek ausschließlich auf F# aus. Benutzer.
- Alles Unnötige aus den .fsi-Dateien ausblenden.
- Erstellen Sie ein weiteres F#-Projekt, rufen Sie FooBarCs auf und kompilieren Sie es in FooFar.dll
- Verwenden Sie das erste F#-Projekt auf Quellebene wieder.
- Erstellen Sie eine .fsi-Datei, die alles davor verbirgt Projekt.
- Erstellen Sie eine .fsi-Datei, die die Bibliothek auf C#-Art verfügbar macht, und verwenden Sie C#-Redewendungen für Namen, Namespaces usw.
- Erstellen Sie Wrapper, die an die Kernbibliothek delegieren, und führen Sie bei Bedarf die Konvertierung durch.
Die Frage: Nichts davon ist ideal, vielleicht fehlt mir eine Art Compiler-Flag/Schalter/Attribut
oder eine Art Trick und es gibt einen besseren Weg, dies zu tun?
Die Frage: Hat jemand anderes versucht, etwas Ähnliches zu erreichen, und wenn ja, wie haben Sie das gemacht?
BEARBEITEN: Zur Verdeutlichung geht es bei der Frage nicht nur um Funktionen und Delegiert, sondern die Gesamterfahrung eines C#-Benutzers mit einer F#-Bibliothek. Dazu gehören Namespaces, Namenskonventionen, Redewendungen und dergleichen, die in C# typisch sind. Grundsätzlich sollte ein C#-Benutzer nicht erkennen können, dass die Bibliothek in F# erstellt wurde. Und umgekehrt sollte ein F#-Benutzer das Gefühl haben, sich mit einer C#-Bibliothek zu befassen.
EDIT 2:
Ich kann den bisherigen Antworten und Kommentaren entnehmen, dass meiner Frage die nötige Tiefe fehlt,
vielleicht hauptsächlich aufgrund der Verwendung nur eines Beispiels, bei dem Interoperabilitätsprobleme zwischen F# und C#
auftauchen, nämlich der Frage der Funktionswerte. Ich denke, dass dies das offensichtlichste Beispiel ist, und das hat mich dazu veranlasst, es zum Stellen der Frage zu verwenden, hat aber gleichzeitig auch den Eindruck erweckt, dass dies das einzige Problem ist, das mich beschäftigt.
Lassen Sie mich konkretere Beispiele nennen. Ich habe das hervorragendste
F# Component Design Guidelines
Dokument gelesen (vielen Dank @gradbot dafür!). Die Richtlinien im Dokument, sofern sie verwendet werden, behandeln einige der Probleme, aber nicht alle.
Das Dokument ist in zwei Hauptteile unterteilt: 1) Richtlinien für die Ausrichtung auf F#-Benutzer; und 2) Richtlinien für die
Ansprache von C#-Benutzern. Nirgendwo wird auch nur versucht, so zu tun, als sei ein einheitlicher
Ansatz möglich, der genau meine Frage widerspiegelt: Wir können auf F# abzielen, wir können auf C# abzielen, aber was ist die
praktische Lösung, um auf beide abzuzielen?
Zur Erinnerung: Das Ziel besteht darin, eine in F# verfasste Bibliothek zu haben, die idiomatisch sowohl in F#- als auch in C#-Sprachen verwendet werden kann.
Das Schlüsselwort hier ist idiomatisch. Das Problem ist nicht die allgemeine Interoperabilität, bei der es einfach
möglich ist, Bibliotheken in verschiedenen Sprachen zu verwenden.
Nun zu den Beispielen, die ich direkt den
F# Component Design Guidelines entnehme.
- Module+Funktionen (F#) vs. Namespaces+Typen+Funktionen
- F#: Verwenden Sie Namespaces oder Module, um Ihre zu enthalten Typen und Module.
Die idiomatische Verwendung besteht darin, Funktionen in Modulen zu platzieren, z. B.:Code: Select all
// library module Foo let bar() = ... let zoo() = ... // Use from F# open Foo bar() zoo() - C#: Verwenden Sie Namespaces, Typen und Mitglieder als primäre Organisationsstruktur für Ihre
Komponenten (im Gegensatz zu Modulen) für Vanilla .NET-APIs.
Dies ist nicht mit der F#-Richtlinie kompatibel und das Beispiel müsste
umgeschrieben werden, um es an die C#-Benutzer anzupassen:Auf diese Weise unterbrechen wir jedoch die idiomatische Verwendung von F#, daCode: Select all
[] type Foo = static member bar() = ... static member zoo() = ...
wir bar und zoo nicht mehr verwenden können, ohne ihnen Foo voranzustellen.
- F#: Verwenden Sie Namespaces oder Module, um Ihre zu enthalten Typen und Module.
- Verwendung von Tupeln
- F#: Verwenden Sie Tupel, wenn dies für die Rückgabe angemessen ist Werte.
- C#: Vermeiden Sie die Verwendung von Tupeln als Rückgabewerte in Vanilla .NET-APIs.
- Async
- F#: Verwenden Sie Async für asynchrone Programmierung an F#-API-Grenzen.
- C#: Machen Sie asynchrone Vorgänge entweder mit dem asynchronen .NET-Programmiermodell
(BeginFoo, EndFoo) oder als Methoden, die .NET-Aufgaben zurückgeben (Task), verfügbar, statt als F#-Async
Objekte.
- Verwendung von Optionen
- F#: Erwägen Sie die Verwendung von Optionswerten für die Rückgabe Typen statt Ausnahmen auszulösen (für F#-bezogenen Code).
- Erwägen Sie die Verwendung des TryGetValue-Musters, anstatt F#-Optionswerte (Option) in Vanilla
.NET-APIs zurückzugeben, und bevorzugen Sie Methodenüberladung gegenüber der Verwendung von F#-Optionswerten als Argumente.
- Diskriminierte Unions
- F#: Verwenden Sie diskriminierte Unions als Alternative zu Klassenhierarchien zum Erstellen baumstrukturierter Daten
- C#: Keine spezifischen Richtlinien dafür, aber das Konzept der diskriminierten Unions ist C# fremd
- Curried Funktionen
- F#: Curry-Funktionen sind idiomatisch für F#
- C#: Verwenden Sie kein Currying von Parametern in Vanilla .NET-APIs.
- Prüfung auf Nullwerte
- F#: Dies ist nicht idiomatisch für F#
- C#: Erwägen Sie die Überprüfung auf Nullwerte an Vanilla .NET API-Grenzen.
- Verwendung der F#-Typen list, map, set usw.
- F#: es ist idiomatisch, diese in F# zu verwenden
- C#: Erwägen Sie die Verwendung der .NET-Sammlungsschnittstellentypen IEnumerable und IDictionary
für Parameter und Rückgabewerte in Vanilla .NET-APIs. (d. h. keine F#-Liste, -Map, -Set verwenden)
- Funktionstypen (der offensichtliche)
- F#: Die Verwendung von F#-Funktionen als Werte ist für F# offensichtlich idiomatisch
- C#: Verwenden Sie .NET-Delegattypen anstelle von F#-Funktionstypen in Vanilla-.NET-APIs.
Übrigens haben die Richtlinien auch eine teilweise Antwort:
... eine gemeinsame Implementierungsstrategie, wenn Die Entwicklung höherwertiger
Methoden für Vanilla .NET-Bibliotheken besteht darin, die gesamte Implementierung mithilfe von F#-Funktionstypen zu erstellen und
dann die öffentliche API mithilfe von Delegaten als dünne Fassade über der eigentlichen F#-Implementierung zu erstellen.
Um es zusammenzufassen.
Es gibt eine eindeutige Antwort: Es gibt keine Compiler-Tricks, die ich habe verpasst.
Gemäß dem Richtliniendokument scheint es eine vernünftige Strategie zu sein, zuerst für F# zu schreiben und dann
einen Fassaden-Wrapper für .NET zu erstellen.
Die Frage bleibt dann bezüglich der praktischen Umsetzung davon:
- Separate Assemblys? oder
- Verschiedene Namensräume?
Mobile version