Ошибки валидации и захвата с использованием алгебры - PullRequest
8 голосов
/ 21 мая 2019

Я наткнулся на эту статью в среде: https://medium.com/@odomontois/tagless-unions-in-scala-2-12-55ab0100c2ff. Есть фрагмент кода, который мне трудно понять. Полный исходный код статьи можно найти здесь: https://github.com/Odomontois/zio-tagless-err.

Код такой:

trait Capture[-F[_]] {
  def continue[A](k: F[A]): A
}

object Capture {
  type Constructors[F[_]] = F[Capture[F]]

  type Arbitrary

  def apply[F[_]] = new Apply[F]

  class Apply[F[_]] {
    def apply(f: F[Arbitrary] => Arbitrary): Capture[F] = new Capture[F] {
      def continue[A](k: F[A]): A = f(k.asInstanceOf[F[Arbitrary]]).asInstanceOf[A]
    }
  }
}

Вот мои вопросы:

  • как компилятор scala решает / обрабатывает произвольный тип, если тип объявлен в объекте? Кажется, это зависит от типа параметра метода apply, но как это соотносится с тем фактом, что Capture является объектом, и вы можете иметь несколько вызовов apply с разными типами? Я наткнулся на этот пост Что означает объявление типа без определения в объекте? , но оно мне до сих пор не ясно.
  • в соответствии со статьей в приведенном выше коде используется трюк из другой библиотеки https://github.com/alexknvl. Не могли бы вы объяснить, в чем суть этого шаблона? Для чего это? Я понимаю, что автор использовал его для того, чтобы зафиксировать множество типов ошибок, которые могут возникнуть в процессе входа в систему.

Спасибо!

Обновление:

Первый вопрос:

На основании spec , когда верхняя граница отсутствует, принимается значение Any. Таким образом, Произвольный рассматривается как Любой, однако он не кажется взаимозаменяемым с Любым.

Это компилируется:

object Test {
    type Arbitrary

    def test(x: Any): Arbitrary = x.asInstanceOf[Arbitrary]
  }

однако это не так:

object Test {
   type Arbitrary

   def test(x: Any): Arbitrary = x
}

Error:(58, 35) type mismatch;
 found   : x.type (with underlying type Any)
 required: Test.Arbitrary
    def test(x: Any): Arbitrary = x

См. Также эту scala головоломку .

1 Ответ

2 голосов
/ 24 мая 2019

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

  • type X >: L <: U будет означать, что X, каким бы оно ни было, должно быть ограничено между L и G - и фактически любое значение, которое, как мы знаем, соответствует этому определению, может использоваться там,
  • type X = Y - очень точное ограничение - компилятор знает, что каждый раз, когда у нас есть Y, мы можем назвать его Y и наоборот
  • но type X также является законным. Мы обычно используем его, чтобы объявить это в trait или что-то еще, но затем мы накладываем на него больше ограничений в расширяющем классе.
    trait TestA { type X }
    trait TestB extends TestA { type X = String }
    
    однако нам не нужно указывать его для конкретного типа .

Итак код из вопроса

    def apply(f: F[Arbitrary] => Arbitrary): Capture[F] = new Capture[F] {
      def continue[A](k: F[A]): A = f(k.asInstanceOf[F[Arbitrary]]).asInstanceOf[A]
    }

можно прочитать как: у нас есть Arbitrary тип, о котором мы ничего не знаем, но мы знаем, что если мы поместим F[Arbitrary] в функцию, мы получим Arbitrary.

Дело в том, что компилятор не позволит вам передать любое значение как Arbitrary, поскольку он не может доказать, что ваше значение относится к этому типу. Если бы это могло доказать, что Arbitrary=A вы могли бы просто написать:

    def apply(f: F[Arbitrary] => Arbitrary): Capture[F] = new Capture[F] {
      def continue[A](k: F[A]): A = f(k)
    }

Однако, это невозможно, поэтому вы вынуждены использовать .asInstanceOf. Вот почему type X не равносильно высказыванию type X = Any.

Есть причина, почему мы не просто используем дженерики. Как бы вы передали полиморфную функцию внутри? Тот, который делает F[A] => A для любого A? Одним из способов будет использование естественного преобразования (или ~> или FunctionK) из F[_] в Id[_]. Но как грязно было бы его использовать!

// no capture pattern or other utilities
new Capture[F] {
  def continue[A](fa: F[A]): A = ...
}

// using FunctionK
object Capture {

  def apply[F[_]](fk: FunctionK[F, Id]): Caputure[F] = new Capture[F] {
    def continue[A](fa: F[A]): A = fk(fa)
  }
}

Capture[F](new FunctionK[F, Id] {
  def apply[A](fa: F[A]): A = ...
})

Не приятно. Проблема в том, что вы не можете передать что-то вроде полиморфной функции (здесь [A]: F[A] => A). Вы можете передать экземпляр только полиморфным методом (то есть FunctionK работает).

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

Capture[F](f: F[Arbitrary] => Arbitrary): Capture[F]

Затем вы заставляете компилятор думать, что он имеет тип F[A] => A, когда вы изучаете A.

f(k.asInstanceOf[F[Arbitrary]]).asInstanceOf[A]

Другая часть шаблона - это частичное применение параметров типа sort. Если вы сделали все за один раз:

object Capture {
  type Constructors[F[_]] = F[Capture[F]]

  type Arbitrary

  def apply[F[_]](f: F[Arbitrary] => Arbitrary): Capture[F] = new Capture[F] {
      def continue[A](k: F[A]): A = f(k.asInstanceOf[F[Arbitrary]]).asInstanceOf[A]
    }
}

у вас могут возникнуть проблемы, например, передача Capture.apply как нормальная функция. Вам придется делать такие вещи, как otherFunction(Capture[F](_)). Создав Apply "фабрику", мы можем разделить приложение параметров типа и передать функцию F[Arbitrary] => Arbitrary.

Короче говоря, речь идет о том, чтобы просто написать:

  • takeAsParameter(Capture[F]) и
  • Capture[F] { fa => /* a */ }
...