Skip to content

Instantly share code, notes, and snippets.

@julienrf
Last active November 18, 2015 08:14
Show Gist options
  • Save julienrf/dd96f5c58af391c07df2 to your computer and use it in GitHub Desktop.
Save julienrf/dd96f5c58af391c07df2 to your computer and use it in GitHub Desktop.
Yet another Scala enumeration-like tool powered by shapeless. Inspired by https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/enum.scala
/** A typeclass giving the values of an enumeration */
@implicitNotFound("Unable to find values of ${A}. Make sure it is a sealed trait and is only extended by case objects.")
class Values[A](val values: Set[A])
/**
* The companion object contains the machinery to automatically derive the values of a sealed trait
* extended by case objects only.
*
* Basically, the derivation process is the following:
* - we are given a sort of list containing the types of the case objects that extend a sealed trait `A` ;
* - we inductively traverse this structure using two implicit definitions: one for the empty list case and
* one for the “cons” case ;
* - we accumulate the traversed case objects in a `List` ;
* - that’s it: the resulting `List` contains all the possible values of type `A`.
*
* The first step is the hard part of this process and is actually achieved by shapeless (using macros).
*/
object Values {
/**
* @return All the possible values of `A`
*/
@inline def apply[A](implicit values: Values[A]): Values[A] = values
/**
* Derives a `Values[A]` instance given a representation `Repr` of type `A` in terms of `Coproduct`, and a given
* a `ValuesAux[A, Repr]` instance.
*
* @tparam Repr Type of the representation of `A` as a `Coproduct`.
* A `Coproduct` is recursively defined as either `CNil` or `H :+: T`, where `T` is a
* subtype of `Coproduct`.
* For instance, a sealed trait that is extended by only one case object `Foo` can have the following
* representation: `Foo.type :+: CNil`.
* @param gen Isomorphism between `A` and `Repr`. Shapeless is able to provide such an implicit value for any
* sealed type
*/
implicit def derived[A, Repr <: Coproduct](implicit gen: Generic.Aux[A, Repr], v: ValuesAux[A, Repr]): Values[A] =
new Values[A](v.values.to[Set])
/**
* An intermediate data structure that carries both the type `A` and its representation `Repr`.
*
* @tparam Repr Phantom type describing the structure of `A`
*/
case class ValuesAux[A, Repr](values: List[A])
/**
* To sum up: we are given the types of the possible `A` values in a `Coproduct` structure and we want to
* define how to traverse this structure to accumulate all the values in a `List`.
*/
object ValuesAux {
/**
* Base case: no values
*/
implicit def cnil[A]: ValuesAux[A, CNil] = ValuesAux[A, CNil](Nil)
/**
* Induction case: append a value of type `L` to the `R` previous values
* @param l Singleton of type `L`
*/
implicit def ccons[A, L <: A, R <: Coproduct](implicit l: Witness.Aux[L], r: ValuesAux[A, R]): ValuesAux[A, L :+: R] =
ValuesAux[A, L :+: R](l.value :: r.values)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment