Scala case class hierarchy - class

I got some difficulties designing my case classes. A simplified version looks like:
abstract class Base(s: Option[String]) {
//code
}
case class CaseClass(s: Option[String] = None) extends Base(s) {
//code
}
And I have a method where I want to do something like:
def method(base : Base) = {
//code
base copy (s = Some("string"))
}
Of course I get:
value copy is not a member of Base
So what I want to do is create a new instance based on my base class (which is not a case class). Obviously one can not do this. But how would you solve this in a elegant way?
Thanks in advance!

If you parameterize your base class and also define the abstract copy method there, you can have the subclasses return instances of their own types from the copy method. In this case, you want CaseClass to return a CaseClass, presumably.
abstract class Base[T](s: Option[String]) {
def copy(in: Option[String]) : T
}
case class CaseClass(s: Option[String]) extends Base[CaseClass](s) {
def copy(in: Option[String]) = CaseClass(in)
}
case class OtherClass(s: Option[String]) extends Base[OtherClass](s) {
def copy(in: Option[String]) = OtherClass(in)
}
def method[T <: Base[T]](base: T) : T = {
base.copy(Some("String"))
}
scala> method(CaseClass(None))
res1: CaseClass = CaseClass(Some(String))
scala> method(OtherClass(Some("hi")))
res2: OtherClass = OtherClass(Some(String))
Other subclasses of Base would return their own types. The type parameter on #method is defined with an upper bound of Base[T]. This means that T must be any sub-type of Base[T] and is what allows you to supply instances of CaseClass and OtherClass as parameters to that method.

The behavior you're trying to achieve is not implementable. copy method of a case class is autogenerated by the compiler, and once you add a method called copy to your implementation, compiler will not generate any sugar.
You can reimplement copy with traits, but it will not be as flexible as the generated one (you will have to update the base trait, copy and method implementations every time the field-set of a case class changes):
sealed trait Base[T] {
val s: Option[String]
def copy(s: Option[String]) : T
}
case class CaseClass(override val s: Option[String] = None) extends Base[CaseClass] {
override def copy(s: Option[String]) = CaseClass(s)
}
def method[T <: Base[T]](base : Base[T]) = base copy (s = Some("strng"))
Alternatively, you can implement method as follows:
case class CaseClass(s: Option[String] = None)
def method[X <: {def copy(s: Option[String]):X}](base : X) =
base copy(s = Some("string"))
scala> method(CaseClass())
res4: CaseClass = CaseClass(Some(string))
Thus you won't need Base trait, and reduce the number of alterations, if your case classes change.

Related

Scala: how to use an implicit class extension's implementation of a trait

I am trying to provide an extension to a class I can't modify using an implicit class. I have a trait HasFoo[A] that takes a type parameter. I then have a class (Processor) that expects an A that implements HasFoo[A]. The issue is that the compiler doesn't recognize that the Bar class is implementing HasFoo[Bar] via the implicit class extension.
The error is Type argument Bar does not conform to upper bound Playground.HasFoo[Bar]
Is there a way to get the compiler to recognize that the trait HasFoo[Bar] is implemented by the implicit class, or is there a better way to do this?
// Bar is an autogenerated class that I can't modify directly
final case class Bar(baz: String)
trait HasFoo[A] {
def foo: A
}
implicit class Ext(val bar: Bar) extends HasFoo[Bar] {
def foo: Bar = bar.copy(baz = s"${bar.baz} else")
}
class Processor[A <: HasFoo[A]]() {}
// This errors out because type `Bar` doesn't implement `foo`,
// even though the implicit extension does.
val process = new Processor[Bar]()
The class Bar implementing the method def foo from the trait HasFoo doesn't make Bar a subtype of HasFoo.
You can try to make HasFoo a type class rather than just OOP trait (replacing subtype polymorphism and F-bounded polymorphism with ad hoc polymorphism). Third-party classes like autogenerated Bar that can't be modified is exactly a use case for type classes.
// autogenerated class
final case class Bar(baz: String)
// type class
trait HasFoo[A] {
def foo(a: A): A
}
// extension method
implicit class Ext[A: HasFoo](a: A) {
def foo: A = implicitly[HasFoo[A]].foo(a)
}
// Bar is an instance of the type class
implicit val barHasFoo: HasFoo[Bar] = new HasFoo[Bar] {
override def foo(bar: Bar): Bar = bar.copy(baz = s"${bar.baz} else")
}
Bar("baz").foo
// replacing F-bound with context bound
class Processor[A: HasFoo]
val process = new Processor[Bar]
Some intros to type classes:
What are type classes in Scala useful for?
https://kubuszok.com/2018/implicits-type-classes-and-extension-methods-part-1/
https://tpolecat.github.io/2013/10/12/typeclass.html https://tpolecat.github.io/2015/04/29/f-bounds.html
https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:generic:type-classes (chapter 3.1)
https://www.baeldung.com/scala/type-classes
https://docs.scala-lang.org/scala3/book/types-type-classes.html
https://docs.scala-lang.org/scala3/reference/contextual/type-classes.html
https://gist.github.com/BalmungSan/c19557030181c0dc36533f3de7d7abf4#typeclasses
In principle you can have both type class and OOP trait (for example if you already have many implementations of the OOP trait and you don't want to modify them) although this seems to be overengineering
// autogenerated class
final case class Bar(baz: String)
// OOP trait
trait HasFoo[A] {
def foo: A
}
class HasFooImpl extends HasFoo[HasFooImpl] {
override def foo: HasFooImpl = new HasFooImpl
}
// type class
trait HasFooTC[A] {
def foo(a: A): A
}
implicit class Ext[A: HasFooTC](a: A) extends HasFoo[A] {
override def foo: A = implicitly[HasFooTC[A]].foo(a)
}
// all (F-bounded) implementations of the OOP trait are instances of the type class
implicit def hasFooSubtypes[A <: HasFoo[A]]: HasFooTC[A] = new HasFooTC[A] {
override def foo(a: A): A = a.foo
}
implicit val barHasFoo: HasFooTC[Bar] = new HasFooTC[Bar] {
override def foo(bar: Bar): Bar = bar.copy(baz = s"${bar.baz} else")
}
Bar("baz").foo // extension method
new HasFooImpl().foo // OOP method
class Processor[A: HasFooTC]
val process = new Processor[Bar]
val process1 = new Processor[HasFooImpl]
An alternative to the type class is another pattern, the magnet. Magnets are much less popular than type classes (implicit conversions should be used with caution although the above implicit class actually defined an implicit conversion too)
import scala.language.implicitConversions
// autogenerated class
final case class Bar(baz: String)
// magnet
trait HasFoo[A] {
def foo: A
}
// implicit conversion from Bar to the magnet
implicit def fromBar(bar: Bar): HasFoo[Bar] = new HasFoo[Bar] {
override def foo: Bar = bar.copy(baz = s"${bar.baz} else")
}
Bar("baz").foo
// replacing F-bound with view bound
class Processor[A](implicit ev: A => HasFoo[A])
val process = new Processor[Bar]
Group TypeClass instances by type parameter
Overloading methods based on generics
Type erasure problem in method overloading
How to make a typeclass works with an heterogenous List in scala
Generic function where the return type depends on the input type in Scala?
Problem with bringing into scope scala implicit conversions
https://kubuszok.com/2018/implicits-type-classes-and-extension-methods-part-3/#magnet-pattern
Scala generic method - No ClassTag available for T - when using Collection
Trying to extract the TypeTag of a Sequence of classes that extend a trait with different generic type parameters
As #LuisMiguelMejíaSuárez advices in comments, alternatively a wrapper can be used. This would also work if F-bounded Processor couldn't be modified.
// autogenerated class
final case class Bar(baz: String)
trait HasFoo[A] {
def foo: A
}
final case class BarWrapper(bar: Bar) extends HasFoo[BarWrapper] {
override def foo: BarWrapper = copy(bar = bar.copy(baz = s"${bar.baz} else"))
}
class Processor[A <: HasFoo[A]]
val process = new Processor[BarWrapper]

How to restrict that case classes have specific parameter types constructor in Scala?

Let's say I have case classes something like below.
trait Foo {
def a: String
}
case class Bar(a: String,b: Option[Int]) extends Foo{
def this(test: Test) = this(test.foo,None)
}
case class Buzz(a: String,b: Boolean) extends Foo{
def this(test: Test) = this(test.foo,false)
}
I'm using the constructor def this(test: Test) via reflection and working as I expected.
A method signature that I use the constructor is something like this
def test[T <: Foo: ClassTag](cb: (String) => Future[T]): Future[Result]
What I want to do is restrict that any case classes that extends trait Foo needs to have def this(test: Test).And the case if any of them don't have it, It should be a compile error.
My attempt
//Compile error
trait Foo[T] {
def a: String
def this(test: Test):T
}
Is there any way to do this?
Thanks in advance.
It is not possible to use the type system to enforce that a class has a specific constructor. This shouldn't really be a surprise, because you're already using reflection to access said constructor. Using a reflective call, the only way to check for the appropriate constructor would be to use more reflection--preferably via a macro to mail compilation fail.
There is almost always a better way than using reflection, though. In this case, we can use a type class to find the correct method that can build a sub-type of Foo (or anything, really) from a Test.
Let's assume Test looks like this:
case class Test(foo: String)
Then, we define a TestBuilder type class, which can provide evidence that we can build an A from a Test.
trait TestBuilder[A] {
def build(test: Test): A
}
// Convenience method for creating type class instances
object TestBuilder {
def apply[A](f: Test => A): TestBuilder[A] = new TestBuilder[A] {
def build(test: Test): A = f(test)
}
}
Then, we define out Foos, each with an instance of TestBuilder[A], where A is the type of each Foo:
trait Foo {
def a: String
}
case class Bar(a: String, b: Option[Int]) extends Foo
object Bar {
implicit val builder = TestBuilder(test => Bar(test.foo, None))
}
case class Buzz(a: String, b: Boolean) extends Foo
object Buzz {
implicit val builder = TestBuilder(test => Buzz(test.foo, false))
}
Note that we no longer need the alternate constructors, and rely on the type class instances to build our Foos using apply.
Now, your test method could look something like this. I changed around the return types because you don't define any implementation or what Result is, but the idea is the same.
def test[T <: Foo : ClassTag : TestBuilder](cb: String => Future[T]): Future[T] = {
val test = Test("abc")
// use the implicitly resolved type class to build `T` from a `Test`
val t = implicitly[TestBuilder[T]].build(test)
Future(t).andThen {
case Success(x) => cb(x.a)
}
}
Now, something like this will compile:
// T is Bar
scala> test((s: String) => Future(Bar(s, None)))
res0: scala.concurrent.Future[Bar] = scala.concurrent.impl.Promise$DefaultPromise#56f2bbea
And using some other type Baz, without an instance of TestBuilder[Baz] will fail.
case class Baz(a: String) extends Foo
scala> test((s: String) => Future(Baz(s)))
<console>:29: error: could not find implicit value for evidence parameter of type TestBuilder[Baz]
test((s: String) => Future(Baz(s)))
^
I don't think you can do quite what you're looking for. But maybe this would work for you:
trait Foo {
def a: String
def create(a: String): Foo
}
case class Bar(a: String,b: Option[Int]) extends Foo{
def create(a: String) = Bar(a,None)
}
case class Buzz(a: String,b: Boolean) extends Foo{
def create(a: String) = Buzz(a,false)
}
You would then have a way to construct a Bar or Buzz without having to specify the second parameter.
BTW, I didn't quite follow your template exactly because I didn't know what Test was supposed to be.
I don't think there's a way to do this directly. But one usually constructs case classes through factories -- their companion objects -- and that gives you the flexibility to do what you want in a different way.
Define
trait Test {
def foo : String = ???
}
abstract class Foo[T <: Foo[T]]()(implicit ev : FooMaker[T]) {
def a: String
}
trait FooMaker[T <: Foo[T]] {
def apply( test : Test ) : T
}
implicit object Bar extends FooMaker[Bar] {
def apply(test: Test) = Bar(test.foo,None)
}
case class Bar(a: String,b: Option[Int]) extends Foo[Bar]
implicit object Buzz extends FooMaker[Buzz] {
def apply(test: Test) = Buzz(test.foo,false)
}
case class Buzz(a: String,b: Boolean) extends Foo[Buzz]
But if you try to define a Foo without the factory method in the companion object that you require:
case class Barf(a : String, b : Short ) extends Foo[Barf]
You'll see
scala> case class Barf(a : String, b : Short ) extends Foo[Barf]
<console>:12: error: could not find implicit value for parameter ev: FooMaker[Barf]
case class Barf(a : String, b : Short ) extends Foo[Barf]
Add the companion object with the factory you need, and it's all good
implicit object Barf extends FooMaker[Barf] {
def apply(test: Test) = Barf(test.foo,0.toShort)
}
case class Barf(a : String, b : Short ) extends Foo[Barf]
In the REPL:
scala> :paste
// Entering paste mode (ctrl-D to finish)
implicit object Barf extends FooMaker[Barf] {
def apply(test: Test) = Barf(test.foo,0.toShort)
}
case class Barf(a : String, b : Short ) extends Foo[Barf]
// Exiting paste mode, now interpreting.
defined object Barf
defined class Barf
Note that to compile this stuff in the REPL you'll need to use :paste because the mutually interdependent definitions can't be defined separately.

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.

Scala: Polymorphic daisy chaining

class Foo(protected[this] val s: Iterator[String]) {
def apply(it: Iterator[String]): Foo = new Foo(it ++ s)
}
class Bar(s: Iterator[String]) extends Foo(s) {
}
Question: How can I get Bar.apply() to return a new Bar instead of a new Foo? I don't want to override.
You can use F-bounded polymorphism to get an apply that returns the proper type. You also need to define a method that creates an instance of the subclass:
abstract class Foo[X](protected[this] val s: Iterator[String]) {
self: X =>
def newSubclass(s: Iterator[String]): X
def apply(it: Iterator[String]): X = newSubclass(it ++ s)
}
class Bar(s: Iterator[String]) extends Foo[Bar](s) {
def newSubclass(s: Iterator[String]): Bar = new Bar(s)
}
Bar.apply will have Bar as its return type, without needing to be overriden.
You can read more about F-bounded polymorphism at the Twitter Scala school.
Have looked through this article. Seems it's what you want. Quickly scetch out simple example(using var's in this example)
class A(var s: String) {
def apply(a: String): this.type = {
s = "A" + a
this
}
}
class B(var s: String) extends A(s)
P.S.: Tried to use vals but it is impposible to call constructor in method which return type is this.type. Maybe you'll find the solution))

Extending a trait and types

I would like to have a sealed trait which have a declared method that returns the
actual class that extends the trait. Should I use an abstract type, a parameter type or
is there any other nice way to solve this?
sealed trait Foo {
type T
def doit(other: T): T
}
or
sealed trait Foo[T] {
def doit(other: T): T
}
Note that T must be a subtype of Foo in this example. If I do it like this the type
information feels too repeated:
case class Bar(name: String) extends Foo[Bar] {
def doit(other: Bar): Bar = ...
}
They are mostly interchangeable. According to Odersky, the reason was mainly for completeness: That similarly to the fact that methods and fields (values) can be either abstract or passed as parameters, so can types.
It is better to use an abstract type when you intend to mix several traits that all use the same type name. With type parameters you need to explicitly pass the type to each
Here's an article explaining all of this: http://www.artima.com/weblogs/viewpost.jsp?thread=270195
You can cut down on the repetition somewhat by having your doit method return a factory function:
trait Foo[T] {
self: T =>
def doit: T => T
}
case class Bar(name: String) extends Foo[Bar] {
// note: types omitted
def doit = { other => Bar(name + other.name) }
}
It's not possible to do the same with an abstract type:
trait Foo {
self: T => // won't compile because T isn't defined yet
type T
def doit: T => T
}
You can write:
trait Foo[T] {
self:T =>
def doit(other: T): T
}
case class Bar(name: String) extends Foo[Bar] {
def doit(other: Bar): Bar = ...
}
The difference to your example is that Bar can't be instantiated in any other way (e.g. case class Bar(name: String) extends Foo[String]).
trait Foo[A <: Foo[A]]
This trait can only be mixed in if A is a subtype of Foo[A] and the only type satisfying that is the class Foo is being mixed into. I saw this solution in the Mapper traits in Lift.
EDIT - Below is my original answer. Your comment indicates that you wish to return an arbitrary instance of a matching type but I don't really believe that this is in any way sensible. Suppose it were, via the T.type syntax:
trait T { def foo : T.type }
trait U extends T { def foo = new U } //must be a U
class W extends U
val w : W = (new W).foo //oh dear.
This is accomplishable via this.type:
scala> trait T {
| def foo : this.type
| }
defined trait T
scala> class W extends T {
| def foo = this
| }
defined class W
scala> (new W).foo
res0: W = W#22652552
scala> res0.foo
res1: res0.type = W#22652552
And then also:
scala> ((new W) : T)
res4: T = W#45ea414e
scala> res4.foo.foo.foo
res5: res4.type = W#45ea414e