Shapeless 的“分配财产”

eit*_*rra 2 scala cartesian-product hlist shapeless coproduct

不确定正确的术语是否是“分配财产”,但我记得在学校学过这个,所以这里是我想做的一个例子:

鉴于:

type MyHList = (A :+: B :+: C :+: CNil) :: (Foo :+: Bar :+: CNil) :: HNil
Run Code Online (Sandbox Code Playgroud)

Shapeless 中是否有任何内置类型类可以解决这个问题:

type Out = (A, Foo) :+: (A, Bar) :+: (B, Foo) :+: (B, Bar) :+: (C, Foo) :+: (C, Bar) :+: CNil
Run Code Online (Sandbox Code Playgroud)

谢谢

Dmy*_*tin 5

我将这种变换称为笛卡尔张量积(即每一项乘以每一项的乘积,与内积/标量积/压缩相反)。尽管它确实与分配法有关。

我想字面上没有这样的标准类型类,但它可以通过标准类型类来表达

import shapeless.{:+:, ::, CNil, Coproduct, HList, HNil, Poly1, poly}
import shapeless.ops.coproduct.{FlatMap, Mapper}

trait Cartesian[L <: HList] {
  type Out <: Coproduct
}
object Cartesian {
  type Aux[L <: HList, Out0 <: Coproduct] = Cartesian[L] { type Out = Out0 }

  implicit def mkCartesian[C <: Coproduct, C1 <: Coproduct](implicit
    flatMap: FlatMap[C, MapperPoly[C1]]
  ): Aux[C :: C1 :: HNil, flatMap.Out] = null

  trait MapperPoly[C <: Coproduct] extends Poly1
  object MapperPoly {
    implicit def cse[C <: Coproduct, A](implicit
      mapper: Mapper[TuplePoly[A], C]
    ): poly.Case1.Aux[MapperPoly[C], A, mapper.Out] = null
  }

  trait TuplePoly[A] extends Poly1
  object TuplePoly {
    implicit def cse[A, B]: poly.Case1.Aux[TuplePoly[A], B, (A, B)] = null
  }
}

implicitly[Cartesian.Aux[MyHList, Out]] // compiles
Run Code Online (Sandbox Code Playgroud)

类型类Cartesian现在仅作用于类型级别。在值级别上,其定义可能会有点棘手(使用poly.Case1.Aux[P, ...for P <: MapperPoly[C]poly.Case1.Aux[P, ...forP <: TuplePoly[A]而不是 和poly.Case1.Aux[MapperPoly[C], ...using poly.Case1.Aux[TuplePoly[A], ...Unpack1请参阅使用超类型过滤 HList)。更新:或者也许不是:)

此外,始终可以选择递归地定义自定义类型类,而不是尝试将所有内容推导为标准类型类。


HList这是多个 s的递归类型级实现Coproduct(不一定是两个)

// transforms an hlist of coproducts into a coproduct of tuples
trait Cartesian[L <: HList] {
  type Out <: Coproduct
}
object Cartesian {
  type Aux[L <: HList, Out0 <: Coproduct] = Cartesian[L] { type Out = Out0 }

  implicit def mkCartesian[L <: HList, C <: Coproduct](implicit
    cartesian: CartesianHelper.Aux[L, C],
    mapper: coproduct.Mapper[tuplerPoly.type, C]
  ): Aux[L, mapper.Out] = null

  object tuplerPoly extends Poly1 {
    implicit def cse[L <: HList](implicit
      tupler: hlist.Tupler[L]
    ): Case.Aux[L, tupler.Out] = null
  }
}

// transforms an hlist of coproducts into a coproduct of hlists
trait CartesianHelper[L <: HList] {
  type Out <: Coproduct
}
trait LowPriorityHelper1 {
  type Aux[L <: HList, Out0 <: Coproduct] = CartesianHelper[L] { type Out = Out0 }

  // (a + (a1+...)) * (b1+...) * (c1+...) * ... 
  //  = a * ((b1+...) * (c1+...) * ...) 
  //  + ((a1+...) * (b1+...) * (c1+...) * ...)
  implicit def recurse[H, T <: Coproduct, T1 <: HList,
    C <: Coproduct, C1 <: Coproduct, C2 <: Coproduct](implicit
    ev: T1 <:< (_ :: _),
    cartesian: Aux[T1, C],
    mapper: coproduct.Mapper.Aux[PrependPoly[H], C, C1],
    cartesian1: Aux[T :: T1, C2],
    extendBy: coproduct.ExtendBy[C1, C2]
  ): Aux[(H :+: T) :: T1, extendBy.Out] = null

  trait PrependPoly[H] extends Poly1
  object PrependPoly {
    implicit def cse[H, L <: HList]: poly.Case1.Aux[PrependPoly[H], L, H :: L] = null
  }
}
trait LowPriorityHelper extends LowPriorityHelper1 {
  implicit def one[C <: Coproduct](implicit 
    mapper: coproduct.Mapper[prependPoly.type, C]
  ): Aux[C :: HNil, mapper.Out] = null

  object prependPoly extends Poly1 {
    implicit def cse[A]: Case.Aux[A, A :: HNil] = null
  }
}
object CartesianHelper extends LowPriorityHelper {
  implicit def hnil: Aux[HNil, CNil] = null
  implicit def cnil[T <: HList]: Aux[CNil :: T, CNil] = null
}

type MyHList1 = (A :+: B :+: C :+: CNil) :: (Foo :+: Bar :+: CNil) :: (X :+: Y :+: CNil) :: HNil
type Out1 = (A, Foo, X) :+: (A, Foo, Y) :+: (A, Bar, X) :+: (A, Bar, Y) :+: (B, Foo, X) :+: (B, Foo, Y) :+:
  (B, Bar, X) :+: (B, Bar, Y) :+: (C, Foo, X) :+: (C, Foo, Y) :+:  (C, Bar, X) :+: (C, Bar, Y) :+: CNil
implicitly[Cartesian.Aux[MyHList1, Out1]] // compiles
Run Code Online (Sandbox Code Playgroud)

增值水平:

def cartesian[L <: HList](l: L)(implicit cart: Cartesian[L]): cart.Out = cart(l)

trait Cartesian[L <: HList] extends DepFn1[L] {
  type Out <: Coproduct
}
object Cartesian {
  type Aux[L <: HList, Out0 <: Coproduct] = Cartesian[L] { type Out = Out0 }
  def instance[L <: HList, Out0 <: Coproduct](f: L => Out0): Aux[L, Out0] =
    new Cartesian[L] {
      override type Out = Out0
      override def apply(l: L): Out0 = f(l)
    }

  implicit def mkCartesian[L <: HList, C <: Coproduct](implicit
    cartesian: CartesianHelper.Aux[L, C],
    mapper: coproduct.Mapper[tuplerPoly.type, C]
  ): Aux[L, mapper.Out] = instance(l => mapper(cartesian(l)))

  object tuplerPoly extends Poly1 {
    implicit def cse[L <: HList](implicit
      tupler: hlist.Tupler[L]
    ): Case.Aux[L, tupler.Out] = at(tupler(_))
  }
}

trait CartesianHelper[L <: HList] extends DepFn1[L] {
  type Out <: Coproduct
}
trait LowPriorityHelper1 {
  type Aux[L <: HList, Out0 <: Coproduct] = CartesianHelper[L] { type Out = Out0 }
  def instance[L <: HList, Out0 <: Coproduct](f: L => Out0): Aux[L, Out0] =
    new CartesianHelper[L] {
      override type Out = Out0
      override def apply(l: L): Out0 = f(l)
    }

  implicit def recurse[H, T <: Coproduct, T1 <: HList,
    C <: Coproduct, C1 <: Coproduct, C2 <: Coproduct](implicit
    ev: T1 <:< (_ :: _),
    cartesian: Aux[T1, C],
    prepend: Prepend.Aux[H, C, C1],
    cartesian1: Aux[T :: T1, C2],
    extendBy: coproduct.ExtendBy[C1, C2]
  ): Aux[(H :+: T) :: T1, extendBy.Out] =
    instance(l => {
      val t1 = l.tail
      val c = cartesian(t1)
      l.head.eliminate(h => {
        val c1 = prepend(h, c)
        extendBy.right(c1)
      }, t => {
        val c2 = cartesian1(t :: t1)
        extendBy.left(c2)
      })
    })

  // custom type class instead of mapping with a generic Poly
  trait Prepend[H, C <: Coproduct] extends DepFn2[H, C] {
    type Out <: Coproduct
  }
  object Prepend {
    type Aux[H, C <: Coproduct, Out0 <: Coproduct] = Prepend[H, C] { type Out = Out0 }
    def instance[H, C <: Coproduct, Out0 <: Coproduct](f: (H, C) => Out0): Aux[H, C, Out0] =
      new Prepend[H, C] {
        override type Out = Out0
        override def apply(h: H, c: C): Out0 = f(h, c)
      }

    implicit def cnil[H]: Aux[H, CNil, CNil] = instance((_, _) => unexpected)
    implicit def ccons[H, L <: HList, C <: Coproduct](implicit
      prepend: Prepend[H, C]
    ): Aux[H, L :+: C, (H :: L) :+: prepend.Out] =
      instance((h, c) =>
        c.eliminate(
          l => Inl(h :: l),
          c => Inr(prepend(h, c))
        )
      )
  }
}
trait LowPriorityHelper extends LowPriorityHelper1 {
  implicit def one[C <: Coproduct](implicit
    mapper: coproduct.Mapper[prependPoly.type, C]
  ): Aux[C :: HNil, mapper.Out] = instance(l => mapper(l.head))

  object prependPoly extends Poly1 {
    implicit def cse[A]: Case.Aux[A, A :: HNil] = at(_ :: HNil)
  }
}
object CartesianHelper extends LowPriorityHelper {
  implicit def hnil: Aux[HNil, CNil] = instance(_ => unexpected)
  implicit def cnil[T <: HList]: Aux[CNil :: T, CNil] = instance(_ => unexpected)
}

val c: C = new C {}
val bar: Bar = new Bar {}
val myHList: MyHList = Inr(Inr(Inl(c))) :: Inr(Inl(bar)) :: HNil
val res = cartesian(myHList)
res: Out // compiles
res == Inr(Inr(Inr(Inr(Inr(Inl((c, bar))))))) // true
Run Code Online (Sandbox Code Playgroud)

PrependPoly[H]我用自定义类型类替换了映射余积Prepend[H, C <: Coproduct],因为泛型Poly很棘手,而且并不是所有事情都可以在值级别上用它们完成。

问题#198:将值注入到调用方法外部定义的 Poly 中很尴尬

问题#154:改进对 Polys 部分应用的支持

将额外的参数传递给多态函数?

选出列表 HList 的第 N 个元素,并将该值作为值的 HList 返回

在无形状状态下动态参数化 Poly1 函数

shapeless-dev:如何“参数化”多边形函数?

需要HList的HList折叠函数

对列表的无形状 Hlist 中的元素进行参数化过滤


也可以看看:

获取 Seq[_] 的 HList 并使用值的笛卡尔积生成 Seq[HList]

异构列表的笛卡尔积(Haskell)