Перейдите к предпочтениям Отладки XCode. Удостоверьтесь, что "Символы загрузки лениво" НЕ выбран.
В Hakell, где предложения содержат локальные определения функции. В Scala нет явных предложений where, но такая же функциональность может быть достигнута с помощью локальных var
, val
и def
.
В 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
В 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 на этом компьютере :).
Более сложные примеры могут быть получены аналогичными способами (например, с использованием сопоставления с образцом, которое поддерживают оба языка). Локальные функции имеют в точности синтаксис, как и любые другие функции, только их область видимости - это блок, в котором они находятся.
РЕДАКТИРОВАТЬ : Также см. Ответ Дэниела для получения такого примера и некоторых пояснений по этому вопросу.
РЕДАКТИРОВАТЬ 2 : добавлено обсуждение ленивых
var
s и val
s.
Ответ Эдварда Кметта правильно указал, что в предложении where в Haskell есть лень и чистота. Вы можете сделать что-то очень похожее в Scala, используя ленивые
переменные. Они создаются только при необходимости. Рассмотрим следующий пример:
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
и получить аналогичные результаты. Разница в том, что ленивые переменные кэшируются (или оцениваются только один раз), но def
оценивается каждый раз, когда он вызывается.
РЕДАКТИРОВАТЬ 3: Добавлено обсуждение области видимости, см. Вопрос.
Локальные определения имеют объем блока, в котором они объявлены, как и ожидалось (ну, в большинстве случаев, в редких ситуациях они могут избежать блока, например, когда с использованием привязки переменных в середине потока в циклах for). Поэтому для ограничения объема выражения можно использовать local 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 ()
ведут себя должным образом. Определение бара
в innerFun ()
скрывает полосу
, определенную во включающей области. Кроме того, функция fun
является локальной по отношению к своему охватывающему блоку, поэтому ее нельзя использовать иначе. Метод doesNotCompile ()
... не компилируется. Интересно отметить, что оба вызова println ()
из метода smthDifferent ()
print внутренней области видимости
. Поэтому да, вы можете помещать определения после того, как они используются внутри методов! Я бы не рекомендовал, так как считаю это плохой практикой (по крайней мере, на мой взгляд). В файлах классов вы можете расположить определения методов по своему усмотрению, но я бы оставил все def
внутри функции, прежде чем они будут использованы. И val
s и var
s ... ну ... мне неудобно ставить их после того, как они используются.
Также обратите внимание, что каждый блок должен заканчиваться выражением , а не с определением, поэтому у вас не может быть всех определений в конце блока. Я бы, наверное, поставил все определения в начале блока, а затем напишите всю мою логику, дающую результат, в конце этого блока. Это кажется более естественным, а не чем:
{
// some logic
// some defs
// some other logic, returning the result
}
Как я уже сказал, вы не можете завершить блок только с помощью // some defs
. В этом Scala немного отличается от Haskell :).
РЕДАКТИРОВАТЬ 4 : Разработано определение материала после их использования, подсказано комментарием Кима .
Это сложная вещь для реализации на языке, который имеет побочные эффекты. В мире без побочных эффектов порядок не имеет значения (методы не будут зависеть от побочных эффектов). Но поскольку 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's 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
являются ленивыми
или являются def
.
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
You can use var
and val
to provide local variables, but that is different than Haskell's where
clause in two fairly important aspects: laziness and purity.
Haskell's where
clause is useful because laziness and purity alows the compiler to only instantiate the variables in the where clause that are actually used.
This means you can write a big long local definition, and drop a where
clause below it and there is no consideration needed for the order of effects (because of purity) and no consideration needed for if each individual code branch needs all of the definitions in the where clause, because laziness allows unused terms in the where clause to exist just as thunks, which purity allows the compiler to choose to elide from the resulting code when they aren't used.
Scala, unfortunately, has neither of these properties, and so cannot provide a full equivalent to Haskell's where
clause.
You need to manually factor out the var
s and val
s that you use and put them in before the statements that use them, much like ML let
statements.
Похоже, да. Я не буду вдаваться в подробности, как это уже сделал Flaviu , но приведу пример из Википедии.
Haskell:
calc :: String -> [Float]
calc = foldl f [] . words
where
f (x:y:zs) "+" = (y + x):zs
f (x:y:zs) "-" = (y - x):zs
f (x:y:zs) "*" = (y * x):zs
f (x:y:zs) "/" = (y / x):zs
f xs y = read y : xs
Эти определения - просто определения, локальные для calc
. Итак, в Scala мы бы сделали следующее:
def calc(s: String): List[Float] = {
def f(s: List[Float], op: String) = (s, op) match {
case (x :: y :: zs, "+") => (y + x) :: zs
case (x :: y :: zs, "-") => (y - x) :: zs
case (x :: y :: zs, "*") => (y * x) :: zs
case (x :: y :: zs, "/") => (y / x) :: zs
case (xs, y) => read(y) :: xs
}
s.words.foldLeft(List[Float]())(f)
}
Поскольку Scala не имеет эквивалента read
, вы можете определить его, как показано ниже, с целью запуска этого конкретного примера:
def read(s: String) = s.toFloat
Scala не есть слов
, к моему большому огорчению, хотя это легко определить:
implicit toWords(s: String) = new AnyRef { def words = s.split("\\s") }
Теперь определение Haskell более компактно по разным причинам:
Он имеет более мощный вывод типов, так что ничего, кроме типа of calc
необходимо объявить. Scala не может этого сделать из-за сознательного дизайнерского решения быть объектно-ориентированным с моделью классов.
Он имеет неявное определение сопоставления с образцом, тогда как в Scala вы должны объявить функцию, а затем объявить сопоставление с образцом.
Его обработка каррирования просто превосходит Scala в плане лаконичности. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.
В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.
Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)
вы должны объявить функцию, а затем объявить совпадение с шаблоном.Его обработка каррирования просто превосходит Scala в плане краткости. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.
В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.
Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)
вы должны объявить функцию, а затем объявить совпадение с шаблоном.Его обработка каррирования просто превосходит Scala в плане лаконичности. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.
В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.
Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)
Его обработка каррирования просто превосходит Scala по лаконичности. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.
В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.
Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)
Его обработка каррирования просто превосходит Scala по лаконичности. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.
В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть таким же компактным, каким может быть List в Scala.
Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)
В Haskell есть особая обработка списков, что позволяет иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть таким же компактным, каким может быть List в Scala.
Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)
В Haskell есть особая обработка списков, что позволяет иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.
Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)