to Type a Class


  • ponies
  • cat pics
  • general awesomeness
    (flying choppers, smoke alarms...)

  • nice types
  • live coding!

payday


class Money {
    int euro;
    Money(int euro) { this.euro = euro; }
}

public static Money sum(List<Money> xs) { ... }

Money total = sum(Arrays.asList(new Money(1), new Money(2)));
                    

general-purpose use FTW!


public static <T> T sum(List<T> ts) { ... }
                    

T + T ???

the O-O way

implement interface


interface Addable<T> {
    T add(T t);
}

public static <T extends Addable<T>> T sum(List<T> ts) {
    T ret = ts.get(0);
    for (int i = 1; i < ts.size(); ++i) { ret = ret.add(ts.get(i)); }
    return ret;
}
                    

small print: don't call with empty list

Profit!

unless...

  • the creator of the Money class did not implement Addable?
    like with class Integer...
  • add conflicts with some other method? Java does not offer mechanisms to get around this. Some langs do.
  • more than one valid ways to add Money? or: class Foo implements Bar<A>, Bar<B> ?
    works on my machine

right tool for the job?


The most disastrous thing that you can ever learn is your first programming language.
Alan Kay

Always consider what you would do in Haskell
http://akisaarinen.fi/blog/2012/09/10/haskellify-your-scala/

REPL! (or look down...)


Prelude> :t sum
sum :: Num a => [a] -> a
                        

Prelude> :info Num
class Num a where
  (+) :: a -> a -> a
  ...
                        

ah, Num knows how to add two as...

...but is this just like Addable in Java?

...not quite...


interface Addable<T> {
    T add(T t);
}

public static <T extends Addable<T>> T sum(List<T> ts) {
                        

Haskell?

Haskell doesn't have classes
as we know them from other languages.

And no inheritance.

In Haskell, a
separate concept (Num)
defines how to add things.

One or more definitions of adding can be
separately
defined for a custom type Money.

Function arguments can be restricted with these
type classes.

Haskellify your Java


interface Adder<T> {
    T add(T t1, T t2); // Look, mom, now with two arguments!
}

public static <T> T sum(List<T> ts, Adder<T> adder) {
    T ret = ts.get(0);
    for (int i = 1; i < ts.size(); ++i) { ret = adder.add(ret, ts.get(i)); }
    return ret;
}
                        

now adding is separated from the class definition. Ha!

...but the trade-off is an additional function parameter...

in Java, these two strategies are often combined,
e.g. Collections.sort


public static <T extends Comparable<? super T>> void sort(List<T> list) { ... }

public static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
                            

Scala?

REPL! (or look down...)


scala> def sum[T](ts: List[T]): T = ???
                        

same problem as in Java, forward!


scala> trait Adder[T] { def add(t1: T, t2: T): T }
defined trait Adder
scala> def sum[T: Adder](ts: List[T]) = ts.reduce(implicitly[Adder[T]].add)
sum: [T](ts: List[T])(implicit evidence$1: Adder[T])T
                        

looks the same as in Java, but but...


scala> case class Money(euro: Int)
defined class Money
scala> implicit object MoneyAdder extends Adder[Money] {
    def add(t1: Money, t2: Money) = Money(t1.euro + t2.euro)
}
defined module MoneyAdder
scala> sum(List(Money(1), Money(2)))
res1: Money = Money(3)
                        
  • Adder is defined outside Money
  • no manual parameter passing

desugar

"To translate the source code of a computer program (or its specification) into a more syntactically rigorous form"

def sum[T: Adder](ts: List[T]) = ts.reduce(implicitly[Adder[T]].add)
                    
==

def sum[T](ts: List[T])(implicit e: Adder[T]) = ts.reduce(implicitly[Adder[T]].add)
                    
==

def sum[T](ts: List[T])(implicit e: Adder[T]) = ts.reduce(e.add)
                    

In Scala, the strategy is passed as a parameter just like in Java, but the compiler does it for you!

Trivial to use a custom implementation instead of the default.

default?

In Scala, the compiler implicitly passes suitable objects to functions. Often we don't need to care since the standard library contains many default implicits.

In Haskell, we use imports and hiding to select visible type class instances.

Both have many sensible defaults.

in the Java-land?

we can select one of "type class instances" to be the "default instance" also in Java


class MoneyAdder implements Adder<Money> {
    public Money add(Money t1, Money t2) { ... }
}
class AlternativeMoneyAdder implements Adder<Money> {
    public Money add(Money t1, Money t2) { ... }
}
class Money implements Addable<Money> {
    private static final DEFAULT_ADDER = new MoneyAdder();
    public Money add(Money another) {
        return DEFAULT_ADDER.add(this, another);
    }
}
                    

a more familiar example may be Comparator/Comparable


public interface Comparator<T> {
    int compare(T o1, T o2);
}
public interface Comparable<T> {
    int compareTo(T o);
}
                    

(ever wondered why there's "two of them"?)

Comparator is kind of a
type class.

Each implements Comparator is like an instance of this type class.

Comparable is a way to pick a default instance from the Comparator type class.

always a default?

many times there's no sensible default,
so better not to pick one.


class Employee {
    String firstName;
    String lastName;
}
class EmployeeComparator1 implements Comparator<Employee> {
    int compare(T o1, T o2) { return o1.firstName.compareTo(o2.firstName); }
}
class EmployeeComparator2 implements Comparator<Employee> {
    int compare(T o1, T o2) { return o1.lastName.compareTo(o2.lastName); }
}
                    

Java: toString

  • one of Java screwups
  • every type has an implicit default instance of "toString type class"
Programming is like sex. One mistake and you have to support it for the rest of your life.
Michael Sinz

Haskell: Show

REPL! (or look down...)


Prelude> data Money = Money Int
Prelude> print (Money 42)

<interactive>:2:1:
    No instance for (Show Money) arising from a use of `print'
    Possible fix: add an instance declaration for (Show Money)
    In the expression: print (Money 42)
    In an equation for `it': it = print (Money 42)
Prelude> instance Show Money where show (Money a) = "I've got " ++ show a ++ "€"
Prelude> print (Money 42)
I've got 42€
                        

conclusion?

  • always try to make type class instances (Comparator)
  • be wary of lifting one to a default instance (Comparable)
  • do as you would do in Haskell
  • do as Dijkstra would approve

thanks.

- Jüppe