lets say I have the following class hierarchy:
sealed trait Animal
case class Cat(isFriendly: Boolean) extends Animal
case class Dog(color: String) extends Animal
case class Fish(isFreshWater: Boolean) extends Animal
Now I have an instance of type
Option[Cat] :: Option[Dog] :: Option[Fish] :: HNil
But there is a restriction on the instance. It can only be of one of the following forms
Some(Cat(???)) :: None :: None :: HNil
or
None :: Some(Dog(???)) :: None :: HNil
or
None :: None :: Some(Fish(???)) :: HNil
First, excuse any incoherence - it is part of a larger problem that I am trying to solve that is not yet well articulated
Second, the ???
is just my contrived place holder for real instance such as:
None :: Some(Dog(brown)) :: None :: HNil
Thing is, I am rather new to shapeless and I don't exactly know if the value of the ???
makes a difference.
Onwards to the question
Is there a way to "iterate" over the HList and extract the Some
?
I understand that when speaking generically it is not possible as shown in the following two questions. But I wonder whether adding the restrictions I set above would make a difference
https://stackoverflow.com/a/28598157/101715
https://stackoverflow.com/a/29572541/101715
As explained in the link you gave, such an operation is only possible on HList
if your values are statically typed as Some
and None
, so that the compiler can do anything about it.
If you have additional information that what the type gives (here, the fact that exactly one of the options can be a Some
), it means that you're using the wrong type, since types are the information you have on values at compile time. In this case, the type you should use is Coproduct
:
type AnimalCoproduct = Cat :+: Dog :+: Fish :+: CNil
val dog = Coproduct[AnimalCoproduct](Dog("brown"))
Now, back to your question, assuming you know which are None
and which are Some
at compile time.
First, you need to check which HList
have the property that they are a list of 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] {}
}
So now, if there is an implicit IsNoneList[L]
in scope, it means that L
is an HList
of None.type
. Let's do the same with the property we're looking for:
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)
}
}
Notice that if we have an implicit IsOneSomeHList[L]
in scope, we know that L
has the property we want, but we can also use this implicit to get the type and the value of the only Some
in the list.
EDIT
Let's give an example:
val cat = Some(Cat(isFriendly = true)) :: None :: None :: HNil
IsOneSomeHList[Some[Cat] :: None.type :: None.type :: HNil].get(cat) == Cat(true)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With