Как Scala преобразует классы падежей в функции? - PullRequest
2 голосов
/ 07 октября 2019

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

Рассмотрим функцию ниже

def !![B](h: Out[B] => A): In[B] = { ... }

Если я правильно понял, это полиморфный метод, который имеет параметр типа B и принимает функцию h какпараметр. Out и In - это два других класса, определенных ранее.

Затем используется эта функция, как показано ниже:

case class Q(p: boolean)(val cont: Out[R])
case class R(p: Int)

def g(c: Out[Q]) = {
  val rin = c !! Q(true)_
  ...
}

Я знаю, что каррирование используется, чтобы избежать записианнотация типа и вместо этого просто писать _. Однако я не могу понять, почему и как класс дела Q преобразуется в функцию (h) типа Out[B] => A.

РЕДАКТИРОВАТЬ 1 Обновлено! выше и определения In и Out:

abstract class In[+A] {
  def future: Future[A]
  def receive(implicit d: Duration): A = {
    Await.result[A](future, d)
  }
  def ?[B](f: A => B)(implicit d: Duration): B = {
    f(receive)
  }
}
abstract class Out[-A]{
  def promise[B <: A]: Promise[B]
  def send(msg: A): Unit = promise.success(msg)
  def !(msg: A) = send(msg)
  def create[B](): (In[B], Out[B])
}

Эти примеры кода взяты из следующей статьи: http://drops.dagstuhl.de/opus/volltexte/2016/6115/

Ответы [ 2 ]

5 голосов
/ 07 октября 2019

TLDR;

Использование класса case с несколькими списками параметров и частичное его применение приведет к частично примененному apply расширению call + eta, которое преобразует метод в значение функции:

val res: Out[Q] => Q = Q.apply(true) _

Более подробное объяснение

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

Классы case в Scala представляют собой компактный способ представления данных. Когда вы определяете класс case, вы получаете набор удобных методов, которые создаются для вас компилятором, например hashCode и equals.

Кроме того, компилятор также генерирует метод с именем apply, который позволяет создавать экземпляр класса case без использования ключевого слова new:

case class X(a: Int)

val x = X(1)

Компилятор расширит этот вызов до

val x = X.apply(1)

Произойдет то же самоес вашим классом case только то, что ваш класс case имеет несколько списков аргументов:

case class Q(p: boolean)(val cont: Out[R])

val q: Q = Q(true)(new Out[Int] { })

будет переведен в

val q: Q = Q.apply(true)(new Out[Int] { })

Кроме того, в Scala есть способ преобразования методов,которые являются ненулевым типом , в тип функции, который имеет тип FunctionX, где X является арностью функции. Чтобы преобразовать метод в значение функции, мы используем трюк с именем eta extension , где мы вызываем метод с подчеркиванием.

def foo(i: Int): Int = i

val f: Int => Int = foo _

Это преобразует метод fooв значение функции типа Function1[Int, Int].

Теперь, когда мы обладаем этими знаниями, давайте вернемся к вашему примеру:

val rin = c !! Q(true) _

Если мы просто изолируем Q здесь, этот вызов переводитсяinto:

val rin = Q.apply(true) _

Поскольку метод apply каррируется с несколькими списками аргументов, мы вернем функцию, которая получила Out[Q], создаст Q:

val rin: Out[R] => Q = Q.apply(true) _
2 голосов
/ 10 октября 2019

Я не могу понять, почему и как класс дела Q преобразуется в функцию (h) типа Out[B] => A.

Это не так. На самом деле, case class Q не имеет к этому никакого отношения! Это все о object Q, который является сопутствующим модулем для case class Q.

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

Т.е. когдавы пишете

case class Foo(bar: Baz)(quux: Corge)

Вы получаете не только автоматически определяемые удобные методы класса дел, такие как средства доступа для всех элементов , toString, hashCode, copy и equals, но вы также получаете автоматически определенный сопутствующий модуль, который служит как экстрактором для сопоставления с образцом, так и фабрикой для построения объекта:

object Foo {
  def apply(bar: Baz)(quux: Corge) = new Foo(bar)(quux)
  def unapply(that: Foo): Option[Baz] = ???
}

В Scala apply - это метод, который позволяет вамдля создания «функциональных» объектов: если foo является объектом (а не методом), то foo(bar, baz) переводится в foo.apply(bar, baz).

Последний фрагментзагадка η-expansion , который поднимает метод (который не является объектом) в функцию (которая является объектом и, таким образом, может быть переданав качестве аргумента, хранится в переменной и т. д.) Существует две формы η-расширения: явное η-расширение с использованием оператора _:

val printFunction = println _

И неявное η-расширение: в случаях, когда Scala на 100% знает, что вы имеете в виду функцию, но вы даете ей имя метода, Scala выполнит η-расширение для вас:

Seq(1, 2, 3) foreach println

Ивы уже знаете о карри.

Итак, если мы соберем все вместе:

Q(true)_

Во-первых, мы знаем, что Q здесь не может быть классомQ. Откуда мы это знаем? Поскольку Q здесь используется как значение , но классы являются типами , и, как и большинство языков программирования, Scala имеет строгое разделение между типами и значениями. Следовательно, Q должно быть значением. В частности, поскольку мы знаем, что класс Q является классом case, объект Q является сопутствующим модулем для класса Q.

Во-вторых, мы знаем, что для значения Q

Q(true)

является синтаксическим сахаром для

Q.apply(true)

В-третьих, мы знаем, что для классов case у сопутствующего модуля есть автоматически сгенерированный метод apply, который соответствует первичному конструктору, поэтому мы знаем, что Q.apply имеет два списка параметров.

Итак, наконец, у нас есть

Q.apply(true) _

, который передает первый список аргументов в Q.apply и затем поднимает Q.apply в функцию, которая принимает второйсписок аргументов.

Обратите внимание, что классы дел с несколькими списками параметров необычны, поскольку только параметры в первом списке параметров считаются элементами класса дел, и только элементы получают выгоду от "case class magic ", т.е. только элементы получают средства доступа, реализованные автоматически, только элементы используются в сигнатуре метода copy, только элементы используются в автоматесгенерированные equals, hashCode, toString() методы и т. д.

...