2013-12-25 5 views
11

В Scala хранится много полезных конструкций, таких как Option и Try в стандартной библиотеке.Почему «ленивое» ключевое слово, а не тип стандартной библиотеки?

Почему lazy имеет специальную обработку, имея собственное ключевое слово, если языки, такие как C#, которые не имеют вышеупомянутых типов, предпочитают реализовывать его как функцию библиотеки?

ответ

15

Это правда, что вы могли бы определить ленивым значение, например, как это:

object Lazy { 
    def apply[A](init: => A): Lazy[A] = new Lazy[A] { 
    private var value = null.asInstanceOf[A] 
    @volatile private var initialized = false 

    override def toString = 
     if (initialized) value.toString else "<lazy>@" + hashCode.toHexString 

    def apply(): A = { 
     if (!initialized) this.synchronized { 
     if (!initialized) { 
      value = init 
      initialized = true 
     } 
     } 
     value 
    } 
    } 

    implicit def unwrap[A](l: Lazy[A]): A = l() 
}  

trait Lazy[+A] { def apply(): A } 

Использование:

val x = Lazy { 
    println("aqui") 
    42 
} 

def test(i: Int) = i * i 

test(x) 

С другой стороны, имея lazy в качестве модификатора языка при условии имеет то преимущество, что позволяет ему участвовать в принципе равномерного доступа . Я попытался найти для него запись в блоге, но нет ничего, что выходит за рамки getters и seters. Этот принцип на самом деле более фундаментален. Для значений, следующие унифицированы: val, lazy val, def, var, object:

trait Foo[A] { 
    def bar: A 
} 

class FooVal[A](val bar: A) extends Foo[A] 

class FooLazyVal[A](init: => A) extends Foo[A] { 
    lazy val bar: A = init 
} 

class FooVar[A](var bar: A) extends Foo[A] 

class FooProxy[A](peer: Foo[A]) extends Foo[A] { 
    def bar: A = peer.bar 
} 

trait Bar { 
    def baz: Int 
} 

class FooObject extends Foo[Bar] { 
    object bar extends Bar { 
    val baz = 42 
    } 
} 

Ленивые значения были введены в Scala 2.6. Существует Lambda the Ultimate comment что свидетельствует о том, что рассуждения, возможно, придется делать с формализацией возможности иметь циклические ссылки:

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

Я не знаю, почему циклические ссылки не могут быть автоматически обрабатывается компилятором; возможно, были причины сложности или эффективности. A blog post by Iulian Dragos подтверждает некоторые из этих предположений.

10

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

Ленивый, как библиотека вероятно, будет выглядеть примерно так:

class LazyVal[T](f: =>T) { 
    @volatile private var initialized = false 

    /* 
    this does not need to be volatile since there will always be an access to the 
    volatile field initialized before this is read. 
    */ 
    private var value:T = _ 
    def apply() = { 
    if(!initialized) { 
     synchronized { 
     if(!initialized) { 
      value = f 
      initialized = true 
     } 
     } 
    } 
    value 
    } 
} 

над головой этого может быть объектом для закрытия F, который генерирует значение, а другой объект для самого LazyVal. Таким образом, это будет существенным для функции, которая используется так часто, как это.

На CLR у вас есть типы значений, поэтому накладные расходы не так плохо, если реализуете LazyVal как структуры в C#

Однако теперь доступны макросы, это может быть хорошей идеей, чтобы превратить ленивых в библиотечную функцию или, по крайней мере, разрешить настройку ленивой инициализации. Во многих случаях использования lazy val не требуется синхронизация потоков, поэтому бесполезно иметь @ volatile/synchronized служебные данные каждый раз, когда вы используете lazy val.

+2

Интересно, можно ли реализовать @lazy с помощью макрокоманд. –

+0

Я не знаю, возможно ли это, но это было бы полезно. Существует множество различных вариантов использования ленивых валов. Например, если вы просто используете lazy val для кэширования чистой функции по соображениям производительности, вам все равно, будет ли значение вычисляться дважды в раскадном сценарии. Таким образом, вы можете отключить синхронизацию. Но если вы используете ленивый вал для управления внешним ресурсом, вам все равно. Очевидно, что поведение по умолчанию должно быть максимально безопасным, но некоторые настройки были бы очень хорошими ... –

+0

Маркировка переменной как ленивой с ключевым словом имеет дополнительное преимущество в том, что она может изменить поведение существующего заявления, не касаясь тела , Изменяя только одно ключевое слово, другие объекты не обращают внимания, и тело остается нетронутым. В других реализациях вам всегда нужно добавить какой-нибудь Lazy-блок или неявное определение в правильной области. Замечание Макроса звучит очень интересно, предлагая разные вкусы ленивого, может быть, это хорошая вещь. Хорошо, что @Eugene здесь :) –

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

  • Нет связанных вопросов^_^