Почему Scala применяет thunks автоматически, иногда?

Только после 2:40 в ShadowofCatron Scala Tutorial 3 video, он указал, что круглые скобки, следующие за именем thunk, являются необязательными. "Буг?" сказал мой мозг функционального программирования, поскольку значение функции и значение, которое она оценивает при применении, являются совершенно разными.

Итак, я написал следующее, чтобы попробовать это. Мой мыслительный процесс описан в комментариях.

object Main {

    var counter: Int = 10
    def f(): Int = { counter = counter + 1; counter }

    def runThunk(t: () => Int): Int = { t() }

    def main(args: Array[String]): Unit = {
        val a = f()     // I expect this to mean "apply f to no args"
        println(a)      // and apparently it does

        val b = f       // I expect this to mean "the value f", a function value
        println(b)      // but it the value it evaluates to when applied to no args
        println(b)      // and the application happens immediately, not in the call

        runThunk(b)     // This is an error: it not println doing something funny
        runThunk(f)     // Not an error: seems to be val doing something funny
    }

}

 

Чтобы понять проблему, эта программа Scheme (и последующий дамп консоли) показывает, что я ожидал от программы Scala.

(define counter (list 10))
(define f (lambda ()
            (set-car! counter (+ (car counter) 1))
            (car counter)))

(define runThunk (lambda (t) (t)))

(define main (lambda args
               (let ((a (f))
                     (b f))
                 (display a) (newline)
                 (display b) (newline)
                 (display b) (newline)
                 (runThunk b)
                 (runThunk f))))

> (main)
11
#<procedure:f>
#<procedure:f>
13

 

Придя на этот сайт, чтобы узнать об этом, я наткнулся на этот ответ, который рассказал мне, как исправить вышеуказанную программу Scala:

    val b = f _     // Hey Scala, I mean f, not f()

Но подчеркивание подчеркивается лишь иногда. Когда я звоню runThunk(f), никакой подсказки не требуется. Но когда я 'alias' f to b с val, то примените его, это не сработает: приложение происходит в val; и даже lazy val работает таким образом, так что это не точка оценки, вызывающая такое поведение.

 

Все это оставляет мне вопрос:

Почему Scala иногда автоматически применяет thunks при их оценке?

Это, как я подозреваю, введите вывод? И если да, то не должна ли система типов оставаться вне семантики языка?

Это хорошая идея? Программисты Scala применяют thunks, а не ссылаются на их значения намного чаще, чем делает опциональный parens лучше в целом?


Примеры, написанные с использованием Scala 2.8.0RC3, DrScheme 4.0.1 в R5RS.

Ответ 1

Проблема здесь:

Бух? "сказал мое функциональное программирование мозг, поскольку значение функции и значение, которое оно оценивает, когда применяются совершенно разные вещи.

Да, но вы не объявили какую-либо функцию.

def f(): Int = { counter = counter + 1; counter }

Вы объявили метод под названием f, который имеет один пустой список параметров и возвращает Int. Метод не является функцией - он не имеет значения. Никогда, никогда. Лучшее, что вы можете сделать, это получить экземпляр Method через отражение, что совсем не то же самое.

val b = f _     // Hey Scala, I mean f, not f()

Итак, что означает f _? Если f была функцией, это означало бы само функцию, предоставленную, но здесь это не так. На самом деле это означает:

val b = () => f()

Другими словами, f _ является замыканием по вызову метода. Затворы реализуются через функции.

Наконец, почему пустые списки параметров необязательны в Scala? Поскольку в то время как Scala допускает объявления, такие как def f = 5, Java нет. Все методы в Java требуют, по крайней мере, пустого списка параметров. И есть много таких методов, которые в стиле Scala не имели бы никаких параметров (например, length и size). Таким образом, чтобы код выглядел более равномерным относительно пустого списка параметров, Scala делает их необязательными.

Ответ 2

Значение по умолчанию при записи:

val b = f

- это оценить функцию и присвоить результат b, как вы заметили. Вы можете использовать _, или вы можете явно указать тип b:

// These all have the same effect
val b = f _
val b: () => Int = f
val b: Function0[Int] = f

Ответ 3

В вашем примере

def f(): Int = { counter = counter + 1; counter }

определяет метод, а не функцию. Методы AFAIK автоматически продвигаются к функциям в Scala в зависимости от контекста. Чтобы определить функцию, вы можете написать

val f = () => { counter = counter + 1; counter }

и я думаю, вы получите то, что хотите.

Ответ 4

Ваша догадка правильная - Scala имеет зависимую от типа семантику относительно оценки выражений.

Подобно Ruby, он всегда оценивает thunks даже без круглых скобок. (Это может иметь преимущества для взаимодействия, так как вы можете переключать чистые и, возможно, нечистые операции, не изменяя синтаксис.)

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

Обратите внимание, что зависящая от типа оценка может даже моделировать call-by-name


Теперь поведение, зависящее от типа, является хорошим или плохим?... Ну, это, безусловно, может привести к запутыванию таких дел, как ваша, и больше не ощущает этого. Но в большинстве случаев он просто работает по назначению программиста (делая код более кратким). Итак, скажем, все в порядке.