2

Let's say I have the following trait

trait Named {
  def name: String
}

and the following Algebraic Data Type

sealed trait Animal extends Named

case object Dog extends Animal {
  override val name: String = "dog man"
}

case object Cat extends Animal {
  override val name: String = "cat man"
}

case object Owl extends Animal {
  override val name: String = "I am an owl left in the dark"
}

Now, I can deserialize an instance of string into my Animal ADT with the following method.

object Animal {

  def apply(name: String): Animal = name match {
    case Dog.name => Dog
    case Cat.name => Cat
  }
}

@oxbow_lakes mentions at the end of his answer that:

Can't instantiate easily from persisted value. This is also true but, except in the case of huge enumerations (for example, all currencies), this doesn't present a huge overhead.

I find that the fact that when you add a new value it needs to be added to the deserialization code explicitly as error prone (I thought that the compiler would warn me of an in-exhaustive match, but take a look at Owl above and the apply method - there was no warning issued...)

Is there no better way? (If not with the standard scala toolset, a third party one?)

Community
  • 1
  • 1
Yaneeve
  • 4,751
  • 10
  • 49
  • 87
  • Todo for me, answer my own question. Found a solution based on two posts: http://stackoverflow.com/questions/25838411/cant-prove-that-singleton-types-are-singleton-types-while-generating-type-class and http://stackoverflow.com/questions/43650265/implicit-arguments-how-to-encode-in-function-signature summed up as: http://stackoverflow.com/a/43658710/101715 – Yaneeve May 07 '17 at 05:58

2 Answers2

4

This problem already solved by enumeratum library: https://github.com/lloydmeta/enumeratum

Your code could be written like this:

import enumeratum._
import enumeratum.EnumEntry.Lowercase

sealed trait Animal extends EnumEntry with Lowercase
object Animal extends Enum[Animal] {
  val values = findValues

  case object Dog extends Animal
  case object Cat extends Animal
  case object Owl extends Animal
}

val dogName = Animal.Dog.entryName
val dog = Animal.withNameInsensitive(dogName)
user1303559
  • 436
  • 3
  • 10
3

One thing you could try is to use reflection to obtain the set of types that extend Animal, then use that to create a Map[String,Animal] using name to lookup object values, then use the map in your Animal.apply function.

Refer to this question for more information on obtaining the Animal subclasses.

Community
  • 1
  • 1
Mike Allen
  • 8,139
  • 2
  • 24
  • 46
  • It would work but I would prefer a way to do so without reflection, if possible – Yaneeve Apr 27 '17 at 17:11
  • Just out of curiosity, why would you prefer to avoid reflection? You'd only need to use it once to setup the `String` to `Animal` map, so it wouldn't affect performance, and it wouldn't miss any `Animal` subclasses. – Mike Allen Apr 27 '17 at 17:20
  • Obviously your assessment is correct concerning correctness and efficiency. I think that the reason that I would feel better with a compile time solution is that it seems to me that there should be a way to do it. `sealed traits` tell the compiler that there will not be any runtime extension, meaning that all known direct sub types are known in advance. I think that this knowledge should be leverageable. I don't know how to do it, that is why I posted the question. I wonder if what I wish could be done with shapeless, or macros. IMHO, Generally speaking, what can be done in compile time should – Yaneeve Apr 30 '17 at 05:57
  • 1
    You can use macros with reflection to achieve this at compile-time. This is essentially what the `enumeratum` library does. The downside with macros is that they have to be in a separate (sub)project. That is, if you're using _SBT_, you would create one subproject that defines macros (to query sealed types and generate `set`s containing all subclasses), and another subproject (that depends upon the macro subproject) that contains the case classes and stored lookup map. That shouldn't be too difficult. I've only used the `HList` feature of `Shapeless`, so I can't comment on how that might help. – Mike Allen Apr 30 '17 at 16:37