Scala - Typeclass overriding the abstract method - scala

I am struggling a little with scala 2.12:
I have the following hierarchy:
trait A
case class B(format: String) extends A
trait Writer {
def write(config: A): Unit
}
val writer = new Writer {
override def write(config: A) = println("hi")
}
val w = B("console")
writer.write(w)
which works fine. But I want to provide an alternate implementation for writer:
val writer = new Writer {
override def write(config: B) = println("hi")
}
But I get object creation impossible, since method write in trait Writer of type (config: Playground.A)Unit is not defined
I assumed that since B is an A, this should work. How can I override write with a config of type B where B <: A
Scastie: https://scastie.scala-lang.org/QBaiiDP4Sj2lptUjrWLJYw
EDIT: ------------------------------------------------------------
Based on some inputs, I changed the implementation to:
sealed trait A
case class B(format: String) extends A
trait Writer[+T] {
def write[S >: T](config: S): Unit
}
val writer: Writer[A] = new Writer[B] {
override def write[B](config: B) = println("hi")
}
val b = B("console")
writer.write(b)
which works.
But if I modify it to access the variables in config, it breaks:
sealed trait A
case class B(format: String) extends A
trait Writer[+T] {
def write[S >: T](config: S): Unit
}
val writer: Writer[A] = new Writer[B] {
override def write[B](config: B) = println(config.format)
}
val b = B("console")
writer.write(b)
with value format is not a member of type parameter B
https://scastie.scala-lang.org/Xj2rKbbiTmG7raZgQZYfHA
Appreciate the inputs.

You're very close with your latest version. As Matthias Berndt pointed out, the write method declares a new type parameter, but should use the one declared on the trait. In addition, the type parameter should be contravariant.
This code compiles and prints console:
sealed trait A
case class B(format: String) extends A
trait Writer[-T <: A] {
def write(config: T): Unit
}
val writer: Writer[B] = new Writer[B] {
override def write(config: B) = println(config.format)
}
val b = B("console")
writer.write(b)
Note that, because B is a subtype of A, you can also use a Writer[A] with an instance of B. Because Writer is contravariant, you can assign a value of type Writer[A] to a variable of type Writer[B]:
val aWriter: Writer[B] = new Writer[A] {
override def write(config: A) = println(s"Got A: $config")
}
aWriter.write(b) // prints "Got A: B(console)"
You can't do the opposite (assign a Writer[B] value to a Writer[A] variable) because a Writer[A] would be able to accept any value of type A, while a Writer[B] can only accept values of type B.
https://scastie.scala-lang.org/TimMoore/bd5E1p99TLCDVfMbElKqFg/8

It doesn't work because Writer declares that its write method will accept an arbitrary A. What if someone decides to pass an A that is not a B to writer.write? Then it wouldn't work, so the compiler stops you from doing that.

Related

Scala - how to define factory method for contravariant class?

I have a trait with common parameters for a data source, with case classes for each actual source
trait AbstractSource {
val name: String
}
case class ConcreteSource(name: String) extends AbstractSource
I also have a trait for a class which acts on this data source (contravariant for the source)
trait AbstractConsumer[-T <: AbstractSource] {
def foo(inp: T): Unit
}
class ConcreteConsumer extends AbstractConsumer[ConcreteSource] {
override def foo(inp: ConcreteSource): Unit =
println(inp.name)
}
I want to create a factory method for my pipeline method which creates the correct consumer based on the input data source. I have tried the following but both have errors
object ConsumerFactory {
def create(inp: AbstractSource): AbstractConsumer[_ <: AbstractSource] =
inp match {
case _: ConcreteSource => new ConcreteConsumer()
case _ => ???
}
def createTwo[T <: AbstractSource](inp: T): AbstractConsumer[T] =
inp match {
case _: ConcreteSource => new ConcreteConsumer() // errors "required: AbstractConsumer[T], found: ConcreteConsumer"
case _ => ???
}
}
class Main {
def pipeline[T <: AbstractSource](consumer: AbstractConsumer[T], source: T): Unit =
consumer.foo(source)
def execute(): Unit = {
val consumer: ConcreteSource = ConcreteSource("john")
val source = ConsumerFactory.create(consumer) // errors "found: AbstractConsumer[_$1] where type _$1 <: AbstractSource, required: AbstractConsumer[ConcreteSource]"
val source = ConsumerFactory.createTwo(consumer)
pipeline(source, consumer)
}
}
val x = new Main()
x.execute()
If I understand correctly, the issue is that I need to supply a subtype of AbstractConsumer[T] to the pipeline, but I don't know how to do this based on the input due to the contravariant type parameter.
IMHO, theese kinds of problems are more easily solvable with a typeclass, like this:
trait AbstractConsumer[-S <: AbstractSource] {
def consume(inp: S): Unit
}
object AbstractConsumer {
sealed trait ConsumerFactory[S <: AbstractSource] {
type Consumer <: AbstractConsumer[S]
def createConsumer(): Consumer
}
type Factory[S <: AbstractSource, C <: AbstractConsumer[S]] = ConsumerFactory[S] { type Consumer = C }
object Factory {
def apply[S <: AbstractSource, C <: AbstractConsumer[S]](factory: => C): Factory[S, C] =
new ConsumerFactory[S] {
override final type Consumer = C
override final def createConsumer(): Consumer =
factory
}
}
// Get by type.
def apply[S <: AbstractSource](implicit factory: ConsumerFactory[S]): factory.Consumer =
factory.createConsumer()
// Get by value.
def fromSource[S <: AbstractSource](source: S)(implicit factory: ConsumerFactory[S]): factory.Consumer =
factory.createConsumer()
}
Then the concrete source will implement the typeclass, like this:
final class ConcreteConsumer extends AbstractConsumer[ConcreteSource] {
override def consume(inp: ConcreteSource): Unit =
println(inp.name)
}
object ConcreteConsumer {
implicit final val ConcreteConsumerFactory: AbstractConsumer.Factory[ConcreteSource, ConcreteConsumer] =
AbstractConsumer.Factory(new ConcreteConsumer())
}
And, finally, you can use it like this:
import ConcreteConsumer._ // Put the factory in scope.
val source = new ConcreteSource("john")
val consumer1 = AbstractConsumer[ConcreteSource]
val consumer2 = AbstractConsumer.fromSource(source)
You may adapt the code if the factory needs some arguments or something.
The code can be seen running here.

Implementing a typeclass using type parameters versus abstract types

Following on from Witness that an abstract type implements a typeclass
I've tried to compare these two approaches side-by-side in the code snippet below:
// We want both ParamaterizedTC and WithAbstractTC (below) to check that
// their B parameter implements AddQuotes
abstract class AddQuotes[A] {
def inQuotes(self: A): String = s"${self.toString}"
}
implicit val intAddQuotes = new AddQuotes[Int] {}
abstract class ParamaterizedTC[A, _B](implicit ev: AddQuotes[_B]) {
type B = _B
def getB(self: A): B
def add1ToB(self: A): String = ev.inQuotes(getB(self)) // TC witness does not need to be at method level
}
abstract class WithAbstractTC[A] private {
// at this point the compiler has not established that type B implements AddQuotes, even if we have created
// this instance via the apply[A, _B] constructor below...
type B
def getB(self: A): B
def add1ToB(self: A)(implicit ev: AddQuotes[B]): String =
ev.inQuotes(getB(self)) // ... so here the typeclass witness has to occur on the method level
}
object WithAbstractTC {
// This constructor checks that B implements AddQuotes
def apply[A, _B: AddQuotes](getB: A => _B): WithAbstractTC[A] = new WithAbstractTC[A] {
type B = _B
def getB(self: A): B = getB(self)
}
// But we could also have a constructor that does not check, so the compiler can never be certain that
// for a given instance of WithAbstractTC, type B implements AddQuotes
def otherConstructor[A, _B](getB: A => _B): WithAbstractTC[A] { type B = _B } = new WithAbstractTC[A] {
type B = _B
def getB(self: A): B = getB(self)
}
}
case class Container[B: AddQuotes]( get: B )
// These are both fine
implicit def containerIsParamaterized[B: AddQuotes]: ParamaterizedTC[Container[B], B] =
new ParamaterizedTC[Container[B], B] { def getB(self: Container[B]): B = self.get }
implicit def containerIsWithAbstract[_B: AddQuotes]: WithAbstractTC[Container[_B]] =
WithAbstractTC[Container[_B], _B](self => self.get)
val contIsParamaterized: ParamaterizedTC[Container[Int], Int] =
implicitly[ParamaterizedTC[Container[Int], Int]]
val contIsWithAbstract: WithAbstractTC[Container[Int]] =
implicitly[WithAbstractTC[Container[Int]]]
implicitly[AddQuotes[contIsParamaterized.B]]
implicitly[AddQuotes[contIsWithAbstract.B]] // This is not fine
My conclusion (please correct me if I'm wrong) is that if the typeclass witness exists in the public constructor (as in ParamaterizedTC below) then the compiler can always be certain that B implements AddQuotes. Whereas if this witness is put in a constructor in the typeclass companion object (like for WithAbstractTC) then it cannot. This somewhat changes the usage of a type-parameter-based approach versus the abstract-type-based approach.
The difference is rather: in ParametrizedTC you have the implicit in scope of the class, in WithAbstractTC you don't. But nothing stops you from adding it when you have an abstract type:
abstract class WithAbstractTC2[A] private {
type B
implicit val ev: AddQuotes[B]
def getB(self: A): B
def add1ToB(self: A): String =
ev.inQuotes(getB(self))
}
def apply[A, _B](getB: A => _B)(implicit _ev: AddQuotes[_B]): WithAbstractTC2[A] = new WithAbstractTC2[A] {
type B = _B
implicit val ev: AddQuotes[B] = _ev
def getB(self: A): B = getB(self)
}
What unfortunately won't work is something like
def apply[A, _B: AddQuotes](getB: A => _B): WithAbstractTC2[A] = new WithAbstractTC2[A] {
type B = _B
implicit val ev: AddQuotes[B] = implicitly[AddQuotes[_B]]
def getB(self: A): B = getB(self)
}
because it'll pick the implicit in closest scope: the one it's trying to define.
implicitly[AddQuotes[contIsWithAbstract.B]] refusing to compile is not connected with single/multiple constructors/apply methods or type parameter/type member difference. You just lost type refinements everywhere. Compiler can't check that you lost type refinements. You have the right to upcast a type discarding its refinement.
If you restore type refinements the code compiles
object WithAbstractTC {
def apply[A, _B: AddQuotes](getB: A => _B): WithAbstractTC[A] {type B = _B} =
// ^^^^^^^^^^^^^
new WithAbstractTC[A] {
type B = _B
def getB(self: A): B = getB(self)
}
...
}
implicit def containerIsWithAbstract[_B: AddQuotes]:
WithAbstractTC[Container[_B]] { type B = _B } =
// ^^^^^^^^^^^^^^^
WithAbstractTC[Container[_B], _B](self => self.get)
val contIsWithAbstract: WithAbstractTC[Container[Int]] { type B = Int } =
// ^^^^^^^^^^^^^^^^
shapeless.the[WithAbstractTC[Container[Int]]]
//^^^^^^^^^^^^^
implicitly[AddQuotes[contIsWithAbstract.B]] // compiles
Please notice that implicitly looses type refinements, shapeless.the is safe version.
When implicitly isn't specific enough https://typelevel.org/blog/2014/01/18/implicitly_existential.html
How to use class-level implicit constraint for type-member type class via abstract implicit see #AlexeyRomanov's answer.

Type Class for Related Types

Let's say we have the following traits:
trait MyValue
object MyValue {
case class MyBoolean(record: Boolean) extends MyValue
case class MyLong(record: Long) extends MyValue
}
trait MyValueExtractor[T] {
def apply(record: T): Option[MyValue]
}
trait MyThing[T] {
def name: String
def myValueExtractor: MyValueExtractor[T]
def myValue(record: T): Option[MyValue] = myValueExtractor(record)
}
What I want is something like this but without the second type parameter.
Note: I can't actually update the MyThing trait; I'm just using this as an illustration of the intended functionality.
trait MyThing[T, U] {
def name: String
def myValueExtractor: MyValueExtractor[T]
def myValue(record: T): Option[MyValue] = myValueExtractor(record)
def myRelatedValue(record: T): Option[U]
}
I'm wondering if I could use the type class pattern to help solve this (i.e., import some rich class that implicitly gives me a myRelatedValue method)?
Here's the rub. Every time T (above) is MyValue.MyBoolean, U must be a String. Every time T is MyValue.MyLong, U must be a Double. In other words, there's a sort of underlying mapping between T and U.
Is there a good way to do this using type class?
Sure. You just need to define some Mapping typeclass with implementations for your desired pairs of types. Then MyThing can have a method that takes an implicit typeclass instance and simply invokes its method.
Here's the code (I removed the unneeded details)
// types
case class MyBoolean(record: Boolean)
case class MyLong(record: Long)
// trait which uses the Mapping typeclass
trait MyThing[T] {
def myRelatedValue[U](record: T)(implicit ev: Mapping[T, U]): Option[U] = ev.relatedValue(record)
}
// typeclass itself
trait Mapping[T, U] {
def relatedValue(record: T): Option[U]
}
object Mapping {
implicit val boolStringMapping = new Mapping[MyBoolean, String] {
def relatedValue(record: MyBoolean) = Some(record.record.toString)
}
implicit val longDoubleMapping = new Mapping[MyLong, Double] {
def relatedValue(record: MyLong) = Some(record.record)
}
}
// usage
val myBoolThing = new MyThing[MyBoolean] {}
val myLongThing = new MyThing[MyLong] {}
val myStringThing = new MyThing[String] {}
myBoolThing.myRelatedValue(MyBoolean(true)) // Some(true)
myLongThing.myRelatedValue(MyLong(42L)) // Some(42.0)
myStringThing.myRelatedValue("someString") // error: could not find implicit value
Note that e.g. myBoolThing.myRelatedValue(MyBoolean(true)) will yield a type Option[U]. However, since myRelatedValue is parameterized, you can help the compiler and invoke it as myBoolThing.myRelatedValue[String](MyBoolean(true)), in which case you will obtain an Option[String]. If you try something other than String for MyBoolean, you will get an error.

How to enforce a context bound on a wildcard in Scala?

I have an implicit helper set up like this:
trait Helper[T] {
def help(entry: T): Unit
}
object Helpers {
implicit object XHelper extends Helper[X] {
override def help(entry: X): Unit = {println("x")}
}
implicit object YHelper extends Helper[Y] {
override def help(entry: Y): Unit = {println("y")}
}
def help[T](entry: T)(implicit helper: Helper[T]): Unit = {
helper.help(entry)
}
}
I would like to set up a collection of elements and run help on each of them. However, the following gives a compiler error because we can't guarantee all elements have matching Helpers:
val data = Seq[_](new X(), new Y())
data.foreach(entry => Helpers.help(entry))
If we had a generic type T we could enforce the implicit constraint on it with [T: Helper], but that doesn't work on _. How can I enforce that each element of data has a matching Helper?
In Scala context bound like class A[T: Typeclass] is just syntactic sugar for class A[T](implicit ev: Typeclass[T]). Unlike T <: Base or T >: Super, context bound is not really a part of a type signature, so you can't have a signature like val b: Box[T: Typeclass].
If you want to run typeclass operations on elements of some container, you'd have to pack relevant typeclass instances together with the values in the container.
A possible implementation of this may look as follows:
import language.higherKinds
import language.implicitConversions
// Class that packs values with typeclass instances
class WithTC[T, TC[_]](t: T)(implicit tc: TC[T]) {
// Some helper methods to simplify executing typeclass operations
// You may just make `t` and `tc` public, if you wish.
def apply[U](op: (TC[T], T) => U) = op(tc, t)
def apply[U](op: T => TC[T] => U) = op(t)(tc)
}
object WithTC {
// Implicit conversion to automatically wrap values into `WithTC`
implicit def apply[T, TC[_]](t: T)(implicit tc: TC[T]): WithTC[T, TC] =
new WithTC(t)(tc)
}
Then you can make a sequence with existentially typed elements:
import Helpers._
val data: Seq[(T WithTC Helper) forSome { type T }] = Seq(new X(), new Y())
And execute typeclass operations on the sequence elements:
// The following lines produce equivalent results
data.foreach(_(_ help _))
data.foreach(_(t => implicit tc => Helpers.help(t)))
data.foreach(_(t => Helpers.help(t)(_)))
It's not possible with type like Seq since it is only parametrized for one element type that is common for all its elements.
However, you can achieve this with Shapeless HLists and polymorphics functions (Poly):
class X
class Y
trait Helper[T] {
def help(entry: T): Unit
}
object Helpers {
implicit object XHelper extends Helper[X] {
override def help(entry: X): Unit = println("x")
}
implicit object YHelper extends Helper[Y] {
override def help(entry: Y): Unit = println("y")
}
}
import shapeless._
object helper extends Poly1 {
implicit def tCase[T: Helper]: Case.Aux[T, Unit] =
at(implicitly[Helper[T]].help(_))
}
val hlist = new X :: new Y :: HNil
hlist.map(helper)
// Output:
x
y

Scala Mutually Convertible Generic Types

I'm very new to Scala programming, and I really like the degree to which code is composable. I wanted to write some traits that deal with two related objects that are convertible to each other, and build more functionality by continuing to extend that trait so that when I create objects I can specify the related types for my generics. Here is a working toy example of the type of code I'm talking about:
trait FirstConverter[First] {
def toFirst: First
}
trait SecondConverter[Second] {
def toSecond: Second
}
trait TwoWayConverter[First <: SecondConverter[Second], Second <: FirstConverter[First]] {
def firstToSecond(x: First) = x.toSecond
def secondToFirst(x: Second) = x.toFirst
}
trait RoundTripConverter[First <: SecondConverter[Second], Second <: FirstConverter[First]] extends TwoWayConverter[First, Second] {
def firstToFirst(x: First) = secondToFirst(firstToSecond(x))
def secondToSecond(x: Second) = firstToSecond(secondToFirst(x))
}
case class A(s: String) extends SecondConverter[B] {
def toSecond: B = B((s.toInt) + 1)
}
case class B(i: Int) extends FirstConverter[A] {
def toFirst: A = A((i * 2).toString)
}
object ABConverter extends RoundTripConverter[A, B]
object Main {
def main(args: Array[String]): Unit = {
println(ABConverter firstToSecond A("10")) // 11
println(ABConverter secondToFirst B(42)) // 84
println(ABConverter firstToFirst A("1")) // 4
println(ABConverter secondToSecond B(2)) // 5
}
}
While this works, I'm not sure if it's idiomatic Scala. I'm asking if there are any tricks to make the type definitions more concise and if I can somehow define the type restrictions only once and have them used by multiple traits which extend other traits.
Thanks in advance!
One way to improve your design would be to use a type class instead of inheriting from FirstConverter and SecondConverter. That way you could use multiple conversion functions for the same types and convert between classes you don't control yourself.
One way would be to create a type class which can convert an A into a B :
trait Converter[A, B] {
def convert(a: A): B
}
trait TwoWayConverter[A, B] {
def firstToSecond(a: A)(implicit conv: Converter[A, B]): B = conv.convert(a)
def secondToFirst(b: B)(implicit conv: Converter[B, A]): A = conv.convert(b)
}
trait RoundTripConverter[A, B] extends TwoWayConverter[A, B] {
def firstToFirst(a: A)(implicit convAB: Converter[A, B], convBA: Converter[B, A]) =
secondToFirst(firstToSecond(a))
def secondToSecond(b: B)(implicit convAB: Converter[A, B], convBA: Converter[B, A]) =
firstToSecond(secondToFirst(b))
}
We could create type class instances for the following two classes Foo and Bar similar to your A and B
case class Foo(s: String)
case class Bar(i: Int)
implicit val convFooBarFoor = new Converter[Foo, Bar] {
def convert(foo: Foo) = Bar((foo.s toInt) + 1)
}
implicit val convBarFoo = new Converter[Bar, Foo] {
def convert(bar: Bar) = Foo((bar.i * 2) toString)
}
We then could create a FooBarConverter :
object FooBarConverter extends RoundTripConverter[Foo, Bar]
FooBarConverter firstToSecond Foo("10") // Bar(11)
FooBarConverter secondToFirst Bar(42) // Foo(84)
FooBarConverter firstToFirst Foo("1") // Foo(4)
FooBarConverter secondToSecond Bar(2) // Bar(5)
The only problem is because we can not pass parameters to a trait, we can not limit the types to types with a Converter type class instance. So you can create the StringIntConverter below even if no Converter[String, Int] and/or Convert[Int, String] instances exist.
object StringIntConverter extends TwoWayConverter[String, Int]
You cannot call StringIntConverter.firstToSecond("a") because the firstToSecond method needs the implicit evidence of the two mentioned type class instances.