ленивые определения функций в scala - PullRequest
14 голосов
/ 25 августа 2010

Я изучал скалу и должен сказать, что это действительно классный язык.Мне особенно нравятся его возможности сопоставления с образцом и функциональные литералы, но я пришел из javascript, фона ruby, и одним из моих любимых шаблонов в этих языках является ленивый шаблон определения функции и метода.Пример в javascript:

var foo = function() {
  var t = new Date();
  foo = function() {
    return t;
  };
  return foo();
};

Тот же код с незначительными изменениями работает в ruby, где вы просто используете объект singleton для переопределения метода после выполнения вычисления.Подобные вещи очень удобны, когда требуются дорогостоящие вычисления, и вы заранее не знаете, нужен ли вам результат.Я знаю, что в scala я могу использовать кеш для имитации такого же результата, но я стараюсь избегать условных проверок, и до сих пор мои эксперименты возвращали отрицательные результаты.Кто-нибудь знает, есть ли в scala ленивый шаблон определения функции или метода?

Примечание: код javascript взят с сайта Питера Мишо .

Ответы [ 6 ]

30 голосов
/ 25 августа 2010

Весь этот сложный код в JavaScript, кажется, просто пытается кэшировать значение даты. В Scala вы можете добиться того же самого тривиально:

lazy val foo = new Date

И, если вы даже не хотите создавать val, но хотите вызвать функцию, которая будет выполнять дорогостоящий код только тогда, когда это необходимо, вы можете

def maybeExpensive(doIt: Boolean, expensive: => String) {
  if (doIt) println(expensive)
}
maybeExpensive(false, (0 to 1000000).toString)  // (0 to 1000000).toString is never called!
maybeExpensive(true, (0 to 10).toString)        // It is called and used this time

где шаблон expensive: => String называется параметром по имени, который можно представить как «Дайте мне что-то, что сгенерирует строку по запросу». Обратите внимание, что если вы используете его дважды, он будет регенерировать его каждый раз, и именно здесь появляется удобный образец Рэндалла Шульца:

def maybeExpensiveTwice(doIt: Boolean, expensive: => String) {
  lazy val e = expensive
  if (doIt) {
    println(e)
    println("Wow, that was " + e.length + " characters long!")
  }
}

Теперь вы генерируете, только если вам это нужно (через параметр by-name), и сохраните его и используйте повторно, если оно вам понадобится снова (через lazy val).

Так что делайте так, а не JavaScript, даже если вы могли бы сделать Scala похожим на JavaScript.

19 голосов
/ 25 августа 2010

Scala имеет lazy val s, инициализаторы которых не оцениваются до тех пор, пока не будет использовано val.Ленивые значения могут использоваться в качестве локальных переменных метода.

В Scala также есть параметры метода с именами, чьи выражения фактических параметров заключаются в thunk, и этот thunk оценивается каждый раз, когда на тело метода ссылается формальный параметр.

Вместе они могут использоваться для достижения ленивой семантики оценки, такой как стандартная в Haskell (по крайней мере, в моем очень ограниченном понимании Haskell).

def meth(i: => Int): Something = {
  //        ^^^^^^ by-name parameter syntax
  lazy val ii = i
  // Rest of method uses ii, not i
}

В этом методевыражение, используемое в качестве фактического параметра, будет оцениваться либо ноль раз (если динамический путь выполнения тела метода никогда не использует ii), либо один раз (если оно использует ii один или несколько раз).

10 голосов
/ 25 августа 2010

Вы можете определить lazy val, который является функцией:

lazy val foo = {
  val d = new Date
  () => { d }
}

println(foo())

foo() теперь будет возвращать один и тот же объект Date каждый раз, объект, который будет инициализирован при первом вызове foo.

Чтобы немного пояснить код, в первый раз, когда foo () вызывается { val d = new Date; () => { d } }, d присваивается новое значение даты, затем оно вычисляет последнее выражение () => { d } и присваивает его значению foo.Тогда foo - это функция без параметров, которая возвращает d.

6 голосов
/ 25 августа 2010

Я думаю, что некоторые из респондентов были немного смущены тем, как вы сформулировали вопрос.Конструкция Scala, которую вы хотите здесь, - это простое ленивое определение:

lazy val foo = new java.util.Date

Построение объекта Date будет происходить не более одного раза и будет отложено до первой ссылки на foo.

3 голосов
/ 25 августа 2010

Я ничего не знал о Ruby, но у scala также есть шаблон одиночного объекта:

Welcome to Scala version 2.8.0.r22634-b20100728020027 (Java HotSpot(TM) Client VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.

scala> object LazyInit {                                       
     |     val msec = { println("Hi,I'm here!");   System.currentTimeMillis }
     | }
defined module LazyInit

scala> System.currentTimeMillis                                              
res0: Long = 1282728315918

scala> println(System.currentTimeMillis +" : " + LazyInit.msec)              
Hi,I'm here!
1282728319929 : 1282728319930

scala> println(System.currentTimeMillis +" : " + LazyInit.msec)
1282728322936 : 1282728319930

scala> println(System.currentTimeMillis +" : " + LazyInit.msec)
1282728324490 : 1282728319930

scala> 

Если вы хотите получить функцию, вы можете сделать ее подтипом типа функции:

scala> object LazyFun extends (() => Long) {            
     |     val msec = System.currentTimeMillis          
     |     def apply() = msec                           
     | }
defined module LazyFun

scala> System.currentTimeMillis                         
res2: Long = 1282729169918

scala> println(System.currentTimeMillis + " : " + LazyFun())
1282729190384 : 1282729190384

scala> println(System.currentTimeMillis + " : " + LazyFun())
1282729192972 : 1282729190384

scala> println(System.currentTimeMillis + " : " + LazyFun())
1282729195346 : 1282729190384
2 голосов
/ 25 августа 2010

Я думаю, что вы имеете в виду, что «ленивая функция» является буквальной или анонимной функцией функции.

В Scala вы можете делать такие вещи, очень похожие на код JavaScript, который вы разместили.

val foo = () => {
    val t = new Date()
    val foo = () => {t}

    foo()
}

println ("Hello World:" + foo())

Основное отличие состоит в том, что:

  • Вы не можете переназначить внешний foo
  • Ключевого слова "function" нет, вместо этого вы используете что-то вроде (s: String) => {code}
  • Последний оператор является возвращаемым значением блока, поэтому вам не нужно добавлять «return».
...