Search code examples
scalainterfacedesign-principles

Require Function Parameters Implement Method - Scala


Is there a way that I can require objects being passed in to a function implement a core set of methods?

For example, I would like to be able to write a sum method to sum over any iterable of objects that implement the '+' operator.

My initial implementation is as follows

trait addable[T <: addable[T]]{
     def +(other: T): T
 }

 def sum[T <: addable[T]](items: Iterable[T]) = 
     if(items.isEmpty) throw new Exception("Can't sum nothing")
     else items.tail.foldRight(items.head)(_+_) 
          //Starst with the first element and adds all other elements to it

Now this method works, but it's clunky. If I want to have something be summable, I have to explicitly implement addable[T] in every class that I want to sum, not to mention define a bunch of explicit conversions for numeric types and strings.

Is there a way to implement it so that it looks something like this?

def sum[T fulfills addable[T]](items: Iterable[T]) = 
    if(items.isEmpty) throw new Exception("Can't sum nothing")
    else items.tail.foldRight(items.head)(_+_) 

Alternately, is there some design patter that removes the need for this (what I'm doing right now effectively seems to be little more than the adapter pattern)?


Solution

  • A common pattern to do such a thing is to use typeclasses: http://typelevel.org/cats/typeclasses.html

    Here is a sample implementation of Addable typeclass for your use case:

    trait Addable[T] {
      def +(a: T, b: T): T
    }
    
    // Among other places Scala searches for implicits 
    // in the companion objects of the relevant classes.
    // Read more in this answer: https://stackoverflow.com/a/5598107
    object Addable {
    
      // Using context bound notation
      def apply[T : Addable]: Addable[T] = implicitly
    
      // Instance of Addable typeclass for types,
      // that have an instance of the built-in Numeric typeclass
      implicit def numeric[T : Numeric]: Addable[T] = {
        import Numeric.Implicits._
        // This uses Scala 2.12 feature of automatic convertions of lambdas to SAMs
        // You can create an instance of an anonymous subclass in older versions.
        _ + _ 
      }
    
      // Instance of Addable for all kinds of Iterables, 
      // that adds them element by element (like mathematical vectors)
      implicit def iterable[That, T](implicit
        ev: That <:< IterableLike[T, That], // To compute the element type T from That
        cbf: CanBuildFrom[That, T, That],   // To call `map` method
        add: Addable[T]                     // To add elements of the iterable
      ): Addable[That] =
        (a, b) => (a, b).zipped.map(add.+)
    }
    

    Here is a sample implementation of the sum method, that uses this Addable typeclass:

    def sum[T : Addable](items: Iterable[T]): T = items.
      reduceOption(Addable[T].+).
      getOrElse(throw new Exception("Can't sum nothing"))
    

    And some results using it:

    scala> sum(Seq(1.2, 3.4, 5.6))
    res0: Double = 10.2
    
    scala> sum(Seq(Vector(1,2), Vector(4,5), Vector(6,7)))
    res1: scala.collection.immutable.Vector[Int] = Vector(11, 14)