C#/F# als Skriptsprache
-
Seit ein paar Tagen trage ich mich mit dem Gedanken, C# und/oder F# als Skriptsprache für eine meiner Anwendungen einzusetzen. Die Vorteile gegenüber gängigen Skriptsprachen liegen IMO auf der Hand: zumindest C# ist verbreitet und bekannt; es gibt gute Tool-Unterstützung (etwa den Debugger und den C#-Parser von SharpDevelop); die Skript-Programme sind vergleichsweise schnell (Managed Code + JIT vs. der bei Skriptsprachen meist verwendete Bytecode); ich kann so wunderbare Dinge wie Lambda-Funktionen, LINQ, Expressions und implizites Quoting benutzen; ich habe im Skript Zugriff auf das ganze .NET-Framework etc.
In der Theorie ist das wunderbar, und wenn irgendwann Roslyn veröffentlicht wird, muß ich nicht einmal mehr csc.exe manuell oder via CodeDom aufrufen. Allerdings fiel mir zwischenzeitlich auf, daß man einmal geladene Assemblies nicht mehr entladen kann, d.h. wenn ich das Skript in meiner Anwendung N-mal bearbeite, neukompiliere und ausführe, muß ich N verschiedene Versionen desselben Assemblies laden. Die übliche Lösung ist offenbar, das Skript-Assembly in eine separate AppDomain zu stecken, die man ihrerseits entladen kann. Das ist wiederum hinderlich, weil der Sinn des Skripts natürlich ist, sehr ausführlich mit meiner Anwendung zu interagieren und Daten auszutauschen. Vielleicht ist das Problem weniger dringlich, wenn die meisten der Objekte im Skript unmanaged sind und deshalb zwischen verschiedenen AppDomains ausgetauscht werden können - aber das kann's irgendwie nicht sein; ich will ja nicht gegen das System arbeiten.
Gibt es denn Erfahrungen mit sowas? Ist das überhaupt sinvoll umsetzbar? Oder ist die Idee einfach bescheuert?
(Kann sein, daß die Frage in C# und .NET besser aufgehoben ist; falls dem so ist, kann der Thread gerne dorthin verschoben werden.)
-
AppDomains kann man entladen, wo ist das Problem?
C# ist keine gute Scriptsprache, finde ich. Zu viel overhead. Was eine Scriptsprache meist auszeichnet ist ja, dass du gleich mit paar Zeilen das machen kannst, was du machen willst. Ohne zig Zeilen mit imports, Klasse deklarieren, Methoden deklarieren... Ich würde immer noch zu Python oder Lua tendieren. Oder vielleicht Ruby, ich finds subjektiv ausdrucksstärker als Python.
-
Finde Angelscript ganz gut, Verwendung ist ähnlich C#.
-
Bitte seid so gut und ignoriert nicht einfach, was ich geschrieben habe, um sodann eure Lieblingsskriptsprache vorzuschlagen.
Mechanics schrieb:
AppDomains kann man entladen, wo ist das Problem?
Hab ich doch geschrieben:
audacia schrieb:
Das ist wiederum hinderlich, weil der Sinn des Skripts natürlich ist, sehr ausführlich mit meiner Anwendung zu interagieren und Daten auszutauschen.
Zwischen verschiedenen AppDomains Objekte zu tauschen ist etwa so viel Aufwand wie zwischen separaten Prozessen. Das finde ich kaum zumutbar.
Mechanics schrieb:
C# ist keine gute Scriptsprache, finde ich. Zu viel overhead. Was eine Scriptsprache meist auszeichnet ist ja, dass du gleich mit paar Zeilen das machen kannst, was du machen willst.
Meist, aber nicht immer. Ich habe eine Reihe Gründe dafür genannt, warum C# und F# bei mir schwer im Vorteil sind. Das mit dem Overhead stimmt, ist aber nicht so gravierend, weil ein Teil des Skripts, auf jeden Fall aber das Template, automatisch generiert und angepaßt wird (deshalb wäre ein Objektmodell wie NRefactory auch so hilfreich).
AngelScript kenne ich und habe ich benutzt; es orientiert sich an prozeduralem C++ und hat mit C# praktisch nichts zu tun. Die Sprache hat keinerlei moderne Elemente, die für mich interessant wären oder meine Probleme lösen könnten, sonst würde ich sie einfach weiterbenutzen.
-
So schlimm ist der Datenaustausch zwischen AppDomains nicht... Bzw., IPC unter .NET ist generell nicht so aufwändig.
Aber beschreib doch etwas genauer, was du willst. Ist die Host Anwendung native C++? Warum willst du die Scripte mehrfach bearbeiten und neuladen? Ist es etwas, wo der Benutzer selber die Scripte innerhalb der Anwendung schreibt und mit denen auch "rumspielen" kann?
Im Grunde kann ich dir nur meine subjektive Meinung anbieten. Natürlich hab ich in .NET schon zig mal Plugin Schnittstellen geschrieben, geht ja ganz leicht. Aber Plugins sind keine Scripte. Für Scripte hab ich bisher nur Python oder Lua verwendet. Die Idee, C# als Scriptsprache zu nehmen gefällt mir jetzt nicht so gut, aber ich kann auch nicht behaupten, dass es sicher nicht funktionieren wird.
-
Mechanics schrieb:
So schlimm ist der Datenaustausch zwischen AppDomains nicht... Bzw., IPC unter .NET ist generell nicht so aufwändig.
Ich kenne IPC mittels WCF, das ist schon okay, aber man kann nicht behaupten, daß es transparent wäre. Man muß viel beachten, serialisierbare Objekte mit einem Haufen Attributen versehen, spezielle Interfaces bauen, die keine Exceptions werfen, dauernd den WCF-Service refreshen etc.
Mechanics schrieb:
Aber beschreib doch etwas genauer, was du willst. Ist die Host Anwendung native C++? Warum willst du die Scripte mehrfach bearbeiten und neuladen? Ist es etwas, wo der Benutzer selber die Scripte innerhalb der Anwendung schreibt und mit denen auch "rumspielen" kann?
Gerne. Das Numbercrunching ist fast ausnahmslos C++ (mit Visual C++ kompiliert), deshalb sind die ausgetauschten Objekte großenteils nativ. Wie ich das mit dem UI mache, weiß ich noch nicht; vielleicht wird es WPF, vielleicht mache ich's doch mit Delphi/C++Builder und VCL (oder es wird sogar ein MFC-UI, wenn ich genug Selbsthaß aufbieten kann). Für WPF wie für VCL müßte ich mir etwas ausdenken, um das Marshalling zu automatisieren. Derselbe Mechanismus, der sich hier ums Marshalling kümmert, soll auch für die Verbindung von unmanaged C++ zum Skript verantwortlich sein - aber das ist eine andere Baustelle.
Mechanics schrieb:
Im Grunde kann ich dir nur meine subjektive Meinung anbieten.
Wenn die sich auf meine Voraussetzungen bezieht, bin ich dir dafür auch sehr dankbar
Mechanics schrieb:
Natürlich hab ich in .NET schon zig mal Plugin Schnittstellen geschrieben, geht ja ganz leicht. Aber Plugins sind keine Scripte.
Das ist eben das Problem. Plug-ins sind toll, die wird es auch geben (anders gesagt, das Programm wird derart modularisiert, daß ich eine Menge an Datenobjekten und Funktionen, die dem Skript zur Verfügung stehen, als Plug-in einbinden kann), aber das Skript (das teilweise von einem Tool generiert/bearbeitet werden soll) kontrolliert den Prozeßablauf und soll möglichst eine Flexibilität und Simplizität im Umgang mit Abfragen und mit Funktionsausdrücken bieten, wie es sie fast nur in C# oder F# mit LINQ gibt. Mit Python könnte man sowas ähnliches machen, aber die Performance macht mich da etwas nervös. Mir ist schon AngelScript zu langsam, und das ist unter den Bytecode-Interpretern einer der schnelleren, außerdem statisch typisiert.
Das Problem mit der Performance in Skriptsprachen ist, daß es meistens sehr langsam ist, wenn du N-mal eine Subroutine im Skript (etwa eine Lambdafunktion) als Prädikat für irgendeinen Auswahlvorgang aufrufst, weil du dauernd zwischen Anwendung und Skript hin- und herrennst.
Die Alternative ist, daß ich weniger mit Prädikaten und mehr mit Indexmengen arbeite, also etwa in R (mit Arrays propA und propB):
myIndexes <- which(propA >= 0 && propB < 10) filteredA <- propA[myIndexes] filteredB <- propB[myIndexes]
Allerdings finde ich Queries à la LINQ viel einfacher handzuhaben.
-
Ich würde dann die GUI und die Hostanwendung in .NET schreiben und die Numbercruncher in C++ als Plugins einbinden. Das hätte den Vorteil, dass deine .NET Scripte problemlos auf eine API deiner Anwendung zugreifen könnten, weil die ebenfalls .NET wäre und einfach Objekte zur Verfügung stellen könnte. Für deine nativen Daten müsstest du immer noch eine sinnvolle Schnittstelle definieren, aber vielleicht kriegst es sogar so geregelt, dass du die Daten gar nicht marshallen musst und die C++ Plugins das untereinander ausnhandeln, während die Scripte nur die Steuerung übernehmen.
Du könntest ja mehrere .NET Sprachen als Scriptsprachen zulassen. Das ist ja im Grunde erstmal kein großer Unterschied, ob das ein Plugin oder ein Script ist. Du könntest pro Sprache eine Zwischenschicht haben, die die Scripte in Plugins umwandelt. Dann könntest du z.B. auch mal IronPython nehmen, die .NET Variante. Die Performance ist aber zugegebenermaßen nicht berauschend.
F# hätte etwas weniger overhead als C#, ist aber als Scriptsprache natürlich gewöhnungsbedürftig... Weiß jetzt aber nicht, ob jetzt 2-3 interne die Scripte für dein Programm schreiben sollen, oder tausende externe Kunden
-
Die Vorteile gegenüber gängigen Skriptsprachen liegen IMO auf der Hand: zumindest C# ist verbreitet und bekannt; es gibt gute Tool-Unterstützung (etwa den Debugger und den C#-Parser von SharpDevelop); die Skript-Programme sind vergleichsweise schnell (Managed Code + JIT vs. der bei Skriptsprachen meist verwendete Bytecode); ich kann so wunderbare Dinge wie Lambda-Funktionen, LINQ, Expressions und implizites Quoting benutzen; ich habe im Skript Zugriff auf das ganze .NET-Framework etc.
Goehert das zum Problem? Das beschwoert doch nur wieder "C# ist Schrott" als Antwort herauf. Ich persoenlich habe mit Lua und Chicken Scheme sehr gute Erfahrung gemacht.
Das Problem mit der Performance in Skriptsprachen ist, daß es meistens sehr langsam ist, wenn du N-mal eine Subroutine im Skript (etwa eine Lambdafunktion) als Prädikat für irgendeinen Auswahlvorgang aufrufst, weil du dauernd zwischen Anwendung und Skript hin- und herrennst.
Was erwartest du denn von Skriptsprachen? Darueber hinaus haben einige Schemeinterpreter sehr gute JIT-Compiler, andere nicht. Trotzdem wuerde ich das so wie du nicht machen. Zum JIT-Compiler von Lua kann ich nicht viel sagen, soll aber auch fix sein.
-
Mechanics schrieb:
Für deine nativen Daten müsstest du immer noch eine sinnvolle Schnittstelle definieren, aber vielleicht kriegst es sogar so geregelt, dass du die Daten gar nicht marshallen musst und die C++ Plugins das untereinander ausnhandeln, während die Scripte nur die Steuerung übernehmen.
Vielleicht. Das würde mir auch das Überwinden der AppDomain-Barriere erleichtern.
Mechanics schrieb:
Du könntest ja mehrere .NET Sprachen als Scriptsprachen zulassen. Das ist ja im Grunde erstmal kein großer Unterschied, ob das ein Plugin oder ein Script ist.
Doch. Das Plug-in wird in VS entwickelt und steht ab Programmstart zur Verfügung. Das Skript wird vom Benutzer und vom Programm selbst laufend angepaßt, neukompiliert und ausgeführt.
Mechanics schrieb:
F# hätte etwas weniger overhead als C#, ist aber als Scriptsprache natürlich gewöhnungsbedürftig... Weiß jetzt aber nicht, ob jetzt 2-3 interne die Scripte für dein Programm schreiben sollen, oder tausende externe Kunden
Erstmal nur der kleine Kreis. Aber falls es für einen größeren Markt geöffnet wird, ist C# schon wieder schwer im Vorteil, das kennt eben jeder.
knivil schrieb:
Die Vorteile gegenüber gängigen Skriptsprachen liegen IMO auf der Hand: zumindest C# ist verbreitet und bekannt; es gibt gute Tool-Unterstützung (etwa den Debugger und den C#-Parser von SharpDevelop); die Skript-Programme sind vergleichsweise schnell (Managed Code + JIT vs. der bei Skriptsprachen meist verwendete Bytecode); ich kann so wunderbare Dinge wie Lambda-Funktionen, LINQ, Expressions und implizites Quoting benutzen; ich habe im Skript Zugriff auf das ganze .NET-Framework etc.
Goehert das zum Problem? Das beschwoert doch nur wieder "C# ist Schrott" als Antwort herauf.
??
knivil schrieb:
Ich persoenlich habe mit Lua und Chicken Scheme sehr gute Erfahrung gemacht.
Lua werde ich mir auch noch näher ansehen. Die Performance soll okay sein, aber mir mißfällt die dynamische Typisierung. Ich muß mal schauen, wie das C-Interface funktioniert. Das war bei AngelScript das eigentliche Bottleneck; der Bytecode-Interpreter ist eigentlich ganz fix.
knivil schrieb:
Was erwartest du denn von Skriptsprachen?
In diesem Fall, daß der Übergang von Skript- zu Anwendungscode möglichst kostenfrei ist, damit ich im Skript Prädikatfunktionen formulieren kann. Außerdem eine Skriptsprache, die oben genannte oder zumindest vergleichbare Features kennt.
-
Anwendungscode möglichst kostenfrei ist, damit ich im Skript Prädikatfunktionen formulieren kann.
Schwierig, da das Praedikat wahrscheinlich sehr haeufig aufgerufen wird. Du wechselst immer zwischen Anwendung und Interpreter, das ist teuer. Ist es da nicht besser eine eigene kleine DSL zu benutzen und das Praedikat nur zusammenzubauen?
-
knivil schrieb:
Anwendungscode möglichst kostenfrei ist, damit ich im Skript Prädikatfunktionen formulieren kann.
Schwierig, da das Praedikat wahrscheinlich sehr haeufig aufgerufen wird. Du wechselst immer zwischen Anwendung und Interpreter, das ist teuer.
Das ist ja genau der Grund, warum ich C#/F# verwenden möchte. Wenn meine Daten einmal managed sind (und der einmalige Übergang unmanaged -> managed bei der Erstellung ist nicht so teuer), laufen sowohl Skript als auch Aufrufer in der CLR, wodurch es gar keinen Übergang mehr gibt.
Anyway – ich habe mich ein wenig umgeschaut, zuerst bei SharpDevelop 4.2 (eher instabil) und dann bei MonoDevelop 3, das mich gerade sehr beeindruckt. Der Code-Completion-Parser versteht anonyme Typen und kommt mit Typinferenz auch in komplexeren Fällen klar. Außerdem wird für die Compilierung nicht ein externes Programm (csc.exe) aufgerufen, sondern die Compiler-Interaktion funktioniert etwa so, wie Microsoft sich Roslyn vorstellt (Compiler as a Service). Der Compiler-Service kann also prima als Expression-Evaluator mißbraucht werden, ganz ohne Umweg über csc.exe, AppDomain erstellen, Assembly laden. Um die Sache auf die Spitze zu treiben, gibt es eine "C# Interactive" analog zur F# Interactive:
http://www.mono-project.com/CsharpRepl
Und während die F# Interactive ein separater Prozeß ist (fsi.exe), ist die "C# Interactive" einfach ein Wrapper für eine Managed-Code-Schnittstelle.Die Dokumentation der Schnittstelle ist nicht auf dem neuesten Stand, aber laut Reflector kann ich mit dem Compiler-Service auch meine Skripte in situ kompilieren und ausführen, also ohne AppDomains und so. Offenbar macht das MonoDevelop-Backend meine Träume wahr.
-
LINQPad tut außerdem ziemlich genau das, was ich mir vorgestellt habe, allerdings mit manuellem AppDomain-Recycling und csc.exe-Aufrufen:
http://www.linqpad.net/HowLINQPadWorks.aspx