shapeless HList 到 TupleN,其中元组形状不需要与 HList 形状完全匹配 [英] shapeless HList to TupleN where the tuple shape need not exactly match the HList shape

查看:40
本文介绍了shapeless HList 到 TupleN,其中元组形状不需要与 HList 形状完全匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建相当于:

def toTupleN[A1, ..., AN, L <: HList](l: L): TupleN[A1, ..., AN]

使用 toTupleN 的代码只有在 l 中只有一个 N 值组合可以创建元组时才应该编译.其他任何事情都应该产生编译时错误.应考虑可用的隐式转换.请注意,对 l 的大小或其中值的顺序没有限制.

Code using toTupleN should compile only when there is exactly one N combination of values in l that the tuple could be created from. Anything else should generate a compile-time error. Available implicit conversions should be taken into account. Note that there are no restrictions on the size of l or the ordering of values in it.

示例:

val l = 23 :: (1, "wibble") :: (2, "wobble") :: "foo" :: HNil
// l: shapeless.::[Int,shapeless.::[(Int, String),shapeless.::[(Int, String),shapeless.::[String,shapeless.HNil]]]] = 23 :: (1,wibble) :: (2,wobble) :: foo :: HNil

val t2: (String, Int) = toTuple2(l)
// t2: (String, Int) = (foo,23)

val nope: (String, String) = toTuple2(l)
// Compiler error because no combination of l's values can create nope

val nein: ((Int, String)) = toTuple2(l)
// Another compiler error because there is more than one way l's values can create nein

这个问题来自 回答以下问题.这个问题中更通用的机制可用于创建数据结构并使用 FunctionN#tupled 调用任何标准函数(其参数是不同类型的).​​

This question arose from the answer to the following question. The more general machinery in this question could be used to both create data structures and call any standard function (whose arguments are of different types) using FunctionN#tupled.

更新:

一些使用子类型定义所需行为的示例:

Some examples to define the desired behavior with subtypes:

trait A
trait B extends A
trait C extends A

val a: A
val b: B
val c: C

toTuple2[(A, Int)](5 :: b :: HNil)      // (b, 5): subtypes match supertypes when there is no exact match
toTuple2[(A, Int)](5 :: b :: a :: HNil) // (a, 5): only one exact match is available
toTuple2[(A, Int)](5 :: a :: a :: HNil) // compile error: more than one exact match is available
toTuple2[(A, Int)](5 :: b :: c :: HNil) // compile error: more than one inexact match is available

推荐答案

我无法按照您想要的方式进行目标类型推断,但作为补偿,我已经通过 shapeless 将其推广到任意产品类型通用,

I haven't been able to make target type inference work out quite the way you wanted, but as compensation I've generalized to an arbitrary product type via shapeless's Generic,

import shapeless._, ops.hlist._, test._

object Demo {
  trait UniqueSelect[L <: HList, M <: HList] {
    def apply(l: L): M
  }

  object UniqueSelect {
    implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
      new UniqueSelect[L, HNil] {
        def apply(l: L): HNil = HNil
      }

    implicit def hcons[L <: HList, H, T <: HList, S <: HList]
      (implicit
        pt: Partition.Aux[L, H, H :: HNil, S],
        ust: UniqueSelect[S, T]
      ): UniqueSelect[L, H :: T] =
      new UniqueSelect[L, H :: T] {
        def apply(l: L): H :: T = {
          val (h :: HNil, s) = pt(l)
          h :: ust(s)
        }
      }
  }

  def toProductUniquely[P <: Product] = new ToProductUniquely[P]
  class ToProductUniquely[P <: Product] {
    def apply[L <: HList, M <: HList](l: L)
      (implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P =
        gen.from(up(l))
  }

  val l = 23 :: (1, "wibble") :: (2, "wobble") :: "foo" :: HNil

  val t2 = toProductUniquely[(String, Int)](l)
  typed[(String, Int)](t2)
  assert(t2 == ("foo", 23))

  illTyped("""
  toProductUniquely[(String, String)](l)
  """)

  illTyped("""
  toProductUniquely[Tuple1[(Int, String)]](l)
  """)
}

更新 1

如果我们说我们有类型 AB <: A ,那么添加对被请求类型的子类型满足的选择的支持是相当简单的从 A :: B :: HNil 中选择 A 是不明确的,因为两个元素都符合 A.这可以通过在 hcons 的先前定义中向见证人添加一个 SubtypeUnifier 来完成,

Update 1

Adding support for the selection being satisfied by subtypes of the requested types is fairly straightforward if we say that where we have types A and B <: A then the selection of A from A :: B :: HNil is ambiguous because both elements conform to A. This can be done by adding a SubtypeUnifier to the witnesses in the previous definition of hcons,

import shapeless._, ops.hlist._, test._

object Demo extends App {
  trait UniqueSelect[L <: HList, M <: HList] {
    def apply(l: L): M
  }

  object UniqueSelect {
    implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
      new UniqueSelect[L, HNil] {
        def apply(l: L): HNil = HNil
      }

    implicit def hcons[L <: HList, M <: HList, H, T <: HList, S <: HList]
      (implicit
        su: SubtypeUnifier.Aux[L, H, M],
        pt: Partition.Aux[M, H, H :: HNil, S],
        upt: UniqueSelect[S, T]
      ): UniqueSelect[L, H :: T] =
      new UniqueSelect[L, H :: T] {
        def apply(l: L): H :: T = {
          val (h :: HNil, s) = pt(su(l))
          h :: upt(s)
        }
      }
  }

  def toProductUniquely[P <: Product] = new ToProductUniquely[P]
  class ToProductUniquely[P <: Product] {
    def apply[L <: HList, M <: HList](l: L)
      (implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P =
        gen.from(up(l))
  }

  class A
  class B extends A
  class C

  val ac = new A :: new C :: HNil
  val bc = new B :: new C :: HNil
  val abc = new A :: new B :: new C :: HNil

  // Exact match
  val tac = toProductUniquely[(A, C)](ac)
  typed[(A, C)](tac)

  // Subtype
  val tbc = toProductUniquely[(A, C)](bc)
  typed[(A, C)](tbc)

  // Exact match again
  val tabc = toProductUniquely[(B, C)](abc)
  typed[(B, C)](tabc)

  // Ambiguous due to both elements conforming to A
  illTyped("""
  toProductUniquely[(A, C)](abc)
  """)
}

更新 2

我们还可以提供统一语义,该语义优先选择精确匹配,然后回退到您更新的问题中所述的唯一子类型.我们通过组合上述两个解决方案中的实例来做到这一点:第一个正常优先级的完全匹配实例和低优先级的子类型匹配实例,

Update 2

We can also accommodate a unification semantics which gives preference to exact match and then falls back to a unique subtype as described in your updated question. We do this by combining the instances from the two solutions above: the exact match instance from the first at normal priority and the subtype match instance at low priority,

import shapeless._, ops.hlist._, test._

object Demo extends App {
  trait UniqueSelect[L <: HList, M <: HList] {
    def apply(l: L): M
  }

  object UniqueSelect extends UniqueSelect0 {
    implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
      new UniqueSelect[L, HNil] {
        def apply(l: L): HNil = HNil
      }

    implicit def hconsExact[L <: HList, H, T <: HList, S <: HList]
      (implicit
        pt: Partition.Aux[L, H, H :: HNil, S],
        upt: UniqueSelect[S, T]
      ): UniqueSelect[L, H :: T] =
      new UniqueSelect[L, H :: T] {
        def apply(l: L): H :: T = {
          val (h :: HNil, s) = pt(l)
          h :: upt(s)
        }
      }
  }

  trait UniqueSelect0 {
    implicit def hconsSubtype[L <: HList, M <: HList, H, T <: HList, S <: HList]
      (implicit
        su: SubtypeUnifier.Aux[L, H, M],
        pt: Partition.Aux[M, H, H :: HNil, S],
        upt: UniqueSelect[S, T]
      ): UniqueSelect[L, H :: T] =
      new UniqueSelect[L, H :: T] {
        def apply(l: L): H :: T = {
          val (h :: HNil, s) = pt(su(l))
          h :: upt(s)
        }
      }
  }

  def toProductUniquely[P <: Product] = new ToProductUniquely[P]
  class ToProductUniquely[P <: Product] {
    def apply[L <: HList, M <: HList](l: L)
      (implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P = gen.from(up(l))
  }

  trait A
  trait B extends A
  trait C extends A

  val a: A = new A {}
  val b: B = new B {}
  val c: C = new C {}

  // (b, 5): subtypes match supertypes when there is no exact match
  toProductUniquely[(A, Int)](5 :: b :: HNil)

  // (a, 5): only one exact match is available
  toProductUniquely[(A, Int)](5 :: b :: a :: HNil)

  // compile error: more than one exact match is available
  illTyped("""
  toProductUniquely[(A, Int)](5 :: a :: a :: HNil)
  """)

  // compile error: more than one inexact match is available
  illTyped("""
  toProductUniquely[(A, Int)](5 :: b :: c :: HNil)
  """)
}

这篇关于shapeless HList 到 TupleN,其中元组形状不需要与 HList 形状完全匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆