Podstawowa zasada jest taka, że funkcje programowania FP wykonują tę samą pracę, co obiekty w programowaniu OO. Możesz wywoływać ich metody (cóż, w każdym razie metodę „call”), a one reagują zgodnie z niektórymi enkapsulowanymi wewnętrznymi regułami. W szczególności każdy przyzwoity język FP pozwala mieć w swojej funkcji „zmienne instancji” z zamknięciami / zasięgiem leksykalnym.
var make_OO_style_counter = function(){
return {
counter: 0
increment: function(){
this.counter += 1
return this.counter;
}
}
};
var make_FP_style_counter = function(){
var counter = 0;
return fucntion(){
counter += 1
return counter;
}
};
Teraz następne pytanie brzmi: co rozumiesz przez interfejs? Jednym z podejść jest używanie interfejsów nominalnych (jest zgodny z interfejsem, jeśli mówi, że tak robi) - ten zwykle zależy w dużej mierze od używanego języka, więc pozostawmy to na później. Innym sposobem zdefiniowania interfejsu jest sposób strukturalny, sprawdzający, jakie parametry odbiera i zwraca. Jest to rodzaj interfejsu, który zwykle widzisz w dynamicznych językach z kaczymi literami i bardzo dobrze pasuje do wszystkich FP: interfejs to tylko typy parametrów wejściowych do naszych funkcji i typy, które zwracają, więc wszystkie funkcje pasujące do poprawne typy pasują do interfejsu!
Dlatego najprostszym sposobem przedstawienia obiektu pasującego do interfejsu jest po prostu posiadanie grupy funkcji. Zwykle omija się brzydotę polegającą na przekazywaniu funkcji osobno, umieszczając je w jakimś rejestrze:
var my_blarfable = {
get_name: function(){ ... },
set_name: function(){ ... },
get_id: function(){ ... }
}
do_something(my_blarfable)
Używanie nagich funkcji lub zapisów funkcji znacznie rozwiąże większość typowych problemów w sposób „beztłuszczowy” bez ton płyty kotłowej. Jeśli potrzebujesz czegoś bardziej zaawansowanego, czasami języki oferują dodatkowe funkcje. Jednym z przykładów wspomnianych osób są klasy typu Haskell. Klasy typów zasadniczo kojarzą typ z jednym z tych rekordów funkcji i pozwalają pisać rzeczy, dzięki czemu słowniki są niejawne i automatycznie przenoszone do funkcji wewnętrznych, stosownie do przypadku.
-- Explicit dictionary version
-- no setters because haskell doesn't like mutable state.
data BlargDict = BlargDict {
blarg_name :: String,
blarg_id :: Integer
}
do_something :: BlargDict -> IO()
do_something blarg_dict = do
print (blarg_name blarg_dict)
print (blarg_id blarg_dict)
-- Typeclass version
class Blargable a where
blag_name :: a -> String
blag_id :: a -> String
do_something :: Blargable a => a -> IO
do_something blarg = do
print (blarg_name blarg)
print (blarg_id blarg)
Jedną ważną rzeczą, na którą należy zwrócić uwagę w przypadku klas typów, jest to, że słowniki są powiązane z typami, a nie z wartościami (jak to, co dzieje się w słowniku i wersjach OO). Oznacza to, że system typów nie pozwala mieszać „typów” [1]. Jeśli potrzebujesz listy „blargabli” lub funkcji binarnej przestawiającej się na blargable, wówczas typy klas będą ograniczać wszystko do tego samego typu, podczas gdy podejście słownikowe pozwoli ci mieć blargable różnego pochodzenia (która wersja jest lepsza, zależy od tego, kim jesteś) robić)
[1] Istnieją zaawansowane sposoby robienia „typów egzystencjalnych”, ale zwykle nie są warte kłopotu.