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)));
public static <T> T sum(List<T> ts) { ... }
T + T ???
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
unless...
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 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.
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) { ... }
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)
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.
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.
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.
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); }
}
Programming is like sex. One mistake and you have to support it for the rest of your life.
Michael Sinz
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€
do as you would do in Haskell
do as Dijkstra would approve