Валидация и фиксация ошибок с использованием алгебры
Я наткнулся на эту статью в среде: 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 */ }