Понимание последствий Scala

Читая Функциональное программирование на Scala Кьюзано и Бьярнасон, я обнаружил следующий код в главе 9, Комбинаторы синтаксического анализатора:

trait Parsers[ParseError, Parser[+_]] { self =>
  ...
  def or[A](s1: Parser[A], s2: Parser[A]): Parser[A]
  implicit def string(s: String): Parser[String]
  implicit def operators[A](p: Parser[A]) = ParserOps[A](p)
  implicit def asStringParser[A](a: A)(implicit f: A => Parser[String]):
    ParserOps[String] = ParserOps(f(a))

  case class ParserOps[A](p: Parser[A]) {
    def |[B>:A](p2: Parser[B]): Parser[B] = self.or(p,p2)
    def or[B>:A](p2: => Parser[B]): Parser[B] = self.or(p,p2)
  }
}

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

Если строка встречается там, где требуется Parser[String], должна быть вызвана строковая функция в приведенном выше трейте для преобразования строки в Parser[String].

Однако мне сложно понять функции operators и asStringParser. Вот вопросы, которые у меня есть:

  1. Почему для функции неявных операторов нет возвращаемого типа?
  2. Почему ParserOps определен как case class и почему функция | или or не может быть определена в самом типе Parsers?
  3. Чего именно asStringParser пытается достичь? Какова его цель здесь?
  4. Зачем нужен self? В книге говорится: «Используйте self, чтобы явно исключить неоднозначность ссылки на метод or в этой характеристике», но что это означает?

Мне действительно нравится книга, но использование в этой главе сложных языковых конструкций препятствует моему прогрессу. Было бы очень полезно, если бы вы объяснили мне, как работает этот код. Я понимаю, что цель состоит в том, чтобы сделать библиотеку «удобнее» для использования с помощью таких операторов, как | и or, но не понимаю, как это делается.


person Mohideen Imran Khan    schedule 13.04.2018    source источник
comment
1) Это называется выводом типа и является фундаментальной особенностью практически всех языков программирования, изобретенных после 1973 года. 4) Потому что вы хотите вызывать or в Parsers, а не в ParserOps.   -  person Jörg W Mittag    schedule 13.04.2018


Ответы (1)


  1. У каждого метода есть возвращаемый тип. В данном случае это ParserOps[A]. Вам не нужно записывать это явно, потому что в этом случае это может быть выведено автоматически.
  2. Вероятно, из-за автоматически предоставляемого метода ParserOps.apply-factory в сопутствующем объекте. Вам нужно меньше val в конструкторе, и вам не нужно ключевое слово new для создания экземпляра ParserOps. Однако он не используется в сопоставлении с образцом, поэтому вы могли бы сделать то же самое с обычным (не case) классом, это не имело бы значения.
  3. Это шаблон "сутенер-моя-библиотека". Он присоединяет методы | и or к Parser, не заставляя Parser наследовать от чего-либо. Таким образом, вы можете позже объявить Parser чем-то вроде ParserState => Result[A], но у вас по-прежнему будут доступны методы | и or (даже если у Function1[ParserState, Result[A]] их нет).
  4. Вы можете поместить | и or прямо в Parsers, но тогда вам придется использовать синтаксис

    |(a, b)
    or(a, b)
    

    вместо более приятного

    a | b
    a or b
    

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

person Andrey Tyukin    schedule 13.04.2018
comment
Отличные ответы - я застрял на этой главе книги, но это объяснение имеет большой смысл. Спасибо! - person Grisha; 13.08.2018
comment
... но я до сих пор не совсем понимаю, почему asStringParser принимает произвольный тип A плюс неявное преобразование его в синтаксический анализатор строк. - person Grisha; 13.08.2018
comment
@Grisha Часть A => Parser[String] существует для того, чтобы, например, неявный метод мог принимать A = String в качестве аргумента, затем превращать его в Parser[String] с помощью string, а затем сразу же превращать его в ParserOps, так что вы эффективно получаете операции синтаксического анализатора-комбинатора на Stringс. Это называется цепочкой неявных преобразований. Взгляните на этот FAQ, особенно на форму преобразование bToC во втором блоке кода. То же самое с B1 = A, B = Parser[String], C = ParserOps[String]. - person Andrey Tyukin; 13.08.2018
comment
Спасибо! Стараются ли они оставаться максимально гибкими, A ожидая, что это может быть что-то еще String, пока существует неявное A => Parser[String] преобразование? Я не могу представить себе вариант использования этого. - person Grisha; 13.08.2018
comment
@Grisha Как насчет комбинации Strings or скомпилированных Regex паттернов or, например, более сложных композиций общих Parser[X]s? - person Andrey Tyukin; 13.08.2018
comment
небольшое дополнение: похоже, что Scala избегает связывания более одного неявного преобразования, за исключением случаев, когда для самого преобразования требуется неявный параметр. Таким образом, простая цепочка String->string()->Parser[String]->operators()->ParserOps[String] не будет работать, следовательно asStringParser() - person Grisha; 14.08.2018