Девять способов определить метод в 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()
Есть ли какая-либо функциональная разница между ними (с точки зрения того, что она делает) или различия в использовании (с точки зрения того, когда я должен использовать, какой)? Или они все эквивалентны?
4 ответа
Чтобы ответить на ваши вопросы по порядку:
f2
а такжеf5()
вернутьUnit
потому что скала берет любойdef
без "=
"быть функцией, которая возвращаетUnit
независимо от того, что является последним элементом в блоке. Это хорошо, так как в противном случае было бы не слишком многословно определить функцию, которая ничего не возвращает.println(f5())
действителен, даже если он возвращаетUnit
потому что в скалеUnit
является допустимым объектом, хотя, по общему признанию, вы не можете его создать.Unit.toString()
является допустимым, если вообще не полезным, например.- Не все версии, которые распечатывают
10
подобные. Самое главное,f7
,f8
, а такжеf9
на самом деле функции, которые возвращают функции, которые возвращают10
вместо возвращения10
непосредственно. Когда вы объявляетеdef f8 = () => {10}
, вы объявляете функциюf8
которая не принимает аргументов и возвращает функцию, которая не принимает аргументов и возвращает одно целое число. Когда вы вызываетеprintln(f8)
затемf8
старательно возвращает вам эту функцию. Когда вы звонитеprintln(f8())
он возвращает функцию, а затем немедленно вызывает ее. - Функции
f1
,f3
,f4
, а такжеf6
все по сути эквивалентны с точки зрения того, что они делают, они различаются только с точки зрения стиля.
Как указывает "пользователь неизвестен", фигурные скобки важны только для целей определения объема и не имеют никакого значения в вашем случае использования здесь.
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))
Шесть лет спустя, в будущей версии 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, новички, изучающие язык, больше не столкнутся с той же путаницей, с которой я столкнулся шесть лет назад.
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
Для дальнейшего изучения, когда два метода возвращают один и тот же результат, вы можете скомпилировать их и сравнить байт-код. Два бинарных идентичных метода будут идентичны.