'Let' эквивалент Clojure в Scala - PullRequest
       50

'Let' эквивалент Clojure в Scala

22 голосов
/ 03 февраля 2011

Часто я сталкиваюсь со следующей ситуацией: предположим, у меня есть эти три функции

def firstFn: Int = ...
def secondFn(b: Int): Long = ...
def thirdFn(x: Int, y: Long, z: Long): Long = ...

и у меня также есть функция calculate. Мой первый подход может выглядеть так:

def calculate(a: Long) = thirdFn(firstFn, secondFn(firstFn), secondFn(firstFn) + a)

Выглядит красиво и без фигурных скобок - только одно выражение. Но это не оптимально, поэтому я получаю следующий код:

def calculate(a: Long) = {
  val first = firstFn
  val second = secondFn(first)

  thirdFn(first, second, second + a)
}

Теперь это несколько выражений, заключенных в фигурные скобки. В такие моменты я немного завидую Clojure. С помощью let функции я могу определить эту функцию в одном выражении.

Итак, моя цель - определить функцию calculate с одним выражением . Я придумываю 2 решения.

1 - С помощью scalaz Я могу определить это следующим образом (есть ли лучшие способы сделать это с помощью скалаза?):

  def calculate(a: Long) = 
    firstFn |> {first => secondFn(first) |> {second => thirdFn(first, second, second + a)}}

Что мне не нравится в этом решении, так это то, что оно является вложенным. Чем больше val s у меня есть, тем глубже это вложение.

2 - С пониманием for я могу достичь чего-то похожего:

  def calculate(a: Long) = 
    for (first <- Option(firstFn); second <- Option(secondFn(first))) yield thirdFn(first, second, second + a)

С одной стороны, это решение имеет плоскую структуру, как и let в Clojure, но с другой стороны, мне нужно обернуть результаты функций в Option и получить Option как результат calculate (это хорошо это я имею дело с нулями, но я не ... и не хочу).

Есть ли лучшие способы для достижения моей цели? Каков идиоматический способ решения таких ситуаций (может быть, я должен остаться с val s ... но let способ сделать это выглядит так элегантно)?

С другой стороны он подключен к Ссылочная прозрачность . Все три функции являются ссылочно прозрачными (в моем примере firstFn вычисляет некоторую константу, например, Pi), поэтому теоретически их можно заменить результатами вычислений. Я знаю это, но компилятор не знает, поэтому он не может оптимизировать мою первую попытку. И вот мой второй вопрос:

Могу ли я как-то (может быть с аннотацией) дать подсказку компилятору, что моя функция прозрачна по ссылкам, чтобы она могла оптимизировать эту функцию для меня (например, поместить туда какое-то кэширование)? *

Редактировать

Спасибо всем за отличные ответы! Просто невозможно выбрать один лучший ответ (может быть, потому что все они так хороши), поэтому я приму ответ с наибольшим количеством голосов, я думаю, что это достаточно справедливо.

Ответы [ 6 ]

12 голосов
/ 03 февраля 2011

в нерекурсивном случае, пусть - это перестройка лямбды.

def firstFn : Int = 42
def secondFn(b : Int) : Long = 42
def thirdFn(x : Int, y : Long, z : Long) : Long = x + y + z

def let[A, B](x : A)(f : A => B) : B = f(x)

def calculate(a: Long) = let(firstFn){first => let(secondFn(first)){second => thirdFn(first, second, second + a)}}

Конечно, это все еще вложено. Не могу избежать этого. Но вы сказали, что вам нравится монадическая форма. Так вот монада личности

case class Identity[A](x : A) {
   def map[B](f : A => B) = Identity(f(x))
   def flatMap[B](f : A => Identity[B]) = f(x)
}

А вот и твой монадный расчет. Разверните результат, позвонив .x

def calculateMonad(a : Long) = for {
   first <- Identity(firstFn)
   second <- Identity(secondFn(first))
} yield thirdFn(first, second, second + a)

Но на данный момент все выглядит как оригинальная версия val.

Монада Идентичности существует в Скалазе с большей изощренностью

http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/Identity.scala.html

7 голосов
/ 03 февраля 2011

Палка с оригинальной формой:

def calculate(a: Long) = {
  val first = firstFn
  val second = secondFn(first)

  thirdFn(first, second, second + a)
}

Это лаконично и понятно даже для разработчиков Java. Это примерно эквивалентно let, без ограничения области имен.

4 голосов
/ 03 февраля 2011

Вот вариант, который вы могли упустить.

def calculate(a: Long)(i: Int = firstFn)(j: Long = secondFn(i)) = thirdFn(i,j,j+a)

Если вы действительно хотите создать метод, я бы так и сделал.

В качестве альтернативы вы можете создать метод (можно назвать его let), который позволяет избежать вложения:

class Usable[A](a: A) {
  def use[B](f: A=>B) = f(a)
  def reuse[B,C](f: A=>B)(g: (A,B)=>C) = g(a,f(a))
  // Could add more
}
implicit def use_anything[A](a: A) = new Usable(a)

def calculate(a: Long) =
  firstFn.reuse(secondFn)((first, second) => thirdFn(first,second,second+a))

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

3 голосов
/ 03 февраля 2011

Почему бы не использовать сопоставление с шаблоном здесь:

def Рассчитать (a: Long) = соответствие firstFn {case f => secondFn (f) match {case s => thirdFn (f, s, s + a)}}}

3 голосов
/ 03 февраля 2011

Если вы чувствуете, что первая форма чище / более элегантна / более читабельна, то почему бы просто не придерживаться ее?

Сначала прочитайте это недавнее сообщение о коммите компилятору Scala изникто, кроме Мартина Одерского, и принимайте это близко к сердцу ...


Возможно, настоящая проблема здесь заключается в том, чтобы мгновенно бросить оружие, заявив, что оно неоптимально.JVM довольно сильно оптимизирует подобные вещи.Временами это просто потрясающе!

Предполагая, что у вас есть реальная проблема с производительностью в приложении, которое действительно нуждается в ускорении, вы должны начать с отчета профилировщика , подтверждающего , что этосущественное узкое место в правильно сконфигурированной и разогретой JVM.

Тогда и только тогда вы должны искать способы сделать это быстрее, что может привести к потере ясности кода.

0 голосов
/ 03 февраля 2011

Как насчет использования карри для записи возвращаемых значений функции (параметры из предыдущих групп параметров доступны в последующих группах).

Немного странно, но довольно кратко и без повторных вызовов:

def calculate(a: Long)(f: Int = firstFn)(s: Long = secondFn(f)) = thirdFn(f, s, s + a)

println(calculate(1L)()())
...