Девять способов определить метод в 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 

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