Scala case class enriched with abstract component (Cake Pattern) - scala

We use the cake pattern to abstract Components (DB, Mock) with self-type annotation, that are Injected at the top level call.
In one case class we want to be able to enrich it's behavior by extending common trait. But how to do it if we want this case class to call an abstract component?
Refactoring the case class as abstract will remove all the implementation of apply, unapply, copy ... Things that we need to do the mapping between database and model (for example using Slick).
Here is an exemple representing Plants into some Blocks as Resources owned by an Account:
trait Resource {
def getAccount: Future[Account]
}
case class Account(id: Int)
case class Block(id: Int, name:String, accountId: Int) extends Resource
case class Plant(id: Int, name: String, blockId: Int) extends Resource {
this: PlantDBComponent =>
override def getAccount: Future[Account] = plantDB.getAccount(this)
}
trait PlantDBComponent {
def plantDB: PlantDB
trait PlantDB {
def getAccount(plant: Plant): Future[Account]
}
}
trait SlickPlantDBComponent {
def blockDB = SlickPlantDB()
trait SlickPlantDB extends PlantDB {
def getAccount(block: Block): Future[Account] = {
val q = for {
block <- BlockTable if block.id === plant.blockId
account <- AccountTable if account.id === block.accountId
} yield account
db.run(q.result.head)
}
}
}
Any idea on how to do it without reimplementing all the case class boilerplate? Adding getAccount in a Resource companion object with case class pattern matching will not solve the problem

We choose to implement a ResourceService that, according to resource Type, return the Account the resource belong to.
Here is the trait component:
trait ResourceServiceComponent {
val resourceService: ResourceService
trait ResourceService {
def getAccount(resource: Resource): Future[Account] = {
resource match {
case plant: Plant => blockDB.getAccount(plant.blockId)
case ...
}
}
}
}

Related

Defining a generic to be a case class

In this example, I want the generic T to be a case class and a DAOEntity with id, so in the abstract implementation, I can use the copy method.
How to define it?
trait DAOEntity {
def id: String
}
// How to define this generic to force the use of a `case class` to have access to `copy`?
abstract class DAO[T <: DAOEntity] {
def storeInUppercase(entity: T): T = entity.copy(id = entity.id)
}
case class MyEntity(id: String) extends DAOEntity
class MyEntityDAO extends DAO[MyEntity] {
// Other stuff
}
There is no way to know if a type is a case class or not.
And even if there was, you won't get the copy method. The language doesn't provide a way to abstract over constructor; thus neither copy and factories (apply on companions) by extension. Which makes sense, what would be the type signature of such function?
What you can do instead is create a factory-like typeclass and ask for that:
trait DAOFactory[T <: DAOEntity] {
def copy(oldEntity: T, newId: String): T
}
object DAOFactory {
def instance[T <: DAOEntity](f: (T, String) => T): DAOFactory[T] =
new DAOFactory[T] {
override final def copy(oldEntity: T, newId: String): T =
f(oldEntity, newId)
}
}
Which can be used like this:
abstract class DAO[T <: DAOEntity](implicit factory: DAOFactory[T]) {
def storeInUppercase(entity: T): T =
factory.copy(
oldEntity = entity,
newId = entity.id.toUpperCase
)
}
And entities would provide the instance like this:
final case class MyEntity(id: String, age: Int) extends DAOEntity
object MyEntity {
implicit final val MyEntityFactory: DAOFactory[MyEntity] =
DAOFactory.instance {
case (oldEntity, newId) =>
oldEntity.copy(id = newId)
}
}
// This compile thanks to the instance in the companion object.
object MyEntityDAO extends DAO[MyEntity]
You can see the code running here.

How to return the concrete Product type from my function?

I have different product types like:
sealed trait Product
case class Downloadable(url: String) extends Product
case class Shippable(weight: Int) extends Product
case class Virtual(name: String) extends Product
object Load {
def get(id: Int): Product = id match {
case 1 => Downloadable("google.com")
case 2 => Shippable(100)
case _ => Virtual("virtuallll")
}
}
println(Load.get(1))
println(Load.get(2))
println(Load.get(3))
val d = Load.get(1)
println(d.url) // error value url is not a member
I don't have access to the properties of the concrete type, is it possible to be able to?
Pattern matching.
Load.get(1) match {
case dl:Downloadable => println(s"URL: ${dl.url}")
case shp:Shippable => println(s"weight: ${shp.weight}")
case vt:Virtual => println(s"name: ${vt.name}")
}
Each variable (dl, shp, vt) exists only within its case code block.
You are trying to deal uniformly with instances that are not uniform. The solution is to make them uniform, that's what base classes and traits are for: defining and describing common interface.
sealed trait Product[T] { def data: T }
case class Downloadable(url: String) extends Product[String] { def data = url }
case class Shippable(weight: Int) extends Product[Int] { def data = weight }
case class Virtual(name: String) extends Product[String] { def data = name }
then you can do println(Product.get(1).data)
Alternatively (and I don't really recommend that, but if it is like the only place in the codebase where you need this, implementing it properly is too complicated) you can exploit the fact that case classes are instances of Product (not your Product, but scala library trait representing a cartesian product).
All Product instances have a method productIterator that lets you iterate through values of all member variables. So, you can just do this with your existing definitions:
println(Product.load(1).productIterator.next)

Extend case class from another case class

I have two case class Person and Employee
case class Person(identifier: String) {}
case class Employee (salary: Long) extends Person {}
I am getting following error:
Unspecified value parameters: identifier: String
Error: case class Employee has case ancestor Person, but case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes
I am new to Scala and not able to understand what I have to do.
Version:
Scala : 2.11
Unfortunately, I'm afraid it is not possible for case class to extend another case class.
The inheritance in "plain" classes would look like:
class Person(val identifier: String) {}
class Employee(override val identifier: String, salary: Long)
extends Person(identifier) {}
val el = new Employee("abc-test", 999)
println(el.identifier) // => "abc-test"
If you would like to achieve a similar effect with case classes, you would need to reach out to traits:
trait Identifiable {
def identifier: String
}
case class Person(identifier: String) extends Identifiable {}
case class Employee(identifier: String, salary: Long)
extends Identifiable {}
val el = Employee("abc-test", 999)
println(el.identifier) // => "abc-test"
Defining extractors
Extractor provides a way for defining a matching statement used in pattern matching. It is defined in an object in unaply method.
Let's consider the first example again adding support for extractors:
class Person(val identifier: String)
class Employee(override val identifier: String, val salary: Long)
extends Person(identifier)
object Person {
def unapply(identifier: String): Option[Person] = {
if (identifier.startsWith("PER-")) {
Some(new Person(identifier))
}
else {
None
}
}
}
object Employee {
def unapply(identifier: String): Option[Employee] = {
if (identifier.startsWith("EMP-")) {
Some(new Employee(identifier, 999))
}
else {
None
}
}
}
Now, let's define a method that will define pattern matching using those extractors:
def process(anInput: String): Unit = {
anInput match {
case Employee(anEmployee) => println(s"Employee identified ${anEmployee.identifier}, $$${anEmployee.salary}")
case Person(aPerson) => println(s"Person identified ${aPerson.identifier}")
case _ => println("Was unable to identify anyone...")
}
}
process("PER-123-test") // => Person identified PER-123-test
process("EMP-321-test") // => Employee identified EMP-321-test, $999
process("Foo-Bar-Test") // => Was unable to identify anyone...
Case classes in Scala add several different features but often you really use only some of them. So the main question you need to answer is which features you really need. Here is a list based on the spec:
remove the need to type val before field names/constructor params
remove the need for new by adding apply method to the companion object
support for pattern matching by adding unapply method to the companion object. (One of nice things of Scala is that pattern-matching is done in a non-magical way, you can implement it for any data type without requiring it to be a case class)
add equals and hashCode implementations based on all the fields
add toString implementations
add copy method (useful because case classes are immutable by default)
implement Product trait
A reasonable guess of the equivalent for case class Person(identifier: String) is
class Person(val identifier: String) extends Product {
def canEqual(other: Any): Boolean = other.isInstanceOf[Person]
override def equals(other: Any): Boolean = other match {
case that: Person => (that canEqual this) && identifier == that.identifier
case _ => false
}
override def hashCode(): Int = identifier.hashCode
override def toString = s"Person($identifier)"
def copy(newIdentifier: String): Person = new Person(newIdentifier)
override def productElement(n: Int): Any = n match {
case 0 => identifier
case _ => throw new IndexOutOfBoundsException(s"Index $n is out of range")
}
override def productArity: Int = 1
}
object Person {
def apply(identifier: String): Person = new Person(identifier)
def unapply(person: Person): Option[String] = if (person eq null) None else Some(person.identifier)
}
case class Employee(override val identifier: String, salary: Long) extends Person(identifier) {}
Actually the main objections to inheriting from a case class and especially making a case class inheriting another one are the Product trait, copy and equals/hashCode because they introduce ambiguity. Adding canEqual partially mitigates the last problem but not the first ones. On the other hand in a hierarchy like yours, you probably don't need the copy method or Product implementation at all. If you don't use Person in pattern matching, you don't need unapply as well. Most probably all you really need case for is apply, toString and hashCode/equals/canEqual.
Inheriting from case classes (even with regular non-case classes, which is not prohibited) is a bad idea. Check this answer out to get an idea why.
You Person does not need to be a case class. It actually does not need to be a class at all:
trait Person {
def identifier: String
}
case class Employee(identifier: String, salary: Long) extends Person

How to create a factory for a parameterized class?

I'm trying to build a factory for implementations of a generic trait.
Given my domain model:
trait Person
case class Man(firstName: String, lastName: String) extends Person
case class Woman(firstName: String, lastName: String) extends Person
I created a repository for those classes like this:
trait Repository[T <: Person] {
def findAll(): List[T]
}
class ManRepository extends Repository[Man] {
override def findAll(): List[Man] = {
List(
Man("Oliver", "Smith"),
Man("Jack", "Russel")
)
}
}
class WomanRepository extends Repository[Woman] {
override def findAll(): List[Woman] = {
List(
Woman("Scarlet", "Johnson"),
Woman("Olivia", "Calme")
)
}
}
So far so good, some pretty simple classes. But I'd wanted to create a factory to create an instance of these repositories depending on some parameters.
object RepositoryFactory {
def create[T <: Person](gender: String): Repository[T] = {
gender match {
case "man" => new ManRepository()
case "woman" => new WomanRepository()
}
}
}
But this last piece won't compile. If I ommit the explicit return type of the factory, it compiles but returns a repository of type Repository[_1] instead of Repository[Man]
I can't seem to find a proper solution, do any of you guys have got some tips for me?
How about this?
Instead of using a string, use a sealed trait that knows how to create the appropriate repo
Use dependent types to return the correct type of repository
Code:
object GenderStuff {
trait Person
case class Man(firstName: String, lastName: String) extends Person
case class Woman(firstName: String, lastName: String) extends Person
// Note that Repository has to be covariant in P.
// See http://blogs.atlassian.com/2013/01/covariance-and-contravariance-in-scala/ for explanation of covariance.
trait Repository[+P <: Person] {
def findAll(): List[P]
}
class ManRepository extends Repository[Man] {
override def findAll(): List[Man] = {
List(
Man("Oliver", "Smith"),
Man("Jack", "Russel")
)
}
}
class WomanRepository extends Repository[Woman] {
override def findAll(): List[Woman] = {
List(
Woman("Scarlet", "Johnson"),
Woman("Olivia", "Calme")
)
}
}
sealed trait Gender {
type P <: Person
def repo: Repository[P]
}
case object Male extends Gender {
type P = Man
def repo = new ManRepository()
}
case object Female extends Gender {
type P = Woman
def repo = new WomanRepository()
}
object RepositoryFactory {
def create(gender: Gender): Repository[gender.P] = gender.repo
// or if you prefer you can write it like this
//def create[G <: Gender](gender: G): Repository[G#P] = gender.repo
}
val manRepo: Repository[Man] = RepositoryFactory.create(Male)
val womanRepo: Repository[Woman] = RepositoryFactory.create(Female)
}
Of course, this makes the factory object pretty pointless - it's easier to just call Male.repo directly :)
You should probably think some more about what you're trying to achieve. In your example, what type would you expect to be returned by RepositoryFactory.create[Man]("woman")? The compiler has no way to associate those arbitrary strings with types.

Scala - treat separate Types implementing a common TypeClass as the same

I have two case classes, let's call them case class User & case class Ticket. Both of these case classes implement the operations required to be members of a the same TypeClass, in this case Argonaut's EncodeJson.
Is it possible to view these two separate types as the same without creating an empty marker type that they both extend?
trait Marker
case class User extends Marker
case class Ticket extends Marker
To make this concrete,we have two separate functions that return these case classes:
case class GetUser(userId: Long) extends Service[Doesn't Matter, User] {
def apply(req: Doesn't Matter): Future[User] = {
magical and awesome business logic
return Future[User]
}
}
case class GetTicket(ticketId: Long) extends Service[Doesn't Matter, Ticket] {
def apply(req: Doesn't Matter): Future[Ticket] = {
magical and awesome business logic
return Future[Ticket]
}
}
I would like to compose these two Services so that they return the same type, in this case argonaut.Json, but the compiler's response to an implicit conversions is "LOLNO"
implicit def anyToJson[A](a: A)(implicit e: EncodeJson[A]): Json = e(a)
Any ideas? Thanks!
If you've got these case classes:
case class User(id: Long, name: String)
case class Ticket(id: Long)
And these instances:
import argonaut._, Argonaut._
implicit val encodeUser: EncodeJson[User] =
jencode2L((u: User) => (u.id, u.name))("id", "name")
implicit val encodeTicket: EncodeJson[Ticket] = jencode1L((_: Ticket).id)("id")
And the following services (I'm using Finagle's representation):
import com.twitter.finagle.Service
import com.twitter.util.Future
case class GetUser(id: Long) extends Service[String, User] {
def apply(req: String): Future[User] = Future(User(id, req))
}
case class GetTicket(id: Long) extends Service[String, Ticket] {
def apply(req: String): Future[Ticket] = Future(Ticket(id))
}
(These are nonsense but that doesn't really matter.)
Then instead of using an implicit conversion to change the return type, you can write a method to transform a service like this:
def toJsonService[I, O: EncodeJson](s: Service[I, O]): Service[I, Json] =
new Service[I, Json] {
def apply(req: I) = s(req).map(_.asJson)
}
And then apply this to your other services:
scala> toJsonService(GetTicket(100))
res7: com.twitter.finagle.Service[String,argonaut.Json] = <function1>
You could also provide this functionality as a service or a filter, or if you don't mind getting a function back, you could just use GetTicket(100).andThen(_.map(_.asJson)) directly.
The key idea is that introducing implicit conversions should be an absolute last resort, and instead you should use the type class instance directly.