Здесь есть два отдельных вопроса:
- Почему в некоторых классах типов Shapeless в некоторых случаях использует члены типа вместо параметров типа?
- Почему Shapeless включает
Aux
псевдонимы типов в сопутствующие объекты этих классов типов?
Я начну со второго вопроса, потому что ответ более очевиден: псевдонимы типа Aux
являются исключительно синтаксическим удобством. Вы никогда не должны их использовать. Например, предположим, что мы хотим написать метод, который будет компилироваться только при вызове с двумя списками hlists одинаковой длины:
import shapeless._, ops.hlist.Length
def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit
al: Length.Aux[A, N],
bl: Length.Aux[B, N]
) = ()
Класс типа Length
имеет один параметр типа (для типа HList
) и один член типа (для Nat
). Синтаксис Length.Aux
позволяет относительно легко ссылаться на член типа Nat
в неявном списке параметров, но это просто удобство - следующее в точности эквивалентно:
def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit
al: Length[A] { type Out = N },
bl: Length[B] { type Out = N }
) = ()
Версия Aux
имеет несколько преимуществ по сравнению с описанием уточнений типа таким образом: она менее шумная и не требует от нас запоминания имени члена типа. Однако это чисто эргономические проблемы: Aux
псевдонимы делают наш код немного легче для чтения и записи, но они не меняют то, что мы можем или не можем делать с кодом каким-либо значимым образом.
Ответ на первый вопрос немного сложнее. Во многих случаях, включая мой sameLength
, Out
не имеет преимуществ быть членом типа вместо параметра типа. Поскольку Scala не позволяет использовать несколько разделов неявных параметров, нам необходимо N
в качестве параметра типа для нашего метода, если мы хотим убедиться, что два Length
экземпляра имеют один и тот же Out
тип. В этот момент Out
на Length
может также быть параметром типа (по крайней мере, с нашей точки зрения, как с точки зрения авторов sameLength
).
В других случаях, однако, мы можем воспользоваться тем фактом, что в Shapeless иногда (я буду говорить конкретно о where чуть позже) используются члены типа вместо параметров типа. Например, предположим, что мы хотим написать метод, который будет возвращать функцию, которая преобразует указанный тип класса case в HList
:
def converter[A](implicit gen: Generic[A]): A => gen.Repr = a => gen.to(a)
Теперь мы можем использовать это так:
case class Foo(i: Int, s: String)
val fooToHList = converter[Foo]
И мы получим хороший Foo => Int :: String :: HNil
. Если бы Generic
Repr
был параметром типа, а не членом типа, нам пришлось бы вместо этого написать что-то вроде этого:
// Doesn't compile
def converter[A, R](implicit gen: Generic[A, R]): A => R = a => gen.to(a)
Scala не поддерживает частичное применение параметров типа, поэтому каждый раз, когда мы вызываем этот (гипотетический) метод, нам придется указывать оба параметра типа, поскольку мы хотим указать A
:
val fooToHList = converter[Foo, Int :: String :: HNil]
Это делает его практически бесполезным, поскольку весь смысл заключается в том, чтобы позволить универсальному механизму определить представление.
В общем, всякий раз, когда тип однозначно определяется другими параметрами класса типа, Shapeless делает его членом типа, а не параметром типа. Каждый класс case имеет одно общее представление, поэтому Generic
имеет один параметр типа (для типа класса case) и один член типа (для типа представления); каждый HList
имеет одну длину, поэтому Length
имеет один параметр типа, один член типа и т. д.
Создание уникально определенных типов элементов типа вместо параметров типа означает, что если мы хотим использовать их только как типы, зависящие от пути (как в первом converter
выше), мы можем, но если мы хотим использовать их, как если бы они были параметрами типа , мы всегда можем написать уточнение типа (или синтаксически лучшую Aux
версию). Если бы Shapeless создавал параметры типа этих типов с самого начала, было бы невозможно пойти в обратном направлении.
В качестве примечания, эта связь между типом «параметры» класса типа (я использую кавычки, поскольку они могут не быть параметрами в буквальном смысле Scala) называется " функциональная зависимость " на таких языках, как Haskell, но вы не должны чувствовать, что вам нужно что-то понимать о функциональных зависимостях в Haskell, чтобы понять, что происходит. на бесформенном.
person
Travis Brown
schedule
31.12.2015