Почему Scala иногда применяет thunks автоматически?
Сразу после 2:40 в видео Scala Tutorial 3 от ShadowofCatron указано, что скобки, следующие за названием 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's 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's 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)
подсказка не требуется. Но когда я "псевдоним" F к б с val
затем применить его, он не работает: приложение происходит в val
; и даже lazy val
работает таким образом, так что это не точка оценки, вызывающая такое поведение.
Это все оставляет меня с вопросом:
Почему Scala иногда автоматически применяет thunks при их оценке?
Это, как я подозреваю, вывод типа? И если да, то не должна ли система типов оставаться вне семантики языка?
Это хорошая идея? Применяют ли Scala программисты вместо того, чтобы ссылаться на их значения так часто, что сделать необязательные парены лучше в целом?
Примеры написаны с использованием Scala 2.8.0RC3, DrScheme 4.0.1 в R5RS.
4 ответа
Проблема здесь:
Бух?"- сказал мой мозг по функциональному программированию, поскольку значение функции и значение, которое она оценивает при применении, - это совершенно разные вещи.
Да, но вы не объявили никакой функции.
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 требуют как минимум пустой список параметров. И есть много таких методов, которые в стиле Scala не будут иметь никаких параметров (например, length
а также size
). Таким образом, чтобы код выглядел более единообразно в отношении пустого списка параметров, Scala делает их необязательными.
Причина по умолчанию, когда вы пишете:
val b = f
чтобы оценить функцию и присвоить результат b
, как вы заметили. Вы можете использовать _
или вы можете явно указать тип b
:
// These all have the same effect
val b = f _
val b: () => Int = f
val b: Function0[Int] = f
В вашем примере
def f(): Int = { counter = counter + 1; counter }
определяет метод, а не функцию. Методы AFAIK автоматически переводятся в функции в Scala в зависимости от контекста. Чтобы определить функцию вы можете написать
val f = () => { counter = counter + 1; counter }
и я думаю, что вы получите то, что хотите.
Ваше предположение верно - у Scala есть типозависимая семантика в отношении оценки выражений.
Как и Ruby, он всегда оценивает thunks даже без скобок. (Это может иметь преимущества для целей взаимодействия, поскольку вы можете переключать чистые и, возможно, нечистые операции без необходимости изменения синтаксиса.)
Но поскольку Scala обладает мощной системой статических типов, она может нарушить указанное выше правило и избавить программиста от явного частичного применения функций в тех случаях, когда результат оценки не будет иметь смысла с точки зрения типа.
Обратите внимание, что типозависимая оценка может даже имитировать вызов по имени
Является ли поведение оценки, зависящее от типа, хорошим или плохим?... Ну, это, безусловно, может привести к запутанным случаям, подобным вашему, и больше не будет таким чистым. Но в большинстве случаев это просто работает так, как задумал программист (делая код более кратким) - так скажем, все в порядке.