Skip to content

Instantly share code, notes, and snippets.

@piotrMocz
Last active August 29, 2015 14:19
Show Gist options
  • Save piotrMocz/1b88950a62bc7a4776bb to your computer and use it in GitHub Desktop.
Save piotrMocz/1b88950a62bc7a4776bb to your computer and use it in GitHub Desktop.
Typeclasses vs. Overloading vs. Inheritance vs. Monkeypatching
/**
* Przykład w Scali, bo to jedyny znany mi język, który udostępnia i dziedziczenie, i typeclassy.
* Jest prosty, więc nie trzeba znać ani troche Scali :)
*
* tl;dr Typeclassy są super, ale alternatywą są interfejsy, a nie przeciążanie.
*/
// sytuacja trochę jak z Accelerate'em: biblioteka do algebry liniowej, czyli udostępnia jakiś typ macierzowy (A jest parametrem typu):
class Matrix[A](values: Array[A], rows: Int, cols: Int)
// pytanie: czy to może być dowolne A? No nie, bo jeśli chcemy udostępniać operacje na macierzach, to typ A musi dać się dodawać, odejmować etc.
// Pomysł 1: możemy robić przeciążenia:
class Matrix[A](values:Array[A], rows: Int, cols: Int) {
def +(that: Matrix[Int]): Matrix[Int] = {
if (type(A) != Int) throw new IllegalArgumentException // (słabe rozwiązanie i to bardzo)
if (rows != that.rows && cols != that.cols) throw new DimensionMismatchException
new Matrix[Int](values.zipWith(+)(that.values), rows, cols)
}
// i takie samo przeciążenie dla Double, Long, etc.
}
// problem z przeciążaniem zaczyna się, jak ktoś używa naszej biblioteki i chce trzymać w macierzy obiekty typu, np. Dog
// nie jest w stanie dołożyć nowego przeciążenia, bo nie ma dostępu do naszej biblioteki => co kończy dowód => LIPA, nie użyje
// Ok, no to trzeba inaczej.
// Pomysł 2: Dziedziczenie!
trait MathLike { // trait to taki interfejs
def +(m: MathLike)
def -(m: MathLike) // itd...
}
class Matrix(values: Array[MathLike], rows: Int, cols: Int) {
def +(that: Matrix): Matrix = {
if (rows != that.rows && cols != that.cols) throw new DimensionMismatchException // ok, to musi być tak czy owak
new Matrix(values.zipWith(+)(that.values), rows, cols) // i teraz ok, bo wiemy, że każdy element w values ma metodę "+", "-", etc.
}
}
// teraz jak chcemy trzymać obiekty typu Dog w tablicy, to przy tworzeniu klasy musimy zrobić class Dog extends MathLike i zaimplementować
// odpowiednie metody.
// Problem: co, jeśli Dog też nie jest nasz? Again: LIPA (można pisać wrappery, więc nie ma aż takiej lipy jak w poprzednim punkcie)
// oczywiście Dog to głupi przykład, ale jak się pracuje z bibliotekami, to przykłady można mnożyć
// Tak czy owak, to rozwiązanie jest drugie pod względem fajności.
// Pomysł 3: Monkeypatching
// To jest bardzo kiepskie. M.in. jeśli dwa moduły monkey-patchują tę samą metodę, to są takie race-conditions.
// Poza tym bardzo łatwo w ten sposób rozwalić działanie bibliotek używanych przez moduł.
// Nie mam dobrego, krótkiego przykładu na to, ale w internetach jest sporo napisane.
class Matrix... NOPE.
// Pomysł 4: typeclassy!
class Matrix[A: Num](values: Array[A], rows: Int, cols: Int) {
def +(that: Matrix[A: Num]): Matrix = {
if (rows != that.rows && cols != that.cols) throw new DimensionMismatchException
new Matrix(values.zipWith(Num.+)(that.values), rows, cols) // znowu: wymuszamy, żeby typ miał metody +, - etc.
}
}
// no i pięknie! teraz możemy:
// a) napisać generyczną metodę "+", która jedyne, co wymusza na typie, to żeby była dla niego dostępna instancja klasy Num,
// z której są metody "+", "-" i inne
// b) trzymać w macierzy jakikolwiek typ nam się podoba (nasz czy nie nasz -- nie ma znaczenia, teraz to mogą być nawet połączenia do bazy danych z JBDC)
// o ile napiszemy dla niego instancję Num (co robi się raz, w naszym własnym kodzie)
// c) zrobić tą instancję tylko w naszym kodzie (jeśli mamy scoped imports to w ogóle możemy mieć ją widoczną tylko w jednej metodzie/klasie)
// przez co nikomu nic nie zepsujemy
//
// Teraz jeśli psy trzeba dodawać, to musi być tego świadom tylko ten, kto chce je dodawać. Nie musi o tym wiedzieć ani pies,
// ani wszyscy inni ludzie, którzy korzystają z psa. I być może to jest najważniejsze: żeby każdy musiał wiedzieć dokładnie tyle, ile
// potrzebuje.
// No i to jest rozwiązanie prawdziwie generyczne, bomba.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment