Девять способов определить метод в Scala?
Итак, я пытался разобраться в различных способах определения вещей в Scala, осложненных отсутствием понимания того, как обрабатываются блоки {}
:
object NewMain extends Thing{
def f1 = 10
def f2 {10}
def f3 = {10}
def f4() = 10
def f5() {10}
def f6() = {10}
def f7 = () => 10
def f8 = () => {10}
def f9 = {() => {10}}
def main(args: Array[String]){
println(f1) // 10
println(f2) // ()
println(f3) // 10
println(f4) // 10
println(f4()) // 10
println(f5) // ()
println(f5()) // ()
println(f6) // 10
println(f6()) // 10
println(f7) // <function0>
println(f7()) // 10
println(f8) // <function0>
println(f8()) // 10
println(f9) // <function0>
println(f9()) // 10
}
}
Предположительно некоторые из них эквивалентны, некоторые из них являются синтаксическим сахаром для других, а некоторые - вещами, которые я не должен использовать, но я не могу для жизни понять это. Мои конкретные вопросы:
-
Как получается println(f2)
и println(f5())
unit
? Не последний элемент в блоке 10
? Чем он отличается от println(f3())
, который дает 10
?
-
Если println(f5)
дает unit
, не должен println(f5())
быть недопустимым, так как unit
не является функцией? То же самое относится к println(f6)
и println(f6())
-
Из всех тех, которые печатают 10:
f1
, f3
, f4
, f4()
, f6
, f6()
, f7()
, f8()
, f9()
,
существует ли какая-либо функциональная разница между ними (с точки зрения того, что она делает) или различия в использовании (с точки зрения того, когда я должен использовать это)? Или все они эквивалентны?
Ответы
Ответ 1
Чтобы ответить на ваши вопросы в порядке:
-
f2
и f5()
return Unit
, потому что scala принимает любой def
без <=
"как функцию, которая возвращает Unit
, независимо от того, что последний элемент в блоке, Это хорошо, поскольку в противном случае было бы довольно сложно описать функцию, которая ничего не возвращает.
-
println(f5())
действителен, даже если он возвращает Unit
, потому что в scala Unit
- действительный объект, хотя, по общему признанию, это не тот, который вы можете создать. Unit.toString()
является допустимым, если не вообще полезным, выражением, например.
- Не все версии, напечатанные
10
, одинаковы. Самое главное, f7
, f8
и f9
являются фактически функциями, возвращающими функции, которые возвращают 10
, а не возвращают 10
напрямую. Когда вы объявляете def f8 = () => {10}
, вы объявляете функцию f8
, которая не принимает аргументов и возвращает функцию, которая не принимает аргументов и возвращает одно целое число. Когда вы вызываете println(f8)
, тогда f8
dilligently возвращает эту функцию вам. Когда вы вызываете println(f8())
, он возвращает функцию, а затем сразу вызывает ее.
- Функции
f1
, f3
, f4
и f6
по существу эквивалентны в терминах того, что они делают, они меняются только в терминах стиля.
Как указывает "неизвестный пользователь", скобки важны только для целей определения области охвата и не имеют никакого значения в вашем случае использования здесь.
Ответ 2
def f() {...}
представляет собой ситаксический сахар для
def f(): Unit = {...}
Итак, если вы опускаете "=", метод всегда будет возвращать объект типа Unit.
В Scala методы и выражения всегда возвращают что-то.
def f() = 10
is sytactic sugar for
def f() = {
10
}
Если вы напишете def f() =() = > 10, это будет так же, как и запись
def f() = {
() => 10
}
Итак, это означает, что f возвращает объект функции.
Однако вы могли написать
val f = () => 10
Когда вы вызываете это с помощью f(), он возвращает 10
Функциональные объекты и методы могут использоваться взаимозаменяемо в большинстве случаев, но есть несколько синтаксических различий.
например
Когда вы пишете
def f() = 10
println(f)
вы получаете "10", но когда вы пишете
val f = () => 10
println(f)
вы получаете
<function0>
С другой стороны, когда у вас есть этот
val list = List(1,2,3)
def inc(x: Int) = x+1
val inc2 = (x: Int) => x+1
println(list.map(inc))
println(list.map(inc2))
Оба println будут печатать то же самое
List(2,3,4)
Когда вы используете имя метода в том месте, где ожидается функциональный объект, и подпись метода совпадает с сигнатурой ожидаемого функционального объекта, он автоматически преобразуется.
Таким образом, list.map(inc)
автоматически преобразуется компилятором scala в
list.map(x => inc(x))
Ответ 3
Шесть лет спустя, в будущей версии Scala, которая будет выпущена еще дальше в будущем, все улучшилось:
- Определения
f2
и f5
были удалены как " синтаксис процедуры"
- Удалена возможность вызова
f4
f5
и f6
без парнеров .
Это приводит к 9 способам определения функции и 15 способов их вызова до 7 способов определения функции и 10 способов их вызова:
object NewMain extends Thing{
def f1 = 10
def f3 = {10}
def f4() = 10
def f6() = {10}
def f7 = () => 10
def f8 = () => {10}
def f9 = {() => {10}}
def main(args: Array[String]){
println(f1) // 10
println(f3) // 10
println(f4()) // 10
println(f6()) // 10
println(f7) // <function0>
println(f7()) // 10
println(f8) // <function0>
println(f8()) // 10
println(f9) // <function0>
println(f9()) // 10
}
}
См. также lampepfl/dotty2570 lampepfl/dotty # 2571
В результате, относительно ясно, какой синтаксис является необязательным (например, {}
s) и какие определения эквивалентны (например, def f4() = 10
и def f7 = () => 10
). Надеюсь, когда-нибудь, когда выпустят Dotty/Scala -3.0, новички, изучающие язык, больше не будут сталкиваться с той же путаницей, что и шесть лет назад.
Ответ 4
def f1 = 10
def f2 {10}
Вторая форма не использует назначение. Поэтому вы можете думать об этом как о процедуре. Он не должен возвращать что-то и возвращает Unit, даже если последнее утверждение может быть использовано для возврата чего-то определенного (но это может быть оператор if, который будет иметь только что-то конкретное в одной ветке).
def f1 = 10
def f3 = {10}
Здесь вам не нужны скобки. Вы нуждаетесь в них, например, если вы определяете val, поэтому область действия этого val ограничивается закрывающим блоком.
def sqrGtX (n:Int, x: Int) = {
val sqr = n * n
if (sqr > x)
sqr / 2
else x / 2
}
Вам нужны фигурные скобки для определения val sqr здесь. Если val объявлен во внутренней ветки, фигурные скобки не должны находиться на верхнем уровне метода:
def foo (n:Int, x: Int) =
if (n > x) {
val bar = x * x + n * n
println (bar)
bar - 2
} else x - 2
Для дальнейшего исследования, когда два метода возвращают один и тот же результат, вы можете скомпилировать их и сравнить байт-код. Два бинарных идентичных метода будут идентичными.