Reputation: 1879
In a Scala typeclass, there will be a trait on which operations are defined, e.g. NumberLike
with plus()
and minus()
, Transformer
with transform()
, or AddressLabelMaker
with toLabel()
. It's then possible to extend the trait with typeclass members.
Typically the operations will have the same number of parameters for different members, i.e. the signatures will look very similar. My question is: what happens if a member needs what is essentially the same operation, but with an extra parameter (maybe just an implicit one: something that modifies the operation based on the context) ?
Is there a way to do this without defining a whole new (similar) typeclass?
Upvotes: 4
Views: 114
Reputation: 1879
The way I am solving this is to wrap the parameters in a type Params, which has subtypes as necessary. Params becomes the second type parameter of the typeclass. So now members of the typeclass can have the same signature for operations.
Wonder if this is a common solution?
Here is what an example of what I am getting at. I'm not sure though, maybe this code can be improved.
trait Animal
case class Cat(name: String) extends Animal
case class Person(name: String) extends Animal
case class Silverware(kind: String)
trait FeederParams
case class CatFeederParams() extends FeederParams
case class PersonFeederParams(val silverware: Silverware) extends FeederParams
trait AnimalFeeder[A <: Animal, P <: FeederParams] {
def feed(animal: A)(implicit params: P): Unit
}
implicit object CatFeeder extends AnimalFeeder[Cat, CatFeederParams] {
def feed(cat: Cat)(implicit params: CatFeederParams) =
println(cat.name + " eats cat food!")
}
implicit object PersonFeeder extends AnimalFeeder[Person, PersonFeederParams] {
def feed(person: Person)(implicit params: PersonFeederParams) =
println(person.name + " eats people food with " + params.silverware.kind)
}
def feedAnimal[A <: Animal, P <: FeederParams](a: A)(implicit feeder: AnimalFeeder[A, P], params: P) =
feeder.feed(a)
implicit object personParams extends PersonFeederParams(Silverware("the good silver"))
implicit object catParams extends CatFeederParams()
feedAnimal(Person("John"))
feedAnimal(Cat("Garfield"))
Upvotes: 0
Reputation: 1997
I'm not sure what you mean to question, but if it's about changing what a typeclass' instance's methods do, I think it should be possible, by changing the instance's condition somehow before you call the methods.
Given a type class Printer[A]
:
trait Printer[A] {
def print(a: A): Unit
}
def print[A: Printer](a: A) = implicitly[Printer[A]].print(a)
Defining an implicit Printer[Int]
, named IntPrinter
:
implicit object IntPrinter extends Printer[Int] {
def print(a: Int) = s"integer $a"
}
print(3) //=> integer 3
Modifying IntPrinter
so it can change the text to print:
implicit object IntPrinter extends Printer[Int] {
var text: String = "integer "
def print(a: Int) = s"$text$a"
}
print(3) //=> integer 3
IntPrinter.text = ":"
print(3) //=> :3
Thus you can control the behavior of typeclass` methods.
Upvotes: 0
Reputation: 13667
With an extra explicit parameter? No. Each typeclass instance must have the same interface, that of the typeclass. With an implicit parameter? Yes. You can declare an implicit def
that returns the required implicit but itself requires an implicit. Here is an example to explain:
case class Cat(name: String)
case class Person(name: String)
case class Silverware(kind: String)
implicit object GoodSilver extends Silverware("the good silver")
trait AnimalFeeder[A] {
def feed(animal: A): Unit
}
implicit object CatFeeder extends AnimalFeeder[Cat] {
def feed(cat: Cat) = println(cat.name + " eats cat food!")
}
implicit def personFeeder(implicit silverware: Silverware) =
new AnimalFeeder[Person] {
def feed(person: Person) =
println(person.name + " eats people food with " + silverware.kind)
}
def feedAnimal[A](a: A)(implicit feeder: AnimalFeeder[A]) = feeder.feed(a)
CatFeeder
provides an implicit that cat feed any Cat
. personFeeder
is a def
that can create an implicit to feed a Person
, but requires an implicit Silverware
. So in an invocation like:
feedAnimal(Person("John"))
The compiler will search for an implicit AnimalFeeder[Person]
, will find personFeeder
, and then will search for an implicit Silverware
, finally finding GoodSilver
.
It is important to note that personFeeder
is not an implicit conversion. Despite being a method from Silverware
to AnimalFeeder[Person]
it will never implicitly convert Silverware
. This is because its parameter is marked implicit
, implicit conversions must have their parameter explicit.
Upvotes: 2