Last active
August 29, 2015 14:19
-
-
Save piotrMocz/1b88950a62bc7a4776bb to your computer and use it in GitHub Desktop.
Typeclasses vs. Overloading vs. Inheritance vs. Monkeypatching
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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