2013-08-24 2 views
4

J'aimerais construire let semblable à celui de Haskell dans Scala. J'ai essayé quelques moyens, mais aucun ne semble être bon. Voici un code:Expression "let" personnalisée dans Scala

object CustomLet extends App { 
    val data = for (i <- 1 to 1024; j <- 1 to 512) yield (i % j) * i * (i + 1) - 1 

    def heavyCalc() = { println("heavyCalc called"); data.sum } 

    def doSomethingWithRes(res: Int) = { 
    println(s"${res * res}") 
    1 
    } 

    def cond(value: Int): Boolean = value > 256 

    // not really usable, even though it's an expression (2x heavyCalc calls) 
    def withoutLet() = if (cond(heavyCalc())) doSomethingWithRes(heavyCalc()) else 0 

    // not an expression 
    def letWithVal(): Int = { 
    val res = heavyCalc() 
    if (cond(res)) doSomethingWithRes(res) 
    else 0 
    } 

    // a lot of code to simulate "let", at least it is an expression 
    def letWithMatch(): Int = heavyCalc() match { 
    case res => if (cond(res)) doSomethingWithRes(res) else 0 
    } 

    // not perfect solution from 
    // http://stackoverflow.com/questions/3241101/with-statement-equivalent-for-scala/3241249#3241249 
    def let[A, B](param: A)(body: A => B): B = body(param) 

    // not bad, but I'm not sure if it could handle more bindings at once 
    def letWithApp(): Int = let(heavyCalc()) {res => if (cond(res)) doSomethingWithRes(res) else 0} 

    List[(String,() => Int)](
    ("withoutLet", withoutLet), 
    ("letWithVal", letWithVal), 
    ("letWithMatch", letWithMatch), 
    ("letWithApp", letWithApp) 
).foreach(
    item => item match { 
     case (title, func) => { 
     println(s"executing $title") 
     val ret = func() 
     println(s"$title finished with $ret") 
     println() 
     } 
    } 
) 
} 

Ceci est le look idéal de celui-ci (avec une seule liaison, plus pourrait être séparés par ,, pas sûr du mot-clé in):

// desired look 
    def letTest(): Int = 
    let res = heavyCalc() in 
     if (cond(res)) doSomethingWithRes(res) else 0 

Je ne suis pas Bien sûr, si c'est possible, mais je n'ai aucune expérience avec la plupart des trucs Scala avancés comme les macros, donc je ne peux pas vraiment le dire.

EDIT1: Pour être clair, les principales choses que je prévois de lui sont les suivants: être expression et relativement syntaxe simple (comme celui décrit ci-dessus).

+0

Pour ceux d'entre nous qui ne parlent pas Haskell, pourriez-vous expliquer ce que "let" est censé faire? –

+0

Le 'letWithVal' fait la même chose que le' let' dans le 'letTest'. – monnef

Répondre

6

Vous pouvez utiliser un tuyau avant:

object ForwardPipeContainer { 
    implicit class ForwardPipe[A](val value: A) extends AnyVal { 
    def |>[B](f: A => B): B = f(value) 
    } 
} 

à utiliser comme ceci:

import ForwardPipeContainer._ 

def f(i: Int) = i * i 

println(f(3) |> (x => x * x)) 

Vous pouvez mettre plusieurs arguments dans un tuple:

println((f(2), f(3)) |> (x => x._1 * x._2)) 

qui semble mieux si elle est combinée avec Syntax fonction partielle:

println((f(2), f(3)) |> { case (x, y) => x * y }) 

Cette réponse est une variante de What is a good way of reusing function result in Scala, et les deux sont basées sur Cache an intermediate variable in an one-liner où j'ai eu l'idée initiale de.

3
def letTest(): Int = 
    let res = heavyCalc() in 
     if (cond(res)) doSomethingWithRes(res) else 0 

Je voudrais écrire ceci:

def letTest(): Int = { 
    val res = heavyCalc() 
    if (cond(res)) doSomethingWithRes(res) else 0 
} 

Ignorant la paresse, nous est juste une construction qui introduit un champ lexical, se fixe certains termes à certains noms renvoie alors une expression. Donc, à Scala vous faire

{ // new lexical scope 
    // bind terms section 
    val a = f() 
    def b = a + g() // may be I don't want g to be evaluated unless b is needed 
    val c = h() 
    // result expression 
    if (c) b else a 
} 

Les macros devraient être en mesure d'appliquer cette disposition syntaxique si vous voulez vous assurer qu'il n'y a rien d'autre à faire dans le bloc. Il existe en fait une proposition SIP (Scala Improvement Process) appelée Spores qui appliquerait certaines des mêmes contraintes (et une autre: que vous ne saisissiez pas une référence d'un objet englobant sans le savoir).

Notez que les blocs dans Scala sont des expressions qui évaluent à la dernière expression dans le bloc. Alors, laissez-moi prendre un randomlaisser exemple de Haskell:

aaa = let y = 1+2 
      z = 4+6 
      in let f = 3 
       e = 3 
      in e+f 

Cela se traduit par:

val aaa = { 
    val y = 1 + 2 
    val z = 4 + 6 
    val u = { 
    val f = 3 
    val e = 3 
    e + f 
    } 
    u 
} 

Comme vous pouvez le voir la déclaration de bloc peut être utilisé comme une expression.

+0

Pour la solution 'val' - ce n'est pas une expression (et est en fait identique à' letWithVal' de la question post). – monnef

+1

@monnef, ah oui vous avez déjà pensé à ce sujet. Eh bien, en fait, c'est * une * expression (voir mon commentaire). – huynhjl

+0

Oui, vous avez raison. C'est une expression, mais pas exactement ce que je cherchais - je voudrais éviter l'impératif 'val/var' et les blocs aussi. Je suis désolé si ma question n'était pas suffisamment précise. (Je comprends que sous le capot, il se traduira par quelque chose de similaire à la val/var. Je cherche un moyen de liaison facile d'une manière fonctionnelle.) – monnef