Ответ 1
В Hakell, где предложения содержат локальные определения для функции. Scala не имеет явных предложений where, но такая же функциональность может быть достигнута с помощью локальных var
, val
и def
.
Локальные `var` и` val`
В Scala:
def foo(x: Int, y: Int): Int = {
val a = x + y
var b = x * y
a - b
}
В Haskell:
foo :: Integer -> Integer -> Integer
foo x y = a - b
where
a = x + y
b = x * y
Локальный `def`
В Scala
def foo(x: Int, y: Int): Int = {
def bar(x: Int) = x * x
y + bar(x)
}
В Haskell
foo :: Integer -> Integer -> Integer
foo x y = y + bar x
where
bar x = x * x
Пожалуйста, исправьте меня, если я сделал какие-либо синтаксические ошибки в примере Haskell, так как в настоящее время на этом компьютере не установлен компилятор Haskell:).
Более сложные примеры могут быть достигнуты аналогичным образом (например, с использованием сопоставления шаблонов, поддерживаемых обоими языками). Локальные функции имеют точно такой же синтаксис, как и любая другая функция, только что их область видимости - это блок, в котором они находятся.
ИЗМЕНИТЬ. Также см. Daniel ответ для такого примера и некоторую разработку по этому вопросу.
EDIT 2: Добавлено обсуждение lazy
var
и val
s.
Lazy `var` и` val`
Эдвард Кемт правильно ответил, что Хаскелл, где статья имеет лень и чистоту. Вы можете сделать что-то очень похожее в Scala с помощью переменных lazy
. Они создаются только при необходимости. Рассмотрим следующий пример:
def foo(x: Int, y: Int) = {
print("--- Line 1: ");
lazy val lazy1: Int = { print("-- lazy1 evaluated "); x^2}
println();
print("--- Line 2: ");
lazy val lazy2: Int = { print("-- lazy2 evaluated "); y^2}
println();
print("--- Line 3: ");
lazy val lazy3: Int = { print("-- lazy3 evaluated ")
while(true) {} // infinite loop!
x^2 + y^2 }
println();
print("--- Line 4 (if clause): ");
if (x < y) lazy1 + lazy2
else lazy2 + lazy1
}
Здесь lazy1
, lazy2
и lazy3
- все ленивые переменные. lazy3
никогда не создается (поэтому этот код никогда не входит в бесконечный цикл), а порядок экземпляра lazy1
и lazy2
зависит от аргументов функции. Например, когда вы вызываете foo(1,2)
, вы получите lazy1
, созданный до lazy2
, и когда вы вызовете foo(2,1)
, вы получите обратное. Попробуйте выполнить код в интерпретаторе Scala и посмотрите распечатку! (Я не стану его здесь, так как этот ответ уже довольно длинный).
Вы можете добиться аналогичных результатов, если вместо ленивых переменных вы использовали функции без аргументов. В приведенном выше примере вы можете заменить каждый lazy val
на def
и добиться аналогичных результатов. Разница заключается в том, что ленивые переменные кэшируются (aka оценивается только один раз), но def
оценивается каждый раз при его вызове.
ИЗМЕНИТЬ 3: Добавлена дискуссия о просмотре, см. вопрос.
Сфера локальных определений
Локальные определения имеют объем блока, в котором они объявлены, как и ожидалось (ну, в большинстве случаев, в редких ситуациях они могут выходить из блока, например, при использовании привязки переменной потока в середине потока для циклов). Поэтому локальные var
, val
и def
могут использоваться для ограничения объема выражения. Возьмем следующий пример:
object Obj {
def bar = "outer scope"
def innerFun() {
def bar = "inner scope"
println(bar) // prints inner scope
}
def outerFun() {
println(bar) // prints outer scope
}
def smthDifferent() {
println(bar) // prints inner scope ! :)
def bar = "inner scope"
println(bar) // prints inner scope
}
def doesNotCompile() {
{
def fun = "fun" // local to this block
42 // blocks must not end with a definition...
}
println(fun)
}
}
Оба innerFun()
и outerFun()
ведут себя так, как ожидалось. Определение bar
в innerFun()
скрывает bar
, определенный в охватывающей области. Кроме того, функция fun
является локальной для своего закрывающего блока, поэтому ее нельзя использовать иначе. Метод doesNotCompile()
... не компилируется. Интересно отметить, что как println()
вызывает метод smthDifferent()
print inner scope
. Поэтому да, вы можете устанавливать определения после того, как они используются внутри методов! Я бы не рекомендовал, хотя, по-моему, это плохая практика (по крайней мере, на мой взгляд). В файлах классов вы можете упорядочить определения методов по своему усмотрению, но я бы сохранил все def
внутри функции до их использования. И val
и var
... ну... Мне неудобно ставить их после того, как они используются.
Также обратите внимание, что каждый блок должен заканчиваться выражением не с определением, поэтому вы не можете иметь все определения в конце блока. Я бы, вероятно, поместил все определения в начале блока, а затем напишу всю свою логику, создав результат в конце этого блока. Кажется более естественным этот путь, а не:
{
// some logic
// some defs
// some other logic, returning the result
}
Как я уже говорил, вы не можете закончить блок только с помощью // some defs
. Здесь Scala немного отличается от Haskell:).
РЕДАКТИРОВАТЬ 4. Разработано для определения материала после их использования, запросив комментарий Kim.
Определение "материала" после использования
Это сложная вещь для реализации на языке, который имеет побочные эффекты. В мире с чисто-без побочных эффектов порядок не будет важен (методы не будут зависеть от каких-либо побочных эффектов). Но, поскольку Scala допускает побочные эффекты, имеет значение то место, где вы определяете функцию. Кроме того, когда вы определяете val
или var
, правая сторона должна быть оценена на месте, чтобы создать экземпляр val
. Рассмотрим следующий пример:
// does not compile :)
def foo(x: Int) = {
// println *has* to execute now, but
// cannot call f(10) as the closure
// that you call has not been created yet!
// it similar to calling a variable that is null
println(f(10))
var aVar = 1
// the closure has to be created here,
// as it cannot capture aVar otherwise
def f(i: Int) = i + aVar
aVar = aVar + 1
f(10)
}
Пример, который вы даете, работает, но если val
lazy
или они def
s.
def foo(): Int = {
println(1)
lazy val a = { println("a"); b }
println(2)
lazy val b = { println("b"); 1 }
println(3)
a + a
}
В этом примере также хорошо показано кэширование на работе (попробуйте изменить lazy val
на def
и посмотреть, что произойдет:)
Я все еще в мире, у которого есть побочные эффекты, лучше придерживаться определений, прежде чем использовать их. Легче читать исходный код таким образом.
-- Flaviu Cipcigan