2016-11-25 2 views
0

Weil ich mit vielen Variablen einen Dolmetscher bin definiert, schreibe ich das:Scala Teil Endrekursion

type Context = Map[String, Int] 
abstract class Expr 
case class Let(varname: String, varvalue: Expr, body: Expr) extends Expr 
case class Var(name: String) extends Expr 
case class Plus(a: Expr, b: Expr) extends Expr 
case class Num(i: Int) extends Expr 

def eval(expr: Expr)(implicit ctx: Context): Int = expr match { 
    case Let(i, e, b) => eval(b)(ctx + (i -> eval(e))) 
    case Var(s) => ctx(s) 
    case Num(i) => i 
    case Plus(a, b) => eval(a) + eval(b) 
} 

Für sehr lange Ausdrücke versagt dies wegen StackOverflowException, für Ausdrücke des Typs:

Sobald jedoch der Wert einer Variablen definiert ist, muss ich nur noch den Evaluator auf dem Körper der Let aufrufen, es scheint mir, dass es nur eine Art partielle Tail-Rekursion tun sollte.
Wie ist es möglich, partielle Tail-Rekursion in Scala zu erreichen?

+1

http://blog.higher-order.com/blog/2015/06/18/easy-performance-wins-with-scalaz/ –

+1

Ist kein Ausdruck wie 'eval (a) + eval (b) 'verhindern Tail Rekursion? Der Stack muss beibehalten werden, damit das 'eval (b)' ausgewertet werden kann, nachdem das 'eval (a)' beendet ist? Die Tail-Rekursion ist nur möglich, wenn der zurückgegebene Wert ein _single_-Aufruf an die rekursive Funktion ist. – Max

+0

Offensichtlich. Deshalb kann ich keine Schwanzrekursion haben. Allerdings, in dem Fall, ich habe gerade eine Menge 'Let's, ich möchte nicht den Stapel zu behalten, würde ich gerne die Funktion wie Tail-Recursion tun. Eine Art partielle Tail-Rekursion. Tail recurse auf die Teile, die tail-rekursiv sein können, und stapeln die Stack-Größe für andere. –

Antwort

1

Sie möchten eine Möglichkeit, Tail-Call-Optimierungen nur für einige Zweige von eval zu erhalten. Ich glaube nicht, dass dies möglich ist - die meisten Scala werden eine Annotation @tailrec an eine Methode als Ganzes akzeptieren und beim Kompilieren fehlschlagen, wenn sie die Methode nicht in eine Schleife optimieren kann.

jedoch diese iterativen macht, ist ziemlich geradlinig mit den Let Vorteil der Schwanz-Call zu nehmen:

def eval(expr: Expr, ctx: Context): Int = { 

    // The expression/context pair we try to reduce at every loop iteration 
    var exprM = expr; 
    var ctxM = ctx; 

    while (true) { 
    expr match { 
     case Var(s) => return ctxM(s) 
     case Num(i) => return i 
     case Plus(a, b) => return eval(a,ctxM) + eval(b,ctxM) 
     case Let(i, e, b) => { 
     ctxM += i -> eval(e,ctxM). // Update ctxM 
     exprM = b     // Update exprM 
     } 
    } 
    } 
    return 0; // unreachable, but Scala complains otherwise I'm not returning 'Int' 
} 

Hinweis: Das wird den Stapelüberlauf durch lange Ketten von Plus s nicht lösen - Es gibt wirklich nicht viel, was wir damit machen können, weil die rekursiven Aufrufe nicht in der Endposition sind.

Es gab eine Zeit, in der ich dachte, dass Scala einige Anmerkungen zu dieser Art von Sache machen würde, aber ich bin nicht sicher, ob es so viel Interesse an solchen Dingen mehr gibt.

+0

Es scheint, dass Ihre Lösung die beste Alternative ist. Beachten Sie, dass Sie einfach schreiben können 'exprM = expr match {.... case Seien Sie (i, e, b) => ctxM + = i -> eval (e, ctxM); b} 'wenn Sie vermeiden möchten, dass exprM mehrfach zugewiesen wird. Eine 'return'-Anweisung schließt die Zuordnungen in jedem Fall kurz. –