2016-03-01 11 views
7

nehme ich schreiben Code wie folgt:Kotlin: Endrekursion für beide Seiten rekursiven Funktionen

tailrec fun odd(n: Int): Boolean = 
     if (n == 0) false 
     else even(n - 1) 

tailrec fun even(n: Int): Boolean = 
     if (n == 0) true 
     else odd(n - 1) 

fun main(args:Array<String>) { 
    // :(java.lang.StackOverflowError 
    System.out.println(even(99999)) 
} 

Wie erhalte ich Kotlin diese für beide Seiten rekursiven Funktionen zu optimieren, so dass ich main ohne werfen einen Stackoverflow laufen kann? Das Schlüsselwort tailrec funktioniert für die Rekursion mit einer einzigen Funktion, aber nicht komplizierter. Ich sehe auch eine Warnung, dass keine Tail-Calls gefunden werden, wo das Schlüsselwort tailrec verwendet wird. Vielleicht ist das für Compiler zu schwer?

+0

Sie können eine Feature-Anfrage an https://youtrack.jetbrains.com für das Merkmal der „gegenseitigen Endrekursion“ hinzufügen, das ist die beste Wahl, wenn Sie wollen es Kotlin hinzugefügt. Suchen Sie auch dort zuerst, falls es bereits angefordert oder geplant ist. –

+1

ich eine Kotlin Ausgabe erstellt hier: https://youtrack.jetbrains.com/issue/KT-11307 – denine99

Antwort

2

Was Sie suchen sind „richtige Endrekursion“. Die JVM unterstützt diese nicht, Sie benötigen also trampolines.

Eine richtige Endaufruf reinigt die Erinnerung an seine eigenen Funktion (Parameter, lokale Variablen) vor dem Sprung (statt Aufruf) mit dem Schwanz aufgerufenen Funktion auf. Auf diese Weise kann die Tail-Funktion direkt zu ihrer Caller-Caller-Funktion zurückkehren. Unendliche gegenseitige Rekursion ist möglich. (In funktionalen Sprachen ist dies eine der wichtigsten Funktionen.)

Um richtige Tailaufrufe in Assembler zu ermöglichen, benötigen Sie einen Befehl zum Springen (Goto) zu einer Routine/Methode, auf die über Zeiger verwiesen wird. OOP braucht Anrufe (speichert Speicherort auf dem Stapel zu springen zurück und springt dann) zu einer Routine/Verfahren, die über Zeiger verwiesen wird.

Sie können richtige Tail-Calls mit dem Trampolin-Design-Muster emulieren, vielleicht gibt es Unterstützung über die Bibliothek. Das Trampolin ist eine while-Schleife, die eine Funktion aufruft, die einen Verweis auf die nächste Funktion gibt, die einen Verweis auf den nächsten zurück ...

+1

Cool, es klingt so, als könnten wir dies in der JVM unterstützen, indem wir eine Trampolin-Methode schreiben, die Methodenreferenzen mit gegebenen Argumenten aufruft. Die Funktionen 'even' und 'odd' sollten so geändert werden, dass Methodenverweise und das nächste Argument zurückgegeben werden. Wir rufen den ersten Aufruf auf, indem wir die Trampolin-Methode mit einem Verweis auf die "even" -Funktion und das Argument '99999' aufrufen. – denine99

3

von Wikipedia https://en.wikipedia.org/wiki/Tail_call:

ein Endrekursion ist ein Unterprogramm-Aufruf als letzte Aktion eines Verfahrens durchgeführt. Wenn ein Endrekursion auf das gleiche Unterprogramm wieder später in der Aufrufkette genannt führen könnte zu sein, wird das Unterprogramm zu dem seinem Schwanz-rekursive

Also Ihr Fall kein Endrekursion definitionsgemäß ist. Das, was die Warnung sagt.

Momentan gibt es keine Möglichkeit, Compiler, dass optimieren, vor allem, weil es eine sehr seltene Situation. Aber ich bin nicht sicher, dass sogar Haskel das weg optimiert.

+4

Aus der gleichen Seite (leicht bearbeitet): „funktionale Sprachen [wie Kotlin], die die JVM Ziel [neigt] direkte Umsetzung [oder selbst] Tail-Rekursion, aber keine gegenseitige Tail-Rekursion. " Ich kann Ihnen versichern, dass Haskell gegenseitige Schwanzrekursion unterstützt. –

+0

Es tut? Cool! Ich dachte es, nur weil es Haskell ist. Danke für den Tipp. – voddan

+0

Könnten Sie weiter klären? Im geraden/ungeraden Beispiel ist die letzte Aktion von sowohl gerade als auch aus ein Subroutinenaufruf, und wir sehen, dass dieselbe Unterroutine später in der Aufrufkette aufgerufen wird. Daher sind beide Funktionen durch die Definition tail-rekursiv. – denine99

3

Hier ist eine Implementierung von @ comonad der trampoline suggestion. Es klappt!

import kotlin.reflect.KFunction 

typealias Result = Pair<KFunction<*>?, Any?> 
typealias Func = KFunction<Result> 

tailrec fun trampoline(f: Func, arg: Any?): Any? { 
    val (f2,arg2) = f.call(arg) 
    @Suppress("UNCHECKED_CAST") 
    return if (f2 == null) arg2 
     else trampoline(f2 as Func, arg2) 
} 

fun odd(n: Int): Result = 
     if (n == 0) null to false 
     else ::even to n-1 

fun even(n: Int): Result = 
     if (n == 0) null to true 
     else ::odd to n-1 

fun main(args:Array<String>) { 
    System.out.println(trampoline(::even, 9999999)) 
} 
+0

cool! :) Gibt es eine Möglichkeit, dies über Runnable zu tun? – comonad

Verwandte Themen