Jeśli chcę programować w stylu „funkcjonalnym”, czym zastąpiłbym interfejs?
interface IFace
{
string Name { get; set; }
int Id { get; }
}
class Foo : IFace { ... }
Może Tuple<>
?
Tuple<Func<string> /*get_Name*/, Action<String> /*set_Name*/, Func<int> /*get_Id*/> Foo;
Jedynym powodem, dla którego używam interfejsu jest przede wszystkim to, że chcę, aby zawsze były dostępne pewne właściwości / metody.
Edycja: Więcej szczegółów na temat tego, co myślę / próbuję.
Powiedzmy, że mam metodę, która ma trzy funkcje:
static class Blarf
{
public static void DoSomething(Func<string> getName, Action<string> setName, Func<int> getId);
}
Z instancją Bar
mogę użyć tej metody:
class Bar
{
public string GetName();
public void SetName(string value);
public int GetId();
}
...
var bar = new Bar();
Blarf.DoSomething(bar.GetName, bar.SetName, bar.GetId);
Ale to trochę kłopotliwe, ponieważ muszę wspomnieć bar
trzy razy w jednym telefonie. Ponadto, tak naprawdę nie zamierzam, aby osoby dzwoniące dostarczały funkcje z różnych instancji
Blarf.DoSomething(bar1.GetName, bar2.SetName, bar3.GetId); // NO!
W C # interface
jest jednym ze sposobów radzenia sobie z tym; ale to wydaje się być podejściem bardzo obiektowym. Zastanawiam się, czy istnieje bardziej funkcjonalne rozwiązanie: 1) przekaż grupę funkcji razem i 2) upewnij się, że funkcje są ze sobą odpowiednio powiązane.
Odpowiedzi:
Nie traktuj programowania funkcjonalnego jako cienkiego forniru w stosunku do programowania imperatywnego; jest coś więcej niż tylko różnica składniowa.
W takim przypadku masz
GetID
metodę, która implikuje unikalność obiektów. To nie jest dobre podejście do pisania programów funkcjonalnych. Być może mógłbyś powiedzieć nam o problemie, który próbujesz rozwiązać, a my moglibyśmy udzielić Ci bardziej znaczących porad.źródło
Haskell i jego pochodne mają klasy, które są podobne do interfejsów. Choć brzmi to tak, jakbyś pytał o sposób enkapsulacji, to pytanie dotyczy systemów typów. System typów Hindley Milner jest powszechny w językach funkcjonalnych i ma typy danych, które robią to dla ciebie w różny sposób w różnych językach.
źródło
Istnieje kilka sposobów, aby umożliwić funkcji obsługę wielu wejść.
Pierwszy i najczęstszy: polimorfizm parametryczny.
Pozwala to funkcji działać na dowolne typy:
Co jest miłe, ale nie zapewnia dynamicznej wysyłki interfejsów OO. Do tego Haskell ma klasy, Scala ma implikacje itp
Pomiędzy tymi dwoma mechanizmami możesz wyrażać różnego rodzaju złożone i interesujące zachowania na swoich typach.
źródło
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.
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:
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.
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.
źródło
Myślę, że będzie to zależało od języka. Pochodzę z zawziętego pochodzenia. W wielu przypadkach interfejsy ze stanem do pewnego stopnia psują model funkcjonalny. Na przykład CLOS jest tam, gdzie LISP jest mniej funkcjonalny i bliższy imperatywnemu językowi. Zasadniczo wymagane parametry funkcji w połączeniu z metodami wyższego poziomu są prawdopodobnie tym, czego szukasz.
źródło