2013-08-24 3 views
4

Я бы с удовольствием построил let, подобный тому, который был в Haskell в Scala. Я пробовал несколько способов, но никто не кажется хорошим. Вот код:Пользовательское выражение «let» в 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() 
     } 
    } 
) 
} 

Это идеальный вид из него (только с одним связыванием, еще может быть отделена от ,, не уверен в in ключевое слово):

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

Я не конечно, если это возможно, но у меня нет опыта работы с большинством продвинутых материалов Scala, таких как макросы, поэтому я не могу сказать.

EDIT1: Для того, чтобы быть ясно, что основные вещи, я ожидаю от него являются: быть выражение и относительно простой синтаксис (как один, описанной выше).

+0

Для тех из нас, кто не говорит Haskell, не могли бы вы объяснить, что «пусть» должно делать? –

+0

'letWithVal' делает то же самое, что и' let' в 'letTest'. – monnef

ответ

6

Вы можете использовать вперед трубу:

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

, который будет использоваться как это:

import ForwardPipeContainer._ 

def f(i: Int) = i * i 

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

Вы можете поместить несколько аргументов в кортеж:

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

, который выглядит лучше, если в сочетании с частичной функцией synatx:

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

Этот ответ является разновидностью What is a good way of reusing function result in Scala, и оба они основаны на Cache an intermediate variable in an one-liner, где я получил первоначальную идею с.

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

Я бы написал так:

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

Игнорирование лени, пусть только конструкция, которая представляет лексическую область, связывает некоторые термины в некоторых имен возвращает выражение. Таким образом, в Scala вы могли бы сделать

{ // 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 
} 

Макросы должны иметь возможность реализовать эту синтаксическую схему, если вы хотите, чтобы убедиться, что нет ничего другого происходит в блоке. На самом деле существует предложение SIP (процесс улучшения Scala), называемое Spores, которое обеспечило бы выполнение некоторых из тех же ограничений (и еще одно: вы не можете безошибочно ссылаться на ссылку объекта-объекта).

Обратите внимание, что блоки в Scala - это выражения, которые вычисляют последнее выражение в блоке. Итак, позвольте мне взять randomпусть пример из Haskell:

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

Это приводит к:

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

Как вы можете видеть блок заявление может быть использовано в качестве выражения.

+0

К решению 'val' - это не выражение (и на самом деле такое же, как' letWithVal' из сообщения вопроса). – monnef

+1

@monnef, да, вы уже подумали об этом. Ну это на самом деле * есть выражение (см. Мой комментарий). – huynhjl

+0

Да, ты прав. Это выражение, но не совсем то, что я искал - я бы хотел избежать императивных 'val/var' и блоков. Извините, если мой вопрос не был сформулирован достаточно точно. (Я понимаю, что под капотом это переведёт на что-то похожее на val/var. Я ищу способ для удобной привязки функциональным способом.) – monnef

Смежные вопросы