James McCabe
James McCabe

Reputation: 1879

in typeclasses, how to vary operations, with extra parameters?

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

Answers (3)

James McCabe
James McCabe

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

Ryoichiro Oka
Ryoichiro Oka

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

wingedsubmariner
wingedsubmariner

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

Related Questions