Девять способов определить метод в 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, которая будет выпущена еще дальше, ситуация улучшилась:

Это сводит наши 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 

Для дальнейшего изучения, когда два метода возвращают один и тот же результат, вы можете скомпилировать их и сравнить байт-код. Два бинарных идентичных метода будут идентичны.

Другие вопросы по тегам