在 Hlist of Options 中无形地查找 Some among Nones 的实例

Shapeless find instance of Some among Nones in an Hlist of Options

假设我有以下 class 层次结构:

sealed trait Animal

case class Cat(isFriendly: Boolean) extends Animal
case class Dog(color: String) extends Animal
case class Fish(isFreshWater: Boolean) extends Animal

现在我有一个类型的实例

Option[Cat] :: Option[Dog] :: Option[Fish] :: HNil

但是实例有限制。只能是以下形式之一

Some(Cat(???)) :: None :: None :: HNil

None :: Some(Dog(???)) :: None :: HNil

None :: None :: Some(Fish(???)) :: HNil

首先,请原谅任何不连贯的地方——这是我试图解决的一个更大问题的一部分,但尚未得到很好的阐述

其次,??? 只是我为真实实例设计的占位符,例如:

None :: Some(Dog(brown)) :: None :: HNil

事实是,我比较 new 无形而且我不知道 ??? 有所作为。


继续提问

有没有办法 "iterate" 通过 HList 并提取 Some

我明白,在笼统地说时,这是不可能的,如以下两个问题所示。 但是我想知道添加我上面设置的限制是否会有所不同

正如您在 link 中所解释的那样,如果您的值静态类型为 SomeNone,则只有在 HList 上才能执行此类操作,因此编译器可以做任何事情。

如果您有关于类型给出的额外信息(此处,恰好有一个选项可以是 Some),则意味着您使用了错误的类型,因为类型是您在编译时获得的有关值的信息。在这种情况下,您应该使用的类型是 Coproduct:

type AnimalCoproduct = Cat :+: Dog :+: Fish :+: CNil

val dog = Coproduct[AnimalCoproduct](Dog("brown"))

现在,回到您的问题,假设您在编译时知道哪些是 None 哪些是 Some

首先,您需要检查哪些 HList 具有 属性 它们是 None.

的列表
trait IsNoneList[L <: HList]

object IsNoneList {
  //all values in an HNil are None, since there aren't any
  implicit val hnil: IsNoneList[HNil] = new IsNoneList[HNil] {}

  //if all the values in the tail are None, and the head is None, then all the values are None
  implicit def hcons[T <: HList: IsNoneList]: IsNoneList[None.type :: T] = new IsNoneList[None.type :: T] {}
}

所以现在,如果范围内有一个 implicit IsNoneList[L],则意味着 LNone.typeHList。让我们对正在寻找的 属性 做同样的事情:

trait IsOneSomeHList[L <: HList] {
  type OneSome
  def get(l: L): OneSome
}

object IsOneSomeHList {
  type Aux[L <: HList, O] = IsOneSomeHList[L] { type OneSome =  O }

  def apply[L <: HList](implicit L: IsOneSomeHList[L]) = L

  // if the tail is full of None, and the head is a Some, then the property is true
  implicit def someHead[A, T <: HList: IsNoneList]: Aux[Some[A] :: T, A] = new IsOneSomeHList[Some[A] :: T] {
    type OneSome = A
    def get(l: Some[A] :: T) = l.head.get
  }

  //if the head is None, and the tail has the property, then the HCons also has the property, with the same extraction function
  implicit def noneHead[T <: HList](implicit T: IsOneSomeHList[T]): Aux[None.type :: T, T.OneSome] = new IsOneSomeHList[None.type :: T] {
    type OneSome = T.OneSome
    override def get(l: ::[None.type, T]): T.OneSome = T.get(l.tail)
  }
}

注意,如果我们在范围内有一个 implicit IsOneSomeHList[L],我们知道 L 有我们想要的 属性,但我们也可以使用这个隐式来获取类型和列表中唯一 Some 的值。

编辑

举个例子:

val cat = Some(Cat(isFriendly = true)) :: None :: None :: HNil

IsOneSomeHList[Some[Cat] :: None.type :: None.type :: HNil].get(cat) == Cat(true)