Ich denke darüber nach, sowohl Currying- als auch Variadic-Funktionen in einer dynamisch typisierten funktionalen Programmiersprache verfügbar zu machen, aber ich frage mich, ob dies möglich ist oder nicht.
Hier sind einige Pseudocodes:
sum = if @args.empty then 0 else @args.head + sum @args.tail
das soll angeblich alle seine Argumente zusammenfassen. Wenn dann sum
selbst eine Zahl behandelt wird, dann ist das Ergebnis 0
. beispielsweise,
sum + 1
ist gleich 1, vorausgesetzt, dass +
nur mit Zahlen gearbeitet werden kann. Auch wenn dies sum == 0
zutrifft, sum
bleiben Wert und Funktionseigenschaft erhalten, egal wie viele Argumente gleichzeitig angegeben werden (daher "teilweise angewendet" und "variadisch"), zum Beispiel, wenn ich dies erkläre
g = sum 1 2 3
dann g
ist gleich 6
aber wir können uns noch weiter bewerben g
. Zum Beispiel g 4 5 == 15
ist wahr. In diesem Fall können wir das Objekt nicht g
durch ein Literal ersetzen 6
, da sie zwar denselben Wert ergeben, wenn sie als Ganzzahl behandelt werden, aber unterschiedliche Codes enthalten.
Wenn dieses Design in einer echten Programmiersprache verwendet wird, führt es dann zu Verwirrung oder Mehrdeutigkeit?
quelle
sum
ist0
ohne Argument und ruft sich rekursiv mit einem Argument auf.reduce
?args
:empty
,head
, undtail
. Dies sind alles Listenfunktionen, was darauf hindeutet, dass es möglicherweise einfacher und unkomplizierter ist, eine Liste zu verwenden, in der sich die verschiedenen Funktionen befinden. (Also,sum [1, 2, 3]
anstattsum 1 2 3
)Antworten:
Wie können Varargs implementiert werden? Wir brauchen einen Mechanismus, um das Ende der Argumentliste zu signalisieren. Dies kann entweder sein
Beide Mechanismen können im Kontext des Lehrgangs zum Implementieren von Varargs verwendet werden, aber die richtige Typisierung wird zu einem Hauptproblem. Nehmen wir an, wir haben es mit einer Funktion zu tun, mit der
sum: ...int -> int
Ausnahme, dass diese Funktion Currying verwendet (wir haben also einen ähnlichen Typsum: int -> ... -> int -> int
, außer dass wir die Anzahl der Argumente nicht kennen).Case: terminator value: Sei
end
der spezielle Terminator undT
der Typ vonsum
. Wir wissen jetzt , dass angewendetend
die Funktion zurückkehrt:sum: end -> int
und das in einem int wir eine weitere Summe ähnliche Funktion erhalten angewandt:sum: int -> T
. DaherT
ist die Vereinigung dieser Art:T = (end -> int) | (int -> T)
. Durch substituierendeT
, erhalten wir verschiedene mögliche Typen wieend -> int
,int -> end -> int
,int -> int -> end -> int
usw. Allerdings sind die meisten Typ Systeme beherbergen nicht solche Typen.Fall: explizite Länge: Das erste Argument für eine vararg-Funktion ist die Anzahl der varargs. So
sum 0 : int
,sum 1 : int -> int
,sum 3 : int -> int -> int -> int
usw. Dies ist in einigen Typ - Systemen unterstützt und ist ein Beispiel für abhängige Typisierung . Eigentlich wäre die Anzahl der Argumente ein Typparameter sein und keine regulärer Parameter - es keinen Sinn für die arity der Funktion auf einem Laufzeitwert abhängig machen würde,s = ((sum (floor (rand 3))) 1) 2
ist offensichtlich schlecht getippt: diese auswertet , um entweders = ((sum 0) 1) 2 = (0 1) 2
,s = ((sum 1) 1) 2 = 1 2
oders = ((sum 2) 1) 2 = 3
.In der Praxis sollte keine dieser Techniken verwendet werden, da sie fehleranfällig sind und in herkömmlichen Typsystemen keinen (aussagekräftigen) Typ haben. Stattdessen nur eine Liste von Werten als eine Paramter übergeben:
sum: [int] -> int
.Ja, ein Objekt kann sowohl als Funktion als auch als Wert auftreten, z. B. in einem Typensystem mit Zwängen. Sei
sum
aSumObj
, das zwei Zwänge hat:coerce: SumObj -> int -> SumObj
erlaubtsum
, als eine Funktion verwendet zu werden, undcoerce: SumObj -> int
ermöglicht es uns, das Ergebnis zu extrahieren.Technisch gesehen ist dies eine Variation des obigen Terminatorwert-Falls, wobei
T = SumObj
undcoerce
ein Unwrapper für den Typ ist. In vielen objektorientierten Sprachen ist dies mit einer Überladung von Operatoren trivial umsetzbar, zB C ++:quelle
..., force=False)
, um die Anwendung der Anfangsfunktion zu erzwingen.curryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys)
.Möglicherweise möchten Sie diese Implementierung von printf in Haskell zusammen mit dieser Beschreibung der Funktionsweise betrachten . Auf der letzten Seite finden Sie einen Link zu Oleg Kiselyovs Artikel über diese Art von Dingen, der ebenfalls lesenswert ist. Wenn Sie eine funktionale Sprache entwerfen, sollte die Website von Oleg wahrscheinlich obligatorisch sein.
Meiner Meinung nach sind diese Ansätze ein bisschen hacken, aber sie zeigen, dass es möglich ist. Wenn Ihre Sprache jedoch voll abhängig von der Eingabe ist, ist dies viel einfacher. Eine variable Funktion zum Summieren ihrer Ganzzahlargumente könnte dann ungefähr so aussehen:
Eine Abstraktion zum Definieren des rekursiven Typs, ohne dass ein expliziter Name angegeben werden muss, kann das Schreiben solcher Funktionen erleichtern.
Bearbeiten: Natürlich habe ich die Frage gerade noch einmal gelesen und Sie sagten eine dynamisch getippte Sprache, bei der die Typmechanik offensichtlich nicht wirklich relevant ist, und daher enthält die Antwort von @ amon wahrscheinlich alles, was Sie brauchen. Na ja, ich lasse das hier, falls jemand darauf stößt, während er sich fragt, wie es in einer statischen Sprache gemacht wird ...
quelle
Hier ist eine Version zum Aufrufen verschiedener Funktionen in Python3, die den "Terminator" -Ansatz von @amon verwendet, indem sie die optionalen Argumente von Python ausnutzt:
Die zurückgegebene Funktion
f
sammelt Argumente, die in aufeinanderfolgenden Aufrufen an sie übergeben wurden, in einem Array, das im äußeren Gültigkeitsbereich gebunden ist. Nur wenn dasforce
Argument wahr ist, wird die ursprüngliche Funktion mit allen bisher gesammelten Argumenten aufgerufen.Ein Nachteil dieser Implementierung ist, dass Sie immer ein erstes Argument übergeben müssen,
f
damit Sie kein "thunk" erstellen können, eine Funktion, bei der alle Argumente gebunden sind und nur mit der leeren Argumentliste aufgerufen werden können (aber ich denke, dies stimmt überein mit die typische Umsetzung von Curry).Eine weitere Einschränkung ist, dass Sie nach dem Übergeben eines falschen Arguments (z. B. eines falschen Typs) die ursprüngliche Funktion erneut abarbeiten müssen. Es gibt keine andere Möglichkeit, das interne Array zurückzusetzen. Dies erfolgt erst nach erfolgreicher Ausführung der Curry-Funktion.
Ich weiß nicht, ob Ihre vereinfachte Frage "Kann ein Objekt gleichzeitig eine Funktion und ein Nichtfunktionswert sein?" In Python implementiert werden kann, da ein Verweis auf eine Funktion ohne Klammern das interne Funktionsobjekt auswertet . Ich weiß nicht, ob dies gebogen werden kann, um einen beliebigen Wert zurückzugeben.
In Lisp wäre es wahrscheinlich einfach, da Lisp-Symbole gleichzeitig einen Wert und einen Funktionswert haben können. Der Funktionswert wird einfach ausgewählt, wenn das Symbol an der Funktionsposition erscheint (als erstes Element in einer Liste).
quelle