I have a piece of code that matches on a case class and then invokes a function based on the type and an extracted value:
def resolveByType[T](ids: List[Any]) = ids map (id => resolver.resolve[T](id))
override def resolve(deferred: Vector[Deferred[Any]], ctx: Any): Vector[Future[Any]] = deferred flatMap {
case DeferAccounts(ids) => resolveByType[Account](ids)
case DeferLocations(ids) => resolveByType[Location](ids)
case DeferDelegates(ids) => resolveByType[EconomicUnit](ids)
case DeferMUs(ids) => resolveByType[MunicipalUnit](ids)
case _ =>
List(Future.fromTry(Try(List[Any]())))
}
The case classes are defined as follows
case class DeferAccounts(accounts: List[String]) extends Deferred[List[Option[Account]]]
case class DeferLocations(loctions: List[String]) extends Deferred[List[Option[Location]]]
case class DeferDelegates(delegates: List[String]) extends Deferred[List[Option[EconomicUnit]]]
case class DeferMUs(delegates: List[String]) extends Deferred[List[Option[MunicipalUnit]]]
As you can see, all I need is that inner type parameter and the list. I'm trying to figure out the most scala-y way to do this. Seems like a lot of boilerplate code right now. Is there a clean way to extract the list and type parameter programmatically instead of declaratively, as it's being done now, or is this the correct way to do this?
Related
I'm designing a typesafe api to work with a "types" -- an abstraction in the application I'm working on. Here is how it looks like:
sealed trait EnumType
case object A extends EnumType
case object B extends EnumType
case object C extends EnumType
sealed abstract class TypeInfo[T <: EnumType](val enumType: T)
case class Ainfo() extends TypeInfo(A)
case class Binfo() extends TypeInfo(B)
case class Cinfo() extends TypeInfo(C)
sealed trait TypeMeta[T <: EnumType]
case class Ameta() extends TypeMeta[A.type]
case class Bmeta() extends TypeMeta[B.type]
case class Cmeta() extends TypeMeta[C.type]
case class TypeDescription[T <: EnumType](info: TypeInfo[T], meta: TypeMeta[T])
I'm confused about defining a function which would accept a List of TypeInfo and return TypeDescription. I currently implemeted it as follows:
//Type parameter with omitted bound? Is that type safe?
def toDescription(lst: List[TypeInfo[_]]): List[TypeDescription[_]] = {
lst map {
case a: Ainfo => TypeDescription(a, Ameta())
case b: Binfo => TypeDescription(b, Bmeta())
case c: Cinfo => TypeDescription(c, Cmeta())
}
}
To workaround the issue I used [_] pattern which does not look typesafely. Is there a way to redeclare the function?
Its type safe , however the 2 type parameters are bound individually to their own constraints and not to one another.
If you are looking to do that , I think you would need to define a method type parameter like so
//Type parameter with omitted bound? Is that type safe?
def toDescription[T<:EnumType](lst: List[TypeInfo[T]]): List[TypeDescription[T]] = {
lst map {
case a: Ainfo => TypeDescription(a, Ameta())
case b: Binfo => TypeDescription(b, Bmeta())
case c: Cinfo => TypeDescription(c, Cmeta())
}
}
Now if you tried to write
case a: Ainfo => TypeDescription(a, Bmeta())
you will get a compilation error
First the code:
object MyEnums {
sealed abstract class MyEnum(val value: String)
case object First extends MyEnum("Some_ugly_looking_value1")
case object Second extends MyEnum("Some_ugly_looking_value2")
case object Third extends MyEnum("Some_ugly_looking_value3")
case object Fourth extends MyEnum("Some_ugly_looking_value4")
def fromString(value: String): Option[MyEnum] =
value match {
case First.value => Option(First)
case Second.value => Option(Second)
case Third.value => Option(Third)
case Fourth.value => Option(Fourth)
case _ => None
}
}
What I'm trying to achieve here is to be able to parse a string value coming from the outside into the form of the above enum. At the same time I would like to have the exhaustive pattern matching compiler warning if I don't cover all options in the match expression. What options do I have here? I don't like what I implemented above, since if this enum grows I may just forget to implement the new case clause...
Consider enumeratum like so
import enumeratum._
sealed abstract class MyEnum(override val entryName: String) extends EnumEntry
object MyEnum extends Enum[MyEnum] {
val values = findValues
case object First extends MyEnum("Some_ugly_looking_value1")
case object Second extends MyEnum("Some_ugly_looking_value2")
case object Third extends MyEnum("Some_ugly_looking_value3")
case object Fourth extends MyEnum("Some_ugly_looking_value4")
}
MyEnum.withName("Some_ugly_looking_value1") // res1: MyEnum = First
Now we do not have to fiddle with pattern match when adding a new case object.
I have following scala code.
trait Super
case class Sub(value:String) extends Super
case class YetAnotherSub(value:String) extends Super
case class OnlyErrorType(value:String) extends Super
def function1[I <: Super, R](mapper : (I) => R, input: Super) (default: R): R = input match {
case error: OnlyErrorType =>
default
case success: I => mapper(success) // Ideally success => mapper(success)
case _ => default // I don't want this line at all, as I'm not expecting any other type
}
def function2(input:String):Super = if(input.size >= 3) Sub("Greater") else OnlyErrorType("Lesser")
def function3(input:String):String = {
val result = function2(input)
function1({sub:Sub => sub.value.toUpperCase}, result) ("Empty Result")
}
function3("Input")
There are various functions similar to function2 which accepts some parameters and return any subtype of Super. I would like to have a generic mapper, like function1, to map type Super to some other type, but return a default value in case of OnlyErrorType
In other words I would like to have some default handling for OnlyErrorType, but let calling function(in this case function3) specify the mapping for SuccessType (any sub types of Super except OnlyErrorType).
How do I achieve this ?
Above code compiles, but I hate to see warning,
warning: abstract type pattern I is unchecked since it is eliminated by erasure
I think there must be a better way to do this.
It's good you don't like the warning; in this case it basically means the test doesn't work.
The simplest approach is to make SuccessType explicit:
sealed trait Super
trait SuccessType extends Super
case class Sub(value:String) extends SuccessType
case class YetAnotherSub(value:String) extends SuccessType
case class OnlyErrorType(value:String) extends Super
def function1[R](mapper: SuccessType => R, input: Super) (default: R): R = input match {
case _: OnlyErrorType => default
case success: SuccessType => mapper(success)
}
Note that because Super is sealed, it can't be extended directly elsewhere, but one of its subtypes can be, so you can add new SuccessTypes. If that's not desired, make SuccessType sealed as well.
Of course, in this case function1({case sub:Sub => sub.value.toUpperCase}, result) ("Empty Result") will fail if it's passed a YetAnotherSub. If that's not what you want, then you need to distinguish "Super which is Sub if successful" and "Super which is YetAnotherSub if successful" statically. You can use
sealed trait Super[I <: Super[I]]
case class Sub(value:String) extends Super[Sub]
case class YetAnotherSub(value:String) extends Super[YetAnotherSub]
case class OnlyErrorType[I <: Super[I]](value:String) extends Super[I]
def function1[I <: Super[I], R](mapper : (I) => R, input: Super[I]) (default: R): R = input match {
case error: OnlyErrorType[_] =>
default
case success => mapper(success.asInstanceOf[I])
}
def function2(input:String):Super[Sub] = if(input.size >= 3) Sub("Greater") else OnlyErrorType("Lesser")
def function3(input:String):String = {
val result = function2(input)
function1({sub:Sub => sub.value.toUpperCase}, result) ("Empty Result")
}
function3("Input")
But I would prefer not to: the cast in function1 is actually safe, but it isn't trivially so (and defining new subtypes of Super incorrectly can break it).
sealed trait FormField
case class StringField(name: String, value: String) extends FormField
case class ChoiceField[T : Writes](name: String, value: T, choices: List[T]) extends FormField
and then, somewhere else I need to do this:
def makeJson(fields: List[FormField]) = fields.map {
case StringField(name, value) => Json.obj(name -> value)
case ChoiceField(name, value, _) => Json.obj(name -> value)
}
In that last function, scalac/sbt doesn't "understand" that value is convertable to json (through its implicit / type class Writes[T]). How can I write it so that it "gets it"?
(Note: Writes[T] is from Play Framework - it basically says that there is an implicit conversion avaiable for the type T => JsValue)
Your problem is that the Writes implicit is not in scope when you do pattern matching; the easiest solution would be to keep an explicit reference to it so that you can use it when needed. That way your class definition becomes something like:
case class ChoiceField[T](name: String, value: T, choices: List[T])(implicit val writes: Writes[T]) extends FormField
And your pattern match:
case cf # ChoiceField(name, value, _) =>
implicit val tWrites = cf.writes
Json.obj(name -> value)
Given a trait Conjunction with AND and OR case object sub-types:
trait Conjunction
case object AND extends Conjunction
case object OR extends Conjunction
Using Play 2 JSON, I tried to write the following Writes[Conjunction]:
implicit object ConjunctionWrites extends Writes[Conjunction] {
implicit val orWrites: Writes[OR] = Json.writes[OR]
implicit val andWrites: Writes[AND] = Json.writes[AND]
def writes(c: Conjunction) = c match {
case a#AND => Json.toJson(a)(andWrites)
case o#OR => Json.toJson(o)(orWrites)
}
}
But I got a bunch of not found: type AND/OR errors.
How can I serialize these case objects?
When you create a case object, you create a value with that name, but not a type. So AND and OR don't exist as types. If you want to refer to the type of a case object, use .type, e.g. AND.type.
However, the Json.writes macro only works on case classes, not case objects. You'll have to write your own definition:
implicit object ConjunctionWrites extends Writes[Conjunction] {
def writes(c: Conjunction) = c match {
case AND => Json.toJson("AND")
case OR => Json.toJson("OR")
}
}