How to make a type-class extend, or utilize, another type-class?

Hi.

I’m working with a foreign API for which I’m using type-classes to provide polymorphism.

I’m using the SomeEntity type-class to provide access to common properties such as id and created.
Now, I’d like to have a common type-class, SomeCompany, to provide common getters for the MyCustomer and MySupplier classes (in which the API does not provide a common base-class for).

class MyPerson {
	var id = 0
	var created = new java.util.Date()
	var name: String = "Hi"
}

class MyCustomer {
	var id = 0
	var created = new java.util.Date()
	var organizationalNumber = "002222"
	var customerNumber = "111"
}

class MySupplier {
	var id = 0
	var created = new java.util.Date()
	var organizationalNumber = "9999"
	var supplierNumber = 33
}

trait SomeEntity[E] {
	def getId(e: E): Int
	def getCreated(e: E): java.util.Date
}

object SomeEntity {
	implicit object PersonEntity extends SomeEntity[MyPerson] {
		override def getId(e: MyPerson) = e.id
		override def getCreated(e: MyPerson) = e.created
	}
	implicit object CustomerEntity extends SomeEntity[MyCustomer] {
		override def getId(e: MyCustomer) = e.id
		override def getCreated(e: MyCustomer) = e.created
	}
	implicit object SupplierEntity extends SomeEntity[MySupplier] {
		override def getId(e: MySupplier) = e.id
		override def getCreated(e: MySupplier) = e.created
	}
	object ops {
		implicit class SomeEntityOps[E: SomeEntity](e: E) {
			def getId = implicitly[SomeEntity[E]].getId(e)
			def getCreated = implicitly[SomeEntity[E]].getCreated(e)
		}
	}
}

class MyConsumer[E: SomeEntity](e: E) {
	import SomeEntity.ops._
	e.getId
}

trait SomeCompany[E] extends SomeEntity[E] {
	def getOrganizationalNumber(e: E): String
}

object SomeCompany {
	implicit object CompanyCustomer extends SomeCompany[MyCustomer] {
/* // If I uncomment this it works, but I want to be able to "inherit" these from SomeEntity.
		override def getId(e: MyCustomer) = e.id
		override def getCreated(e: MyCustomer) = e.created
*/
		override def getOrganizationalNumber(e: MyCustomer) = e.organizationalNumber
	}
	object ops {
		implicit class SomeCompanyOps[E: SomeCompany](e: E) {
			def getOrganizationalNumber = implicitly[SomeCompany[E]].getOrganizationalNumber(e)
		}
	}
}

class CompanyConsumer[E: SomeCompany](e: E) {
	import SomeCompany.ops._
	e.getOrganizationalNumber
}

The compile-error is:

Error:(144, 18) object creation impossible, since:
it has 2 unimplemented members.
/** As seen from object CompanyCustomer, the missing signatures are as follows.
 *  For convenience, these are usable as stub implementations.
 */
  def getCreated(e: com.visena.integration.tripletex.customer.MyCustomer): java.util.Date = ???
  def getId(e: com.visena.integration.tripletex.customer.MyCustomer): Int = ???
	implicit object CompanyCustomer extends SomeCompany[MyCustomer] {

What do I have to do to provide the necessary implicits for SomeCompany to be able to utilize the SomeEntity type-class for CompanyConsumer to be able to call e.getOrganizationalNumber?

Thanks.

Keep the type classes separate/independent and “bundle” them in the client constraints?

trait SomeEntity[E] {
  def getId(e: E): Int
  def getCreated(e: E): java.util.Date
}

trait SomeCompany[E] {
  def getOrganizationalNumber(e: E): String
}

class CompanyConsumer[E : SomeEntity : SomeCompany](e: E) {
  import SomeEntity.ops._
  import SomeCompany.ops._
  e.getOrganizationalNumber
  e.getId  
}
1 Like

Yea, that works, thanks!
However, I somewhat think that SomeCompany isA SomeEntity, so using inheritance makes sence.

This might work:

trait CustomerEntity extends SomeEntity[MyCustomer] {
  override def getId(e: MyCustomer) = e.id
  override def getCreated(e: MyCustomer) = e.created
}
implicit object CustomerEntity extends CustomerEntity

trait SomeCompany[E] extends SomeEntity[E] {
  def getOrganizationalNumber(e: E): String
}

implicit object CompanyCustomer extends SomeCompany[MyCustomer] with CustomerEntity {
  override def getOrganizationalNumber(e: MyCustomer) = e.organizationalNumber
}

class CompanyConsumer[E : SomeCompany](e: E) {
  import SomeEntity.ops._
  import SomeCompany.ops._
  e.getOrganizationalNumber
  e.getId
}

…but in a type class context, a rather vague hunch about an isA relationship doesn’t feel like a sufficiently compelling reason to pull inheritance in - YMMV. (And I’m not 100% sure there are no conflicting implicits issues with CustomerEntity/CompanyCustomer lurking in the dark…)

Yea, and in this case it doesn’t seem to reduce the amount of code needed either.