Каковы точные правила, когда вы можете опускать скобки, точки, фигурные скобки, = (функции) и т. Д.?

106

Каковы точные правила, когда вы можете опускать (опускать) круглые скобки, точки, фигурные скобки, = (функции) и т. Д.?

Например,

(service.findAllPresentations.get.first.votes.size) must be equalTo(2).
  • service моя цель
  • def findAllPresentations: Option[List[Presentation]]
  • votes возвращается List[Vote]
  • должно и быть - обе функции спецификаций

Почему я не могу пойти:

(service findAllPresentations get first votes size) must be equalTo(2)

?

Ошибка компилятора:

«RestServicesSpecTest.this.service.findAllPresentations типа Option [List [com.sharca.Presentation]] не принимает параметры»

Почему он думает, что я пытаюсь передать параметр? Почему я должен использовать точки для каждого вызова метода?

Почему должно (service.findAllPresentations get first votes size)быть равно (2) в результате:

"не найдено: сначала значение"

Тем не менее, «должно быть равно 2» или (service.findAllPresentations.get.first.votes.size)должно быть равно 2, то есть цепочка методов работает нормально? - параметр цепочки цепочки объектов.

Я просмотрел книгу и сайт Scala и не нашел исчерпывающего объяснения.

Так ли это на самом деле, как объясняет Роб Х. в вопросе о переполнении стека. Какие символы я могу опустить в Scala? , что единственный допустимый вариант использования для исключения "." для операций в стиле "операнд-оператор-операнд", а не для цепочки методов?

Энтони Стаббс
источник

Ответы:

87

Вы, кажется, наткнулись на ответ. Во всяком случае, я постараюсь прояснить это.

Вы можете опускать точку при использовании префиксной, инфиксной и постфиксной нотации - так называемой нотации оператора . При использовании обозначений операторов и только тогда вы можете опустить круглые скобки, если методу передано менее двух параметров.

Теперь обозначение оператора - это обозначение для вызова метода , что означает, что его нельзя использовать при отсутствии вызываемого объекта.

Кратко опишу обозначения.

Префикс:

Только ~, !, +и -может быть использовано в Приставка обозначения. Это обозначение, которое вы используете, когда пишете !flagили val liability = -debt.

Инфикс:

Это обозначение, в котором метод появляется между объектом и его параметрами. Здесь подходят все арифметические операторы.

Постфикс (также суффикс):

Эта запись используется, когда метод следует за объектом и не получает параметров . Например, вы можете писать list tail, и это постфиксная запись.

Вы можете без проблем связать вызовы инфиксной нотации, пока не будет каррирован какой-либо метод. Например, мне нравится использовать следующий стиль:

(list
 filter (...)
 map (...)
 mkString ", "
)

Это то же самое, что:

list filter (...) map (...) mkString ", "

Теперь, почему я использую здесь круглые скобки, если filter и map принимают один параметр? Это потому, что я передаю им анонимные функции. Я не могу смешивать определения анонимных функций с инфиксным стилем, потому что мне нужна граница для конца моей анонимной функции. Кроме того, определение параметра анонимной функции может интерпретироваться как последний параметр инфиксного метода.

Вы можете использовать инфикс с несколькими параметрами:

string substring (start, end) map (_ toInt) mkString ("<", ", ", ">")

Каррированные функции сложно использовать с инфиксной нотацией. Функции сворачивания являются ярким примером этого:

(0 /: list) ((cnt, string) => cnt + string.size)
(list foldLeft 0) ((cnt, string) => cnt + string.size)

Вам необходимо использовать круглые скобки вне вызова инфикса. Я не уверен, что здесь действуют точные правила.

Теперь поговорим о постфиксе. Postfix может быть трудным в использовании, потому что его нельзя использовать нигде, кроме конца выражения . Например, нельзя делать следующее:

 list tail map (...)

Потому что хвост не появляется в конце выражения. Вы тоже не можете этого сделать:

 list tail length

Вы можете использовать инфиксную нотацию, используя круглые скобки для обозначения конца выражений:

 (list tail) map (...)
 (list tail) length

Обратите внимание, что постфиксная нотация не рекомендуется, потому что она может быть небезопасной .

Надеюсь, это развеяло все сомнения. Если нет, просто оставьте комментарий, и я посмотрю, что я могу сделать, чтобы его улучшить.

Дэниел С. Собрал
источник
ах, значит, вы говорите, что в моем заявлении: (((((realService findAllPresentations) get) first) голосов) size) должно быть равно 2 - get, first, голосов и размер - все постфиксные операторы, потому что они не принимают параметров ? так что мне интересно, что должно, быть и равно ...
Энтони Стаббс
Я ничего подобного не говорю, хотя вполне уверен, что так оно и есть. :-) be, вероятно, вспомогательный объект, чтобы сделать синтаксис красивее. Или, более конкретно, чтобы разрешить использование инфиксной записи с «must».
Дэниел С. Собрал,
Ну, get - это Option.get, First - list.first, голосов - это свойство класса case, а size - list.size. Что ты думаешь сейчас?
Энтони Стаббс,
Ах да - все это подкрепляется тем фактом, что "(realService findPresentation 1) .get.id должен быть равным 1" работает - поскольку Service # findPresentations (id: Int) кажется инфиксным оператором. Круто - думаю, теперь я понял. :)
Энтони Стаббс
42

Определения классов:

valили varможет быть опущено в параметрах класса, что сделает параметр закрытым.

Добавление var или val приведет к тому, что он станет общедоступным (то есть будут созданы методы доступа и мутаторы).

{} может быть опущено, если у класса нет тела, то есть

class EmptyClass

Создание экземпляра класса:

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

class D[T](val x:T, val y:T);

Это даст вам ошибку типа (найден Int, ожидаемая строка)

var zz = new D[String]("Hi1", 1) // type error

Тогда как это отлично работает:

var z = new D("Hi1", 1)
== D{def x: Any; def y: Any}

Поскольку параметр типа T определяется как наименее распространенный супертип из двух - Any.


Определения функций:

= может быть отброшен, если функция возвращает Unit (ничего).

{}для тела функции можно отбросить, если функция является одним оператором, но только если оператор возвращает значение (вам нужен =знак), то есть

def returnAString = "Hi!"

но это не работает:

def returnAString "Hi!" // Compile error - '=' expected but string literal found."

Тип возвращаемого значения функции можно не указывать, если его можно вывести (рекурсивный метод должен иметь указанный тип возврата).

() можно отбросить, если функция не принимает аргументов, то есть

def endOfString {
  return "myDog".substring(2,1)
}

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

()на самом деле не отбрасывается как таковой при определении параметра передачи по имени , но на самом деле это совершенно семантически другая нотация, то есть

def myOp(passByNameString: => String)

Говорит, что myOp принимает параметр передачи по имени, который приводит к строке (то есть это может быть блок кода, который возвращает строку), а не параметры функции,

def myOp(functionParam: () => String)

в котором говорится, что myOpпринимает функцию с нулевыми параметрами и возвращает строку.

(Имейте в виду, что параметры, передаваемые по имени, компилируются в функции; это просто улучшает синтаксис.)

() может быть опущено в определении параметра функции, если функция принимает только один аргумент, например:

def myOp2(passByNameString:(Int) => String) { .. } // - You can drop the ()
def myOp2(passByNameString:Int => String) { .. }

Но если требуется более одного аргумента, вы должны включить ():

def myOp2(passByNameString:(Int, String) => String) { .. }

Заявления:

.можно отбросить, чтобы использовать нотацию операторов, которая может использоваться только для инфиксных операторов (операторов методов, принимающих аргументы). См . Ответ Даниэля для получения дополнительной информации.

  • . также может быть удалено для хвоста списка постфиксных функций

  • () можно отбросить для постфиксных операторов list.tail

  • () не может использоваться с методами, определенными как:

    def aMethod = "hi!" // Missing () on method definition
    aMethod // Works
    aMethod() // Compile error when calling method

Поскольку эта нотация зарезервирована по соглашению для методов, которые не имеют побочных эффектов, таких как List # tail (то есть вызов функции без побочных эффектов означает, что функция не имеет наблюдаемого эффекта, за исключением ее возвращаемого значения).

  • () можно опустить для обозначения оператора при передаче одного аргумента

  • () может потребоваться использовать постфиксные операторы, которых нет в конце оператора

  • () может потребоваться для обозначения вложенных операторов, концов анонимных функций или для операторов, которые принимают более одного параметра

При вызове функции, которая принимает функцию, вы не можете опустить () из определения внутренней функции, например:

def myOp3(paramFunc0:() => String) {
    println(paramFunc0)
}
myOp3(() => "myop3") // Works
myOp3(=> "myop3") // Doesn't work

При вызове функции, которая принимает параметр по имени, вы не можете указать аргумент как анонимную функцию без параметров. Например, учитывая:

def myOp2(passByNameString:Int => String) {
  println(passByNameString)
}

Вы должны называть это так:

myOp("myop3")

или

myOp({
  val source = sourceProvider.source
  val p = myObject.findNameFromSource(source)
  p
})

но нет:

myOp(() => "myop3") // Doesn't work

ИМО, чрезмерное использование отбрасываемых типов возврата может быть вредным для повторного использования кода. Просто посмотрите на спецификацию, чтобы увидеть хороший пример снижения читабельности из-за отсутствия явной информации в коде. Количество уровней косвенного обращения к типу переменной может быть безумным. Надеюсь, более совершенные инструменты помогут избежать этой проблемы и сделать наш код лаконичным.

(Хорошо, в стремлении составить более полный, краткий ответ (если я что-то пропустил или получил что-то не так / неточно, прокомментируйте), я добавил в начало ответа. Обратите внимание, что это не язык спецификации, поэтому я не пытаюсь сделать ее строго академически правильной - просто больше похоже на справочную карту.)

Энтони Стаббс
источник
10
Я плачу. Что это.
Profpatsch
12

Коллекция цитат, дающих представление о различных условиях ...

Лично я думал, что в спецификации будет больше. Я уверен, что должно быть, я просто не ищу нужных слов ...

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

«Если тело метода имеет более одного выражения, вы должны заключить его в фигурные скобки {…}. Вы можете опустить фигурные скобки, если в теле метода есть только одно выражение».

Из главы 2 «Печатай меньше, делай больше» Programming Scala :

«Тело верхнего метода идет после знака равенства '='. Почему знак равенства? Почему не использовать только фигурные скобки {…}, как в Java? Потому что точки с запятой, типы возвращаемых функций, списки аргументов методов и даже фигурные скобки иногда опускаются, использование знака равенства предотвращает некоторые возможные неоднозначности синтаксического анализа. Использование знака равенства также напоминает нам, что даже функции являются значениями в Scala, что согласуется с поддержкой Scala функционального программирования, более подробно описанной в главе 8, Функциональное программирование в Scala ».

Из главы 1 « От нуля до шестидесяти: введение в Scala» Programming Scala :

"Функция без параметров может быть объявлена ​​без круглых скобок, и в этом случае она должна вызываться без круглых скобок. Это обеспечивает поддержку унифицированного принципа доступа, так что вызывающий не знает, является ли символ переменной или функцией без параметры.

Тело функции предваряется знаком «=», если оно возвращает значение (т. Е. Тип возвращаемого значения отличается от Unit), но тип возвращаемого значения и знак «=» могут быть опущены, если типом является Unit (т. Е. Он выглядит как процедура в отличие от функции).

Скобки вокруг тела не требуются (если тело представляет собой одно выражение); точнее, тело функции - это просто выражение, и любое выражение, состоящее из нескольких частей, должно быть заключено в фигурные скобки (выражение с одной частью может быть заключено в фигурные скобки) ".

"Функции с нулевым или одним аргументом можно вызывать без точки и круглых скобок. Но любое выражение может заключаться в круглые скобки, поэтому вы можете опустить точку и по-прежнему использовать круглые скобки.

А поскольку фигурные скобки можно использовать везде, где можно использовать круглые скобки, точку можно опустить и заключить в фигурные скобки, которые могут содержать несколько операторов.

Функции без аргументов можно вызывать без скобок. Например, функция length () для String может вызываться как «abc» .length, а не «abc» .length (). Если функция является функцией Scala, определенной без скобок, то функция должна вызываться без скобок.

По соглашению, функции без аргументов, которые имеют побочные эффекты, такие как println, вызываются в круглых скобках; те, у которых нет побочных эффектов, называются без скобок ".

Из сообщения в блоге Scala Syntax Primer :

"Определение процедуры - это определение функции, в котором тип результата и знак равенства опущены; его определяющее выражение должно быть блоком. Например, def f (ps) {stats} эквивалентно def f (ps): Unit = {stats }.

Пример 4.6.3 Вот объявление и определение процедуры с именем write:

trait Writer {
    def write(str: String)
}
object Terminal extends Writer {
    def write(str: String) { System.out.println(str) }
}

Приведенный выше код неявно дополняется следующим кодом:

trait Writer {
    def write(str: String): Unit
}
object Terminal extends Writer {
    def write(str: String): Unit = { System.out.println(str) }
}"

Из спецификации языка:

"С помощью методов, которые принимают только один параметр, Scala позволяет разработчику заменять. На пробел и опускать скобки, что позволяет использовать синтаксис оператора, показанный в нашем примере оператора вставки. Этот синтаксис используется в других местах Scala API, например как создание экземпляров Range:

val firstTen:Range = 0 to 9

Здесь снова to (Int) - это ванильный метод, объявленный внутри класса (на самом деле здесь есть еще несколько неявных преобразований типов, но вы понимаете) ".

Из Scala для Java Refugees, часть 6: Преодоление Java :

«Теперь, когда вы пробуете« m 0 », Scala отвергает его как унарный оператор на основании того, что он недопустимый (~,!, - и +). Он обнаруживает, что« m »является допустимым объектом - это функция, а не метод, и все функции являются объектами.

Поскольку «0» не является допустимым идентификатором Scala, он не может быть ни инфиксным, ни постфиксным оператором. Поэтому Scala жалуется, что ожидал ";" - которые разделяют два (почти) правильных выражения: «m» и «0». Если вы вставили его, он будет жаловаться, что m требует либо аргумента, либо, в противном случае, «_», чтобы превратить его в частично применяемую функцию ».

«Я считаю, что стиль синтаксиса оператора работает только тогда, когда у вас есть явный объект в левой части. Синтаксис предназначен для того, чтобы вы могли выражать операции стиля« операнд-оператор-операнд »естественным образом».

Какие символы я могу опустить в Scala?

Но еще меня смущает эта цитата:

«Для получения вызова метода должен существовать объект. Например, вы не можете выполнить команду« println «Hello World!» », Так как println нуждается в получателе объекта. Вы можете сделать «Консольный println« Hello World! »,« Который удовлетворяет потребность ».

Потому что, насколько я могу видеть, есть это объект , чтобы принять вызов ...

Энтони Стаббс
источник
1
Хорошо, поэтому попробовал прочитать исходный код Specs, чтобы получить некоторые подсказки, и вау. Это отличный пример проблем с магическим кодом - слишком много миксинов, вывода типов, неявных преобразований и неявных параметров. Это так сложно понять снаружи вовнутрь! Для таких больших библиотек более совершенные инструменты могут творить чудеса ... однажды ...
Энтони Стаббс
3

Мне легче следовать этому практическому правилу: в выражениях пробелы чередуются между методами и параметрами. В вашем примере (service.findAllPresentations.get.first.votes.size) must be equalTo(2)анализируется как (service.findAllPresentations.get.first.votes.size).must(be)(equalTo(2)). Обратите внимание, что круглые скобки вокруг 2 имеют более высокую ассоциативность, чем пробелы. Точки также имеют более высокую ассоциативность, поэтому (service.findAllPresentations.get.first.votes.size) must be.equalTo(2)будут разбираться как (service.findAllPresentations.get.first.votes.size).must(be.equalTo(2)).

service findAllPresentations get first votes size must be equalTo 2разбирается как service.findAllPresentations(get).first(votes).size(must).be(equalTo).2.

Марио Каму
источник
2

На самом деле, при втором чтении, возможно, это ключ:

С помощью методов, которые принимают только один параметр, Scala позволяет разработчику заменить расширение. с пробелом и опустить круглые скобки

Как упоминалось в сообщении блога: http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-6 .

Так что, возможно, это на самом деле очень строгий «синтаксический сахар», который работает только тогда, когда вы эффективно вызываете метод для объекта, который принимает один параметр . например

1 + 2
1.+(2)

И ничего больше.

Это объяснило бы мои примеры в вопросе.

Но, как я уже сказал, был бы очень признателен, если бы кто-то мог указать, где именно в спецификации языка это указано.

Хорошо, какой-то приятный парень (paulp_ из #scala) указал, где в спецификации языка эта информация:

6.12.3: Приоритет и ассоциативность операторов определяют группировку частей выражения следующим образом.

  • Если в выражении есть несколько инфиксных операций, то операторы с более высоким приоритетом связываются более тесно, чем операторы с более низким приоритетом.
  • Если есть последовательные инфиксные операции e0 op1 e1 op2. . .opn en с операторами op1,. . . , opn с одинаковым приоритетом, то все эти операторы должны иметь одинаковую ассоциативность. Если все операторы левоассоциативны, последовательность интерпретируется как (... (E0 op1 e1) op2...) Opn en. В противном случае, если все операторы ассоциативны справа, последовательность интерпретируется как e0 op1 (e1 op2 (.. .Opn en)...).
  • Постфиксные операторы всегда имеют более низкий приоритет, чем инфиксные. Например, e1 op1 e2 op2 всегда эквивалентен (e1 op1 e2) op2.

Правый операнд левоассоциативного оператора может состоять из нескольких аргументов, заключенных в круглые скобки, например, e op (e1, ..., en). Затем это выражение интерпретируется как e.op (e1,..., En).

Левоассоциативная бинарная операция e1 op e2 интерпретируется как e1.op (e2). Если op ассоциативно справа, та же операция интерпретируется как {val x = e1; e2.op (x)}, где x - новое имя.

Хм - для меня это не согласуется с тем, что я вижу, или я просто этого не понимаю;)

Энтони Стаббс
источник
хм, чтобы еще больше усугубить путаницу, это также верно: (((((realService findAllPresentations) get) first) голосов) size) должен быть равен 2, но не если я удалю любую из этих пар скобок ...
Антоний Stubbs
2

Нет ни одного. Скорее всего, вы получите совет о том, есть ли у этой функции побочные эффекты. Это подделка. Исправление состоит в том, чтобы не использовать побочные эффекты в разумных пределах, допускаемых Scala. В той степени, в которой это невозможно, все ставки отключены. Все ставки. Использование круглых скобок является элементом набора «все» и является лишним. После отмены всех ставок он не дает никакой ценности.

Этот совет, по сути, является неудачной попыткой создать систему эффектов (не путать с: менее полезен, чем другие системы эффектов).

Старайтесь не вызывать побочных эффектов. После этого подтвердите, что все ставки отключены. Скрытие за фактической синтаксической нотацией для системы эффектов может и приносит только вред.

user92983
источник
Но вот в чем проблема, когда вы работаете с гибридным объектно-ориентированным / функциональным языком, верно? В любом практическом примере вы захотите иметь функции побочных эффектов ... Не могли бы вы указать нам на некоторую информацию о «системах эффектов»? Я думаю, что более важной цитатой является фраза: «Функция без параметров может быть объявлена ​​без скобок, и в этом случае она должна вызываться без скобок. Это обеспечивает поддержку единого принципа доступа, так что вызывающий не знает, символ - это переменная или функция без параметров. ".
Энтони Стаббс