Валидация и фиксация ошибок с использованием алгебры

Я наткнулся на эту статью в среде: 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. Не могли бы вы объяснить, в чем идея этого шаблона? Для чего это? Я понимаю, что автор использовал его для того, чтобы зафиксировать множество типов ошибок, которые могут возникнуть в процессе входа в систему.

Спасибо!

Обновить:

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

На основании спецификации, когда верхняя граница отсутствует, предполагается, что она является Любой. Таким образом, Arbitrary рассматривается как Any, однако он не кажется взаимозаменяемым с 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

Смотрите также эту головоломку скала.

1 ответ

Решение

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

  • 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 */ }
Другие вопросы по тегам