Not very familiar with Scala's type system, but here's what I'm trying to do.
I have a function that tries to filter people by first and last name, and if that fails filters by first name only.
case class Person(id: Int, first: String, last:String)
def(people: Set[Person], firstName: String, lastName: String): (MatchResult, Set[Person]) =
val (both, firstOnly) = people.filter(_.first == firstName).partition(_.last == lastName)
(both.nonEmpty, firstOnly.nonEmpty) match {
case (true, _) => (BothMatch, both)
case (false, true) => (FirstOnly, firstOnly)
case (_, _) => (NoMatch, Set[Person]())
}
Right now I am returning the filtered Set along with an Algebraic Data Type informing the caller which filter's results were used.
sealed trait MatchResult
case object BothMatch extends MatchResult
case object FirstOnly extends MatchResult
case object NoMatch extends MatchResult
However, returning a tuple of the Set + MatchResult doesn't present a very nice contract for the caller. I'm wondering how I can store my filtered results in my MatchResult.
I thought I could simply change to:
sealed trait MatchResult extends Set[People]
case object BothMatch extends MatchResult
case object FirstOnly extends MatchResult
case object NoMatch extends MatchResult
But the compiler tells me that I must implement abstract member iterator: Iterator[A]
I'm not sure if I should try to extend Set or somehow make MatchResult a case class that takes a set as a constructor argument.
One approach is to use case classes to store any matches as a member.
sealed trait MatchResult
case class BothMatch(results:Set[Person]) extends MatchResult
case class FirstOnly(results:Set[Person]) extends MatchResult
case object NoMatch extends MatchResult
In Scala, Set is is trait that has abstract members that must be implemented by any implementing class, and that's why you are receiving that error.
In your implementation, you could use these classes by,
(both.nonEmpty, firstOnly.nonEmpty) match {
case (true, _) => BothMatch(both)
case (false, true) => FirstOnly(firstOnly)
case (_, _) => NoMatch
}
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.
HI I have a case when calling a method to use self. Now not sure the best practice to do this in Scala. I have created a example of how I'm doing it just wanted to ask is this the best way of doing so.
sealed trait Animal {
// match on self
final def speak(): Unit = {
this match {
case Cat(name) => println("spoken like a Cat named: " + name)
case Dog(name) => println("spoken like a Dog named: " + name)
case Pig(name) => println("spoken like a Pig named: " + name)
}
}
final def whoAmI(): Unit = {
this match {
case Cat(_) => println("I am a Cat")
case Dog(_) => println("I am a Dog")
case Pig(_) => println("Could be a Pig")
}
}
}
final case class Cat(name: String) extends Animal
final case class Dog(name: String) extends Animal
final case class Pig(name: String) extends Animal
If your requirement is only to know which sub-type is used, it can be accessed in a more straightforward manner with just this.getClass():
sealed trait Animal {
val name: String
final def speak(): Unit =
println(s"spoken like a ${this.getClass.getSimpleName} named: ${name}")
final def whoAmI(): Unit =
println(s"I am a ${this.getClass.getSimpleName}")
}
If all the implementing sub-types are characterized by a name, that would be convenient to declare an abstract val name: String, which will allow to access the field in speak().
For a use case like this the matchers are not the best option: for each implementing sub-type you have to add an entry in the matchers (may become difficult to maintain). I'd suggest using inheritance: if you have a behavior differentiated among the sub-types, declare an abstract method in the trait, call it from it, but its implementations should remain at sub-types level.
Yes, your use of pattern matching on this is correct.
Since your hierarchy is sealed, you know, that there will not be any new types to account for - otherwise you should have the _ => ... clause.
You can express your second case a bit simpler, since you dont care about the parameters, only about the type.
final def whoAmI(): Unit = {
this match {
case _:Cat => println("I am a Cat")
case _:Dog => println("I am a Dog")
case _:Pig => println("Could be a Pig")
}
}
Finally, this always refers to the innermost type. In case of nested inner classes, you might want to use an alias, like self:
trait Tweeter {
this: self => // reassign this into self, so it can be accessed from an inner class
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?
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")
}
}