Які точні правила, коли ви можете опустити дужки, крапки, дужки, = (функції) тощо?


106

Які точні правила, коли ви можете опустити (пропустити) дужки, крапки, дужки, = (функції) тощо?

Наприклад,

(service.findAllPresentations.get.first.votes.size) must be equalTo(2).
  • service є моїм об’єктом
  • def findAllPresentations: Option[List[Presentation]]
  • votes повертає List[Vote]
  • must і be - це обидві функції специфікацій

Чому я не можу піти:

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

?

Помилка компілятора:

"RestServicesSpecTest.this.service.findAllPresentations type Option [Список [com.sharca.Presentation]] не приймає параметрів"

Чому, на думку, я намагаюся передати параметр? Чому я повинен використовувати крапки для кожного виклику методу?

Чому має (service.findAllPresentations get first votes size)бути рівнимTo (2) призведе до:

"не знайдено: значення перше"

Тим не менше, "має бути рівнимДо 2" (service.findAllPresentations.get.first.votes.size)має бути рівнимДо 2, тобто метод ланцюга працює добре? - параметр ланцюга ланцюга об'єкта.

Я переглянув книгу та веб-сайт Scala і не можу реально знайти всебічного пояснення.

Це насправді, як пояснює Роб H у запитанні про переповнення стека. Які символи я можу опустити в Scala? , що єдиний дійсний варіант використання для пропуску '.' призначений для операцій стилю "оператор операнда", а не для ланцюга методів?

Відповіді:


87

Ви ніби натрапили на відповідь. У всякому разі, я спробую це зрозуміти.

Ви можете опустити крапку, використовуючи позначення префікса, інфіксації та постфікса - так звані позначення оператора . Під час використання позначень оператора і лише тоді ви можете опустити дужки, якщо методу передано менше двох параметрів.

Тепер позначення оператора - це позначення для виклику методу , що означає, що його не можна використовувати за відсутності об'єкта, який викликається.

Я коротко деталізую позначення.

Приставка:

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

Поправка:

Це позначення, де метод з'являється між об'єктом і його параметрами. Тут вписуються арифметичні оператори.

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

Ця позначення використовується, коли метод слід за об'єктом і не отримує жодних параметрів . Наприклад, ви можете написати list tail, і це позначення постфіксу.

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

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

Це те саме, що:

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

Тепер, чому я тут використовую дужки, якщо фільтр і карта беруть один параметр? Це тому, що я передаю їм анонімні функції. Я не можу поєднувати визначення анонімних функцій із стилем інфікування, оскільки мені потрібна межа в кінці моєї анонімної функції. Також визначення параметра анонімної функції може інтерпретуватися як останній параметр методу infix.

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

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) отримує) перший) голос) розмір) повинен бути рівнимДо 2 - отримати, по-перше, голоси та розмір - всі оператори постфікса, тому що вони не приймають жодних параметрів ? тож мені цікаво, що треба, бути і рівним. Щоб бути ...
Антоні

Я нічого подібного не кажу, хоча я майже впевнений, що так і має бути. :-) "бути" - це, мабуть, помічник, щоб зробити синтаксис красивішим. Або, більш конкретно, щоб дозволити використовувати позначення інфіксації за допомогою "must".
Даніель К. Собрал

Добре дістати це Option.get, Перше - list.first, голоси - це властивість класу регістру, а розмір - list.size. Що ти думаєш зараз?
Антоні Стаббс

Ага так - все це підкріплюється тим, що "(realService findPresentation 1) .get.id повинен бути рівнимTo 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приймає функцію, яка має нульові параметри і повертає String.

(Майте на увазі, параметри пропускання імені збираються у функції; це просто робить синтаксис приємнішим.)

() може бути відхилено у визначенні параметра функції, якщо функція бере лише один аргумент, наприклад:

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

Але якщо для цього потрібно більше ніж один аргумент, ви повинні включити ():

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

Виписки:

.можна скинути, щоб використовувати позначення операторів, які можуть бути використані лише для операторів infix (операторів методів, які беруть аргументи). Див . Відповідь Даниїла для отримання додаткової інформації.

  • . також можна скинути для списку функцій постфіксу

  • () може бути скинуто для операторів Postfix операторів list.tail

  • () не можна використовувати з методами, визначеними як:

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

Оскільки це позначення зарезервовано за умовами для методів, які не мають побічних ефектів, як, наприклад, список № хвоста (тобто викликання функції без побічних ефектів означає, що функція не має спостережуваного ефекту, за винятком її зворотного значення).

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

  • () може знадобитися використання операторів Postfix, які не знаходяться в кінці заяви

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

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

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

IMO, надмірне використання типів повернення, що випадають, може бути шкідливим для повторного використання коду. Просто подивіться на специфікацію для гарного прикладу зниження читабельності через відсутність чіткої інформації в коді. Кількість рівнів непрямості, щоб фактично з'ясувати, який тип змінної може бути горіхами. Сподіваємось, кращі інструменти можуть запобігти цій проблемі та зберегти наш код стислим.

(Гаразд, у прагненні зібрати більш повну, стислу відповідь (якщо я щось пропустив чи щось невірно / неточно прокоментуйте), я додав до початку відповіді. Зверніть увагу, це не мова специфікація, тому я не намагаюсь зробити його точно академічно правильним - так само, як довідкова картка.)


10
Я плачу. Що це.
Profpatsch

12

Збірка цитат, що дають уявлення про різні умови ...

Особисто я думав, що в специфікації буде більше. Я впевнений, що має бути, я просто не шукаю потрібних слів ...

Однак є кілька джерел, і я зібрав їх разом, але нічого насправді повного / всеосяжного / зрозумілого /, що пояснює мені вищезазначені проблеми ...:

"Якщо в тілі методу є більше одного виразу, ви повинні оточити його фігурними дужками {...}. Ви можете опустити дужки, якщо тіло методу має лише один вираз."

З глави 2, "Введіть менше, зробіть більше" програми Scala :

"Тіло верхнього методу надходить після знаку рівності '='. Чому знак рівності? Чому не просто фігурні дужки {...}, як у Java? Тому що крапки з комою, типи повернення функцій, списки аргументів методів і навіть фігурні дужки іноді опускаються, використання знака рівності запобігає декільком можливим двозначним розбору. Використання знаку рівності також нагадує нам, що навіть функції є значеннями у Scala, що відповідає підтримці Scala функціонального програмування, більш докладно описаній у главі 8, Функціональне програмування в Скала ».

З глави 1 "Нуль до шістдесяти: введення Scala" програми 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 дозволяє розробнику замінити пробіл і пропустити круглі дужки, що дозволяє синтаксису оператора, показаному в нашому прикладі оператора вставки. Цей синтаксис використовується в інших місцях в API Scala, наприклад як побудова екземплярів діапазону:

val firstTen:Range = 0 to 9

Знову ж таки, до (Int) - це метод ванілі, оголошений всередині класу (насправді тут є ще кілька неявних типів перетворень, але ви отримуєте дрейф). "

Від Scala для Java-біженців Частина 6: Подолання Яви :

"Тепер, коли ви спробуєте" m 0 ", Scala відкидає його як одинаковий оператор, виходячи з того, що він не є дійсним (~,!, - і +). Він виявляє, що" m "є дійсним об'єктом - це функція, а не метод, і всі функції є об'єктами.

Оскільки "0" не є дійсним ідентифікатором Scala, він не може бути ні інфіксом, ні оператором postfix. Тому Скала скаржиться, що очікував ";" - який би розділяв два (майже) дійсні вирази: "m" та "0". Якщо ви вставили його, то він скаржиться, що m вимагає або аргументу, або, якщо цього не зробити, "_", щоб перетворити його на частково застосовану функцію. "

"Я вважаю, що стиль синтаксису оператора працює лише тоді, коли у вас є явний об'єкт з лівої сторони. Синтаксис призначений для того, щоб ви виражали операції в стилі" operand operator operand "стилем природним чином".

Яких персонажів можна опустити в Scala?

Але мене також бентежить ця цитата:

"Для отримання виклику методу повинен бути об'єкт. Наприклад, ви не можете робити" println "Hello World!" ", Оскільки println потребує одержувача об'єкта. Ви можете зробити "Console println" Hello World! ", Який задовольняє потребу."

Тому що, наскільки я бачу, є об’єкт для отримання дзвінка ...


1
Добре, тому спробувавши прочитати джерело Specs, щоб отримати деякі підказки та woah. Це чудовий приклад проблем з магічним кодом - занадто багато мікшин, тип виводу та неявна конверсія та неявні параметри. Це так важко зрозуміти ззовні в! Для таких великих бібліотек кращі інструменти можуть творити чудеса ... одного дня ...
Antony Stubbs

3

Мені легше дотримуватися цього правила: у просторах виразів чергуються методи та параметри. У вашому прикладі (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)).

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,. . . , порівняно з одним і тим же пріоритетом, то всі ці оператори повинні мати однакову асоціативність. Якщо всі оператори ліво-асоціативні, послідовність інтерпретується як (.... (E0 op1 e1) op2 ...) opn en. В іншому випадку, якщо всі оператори є праведними асоціативними, послідовність інтерпретується як e0 op1 (e1 op2 (. .Opn en).).
  • Оператори Postfix завжди мають нижчий пріоритет, ніж оператори infix. Наприклад, e1 op1 e2 op2 завжди еквівалентно (e1 op1 e2) op2.

Правий операнд ліво-асоціативного оператора може складатися з декількох аргументів, укладених у круглі дужки, наприклад, e op (e1, ..., en). Потім цей вираз інтерпретується як e.op (e1, ..., en).

Ліво-асоціативна двійкова операція e1 op e2 інтерпретується як e1.op (e2). Якщо оп є справедливим асоціативним, та сама операція інтерпретується як {val x = e1; e2.op (x)}, де x - свіжа назва.

Хм - для мене це не поєднується з тим, що я бачу, або я просто не розумію цього;)


Хм, для подальшого додавання плутанини це також справедливо: ((((((RealService findAllPresentations) отримуємо) перший) голос) розмір) повинен бути рівнимДо 2, але не, якщо я видаляю будь-яку з цих дужок ...
Антоній Заглушки

2

Їх немає. Ви, ймовірно, отримаєте поради щодо того, чи має функція побічні ефекти. Це хибно. Виправлення полягає в тому, щоб не використовувати побічні ефекти в розумній мірі, дозволеній Scala. Якщо це не вдається, то всі ставки знімаються. Усі ставки. Використання дужок є елементом набору "всі" і є зайвим. Це не надає жодної вартості, коли всі ставки будуть зняті.

Ця порада є по суті спробою системи ефектів, яка не працює (не плутати: менш корисна, ніж інші системи ефектів).

Намагайтеся не побічно діяти. Після цього прийміть, що всі ставки знищені. Сховатися за фактично синтаксичним позначенням системи ефектів може і тільки принести шкоду.


Ну, але це проблема, коли ви працюєте з гібридною OO / функціональною мовою? У будь-якому практичному прикладі ви хочете мати функції побічних ефектів ... Чи можете ви вказати на якусь інформацію про "системи ефектів"? Я думаю, що більш детальною цитатою є "Функція без параметрів не може бути оголошена без дужок; в такому випадку її потрібно викликати без дужок. Це забезпечує підтримку принципу єдиного доступу, таким чином, що абонент не знає, чи символ - це змінна або функція без параметрів. "
Антоні Стаббс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.