Powrót uważany za szkodliwy? Czy kod może funkcjonować bez niego?

45

OK, więc tytuł to trochę clickbaity, ale tak na serio, jestem na dobrej drodze , nie proś przez chwilę kick. Podoba mi się, jak zachęca metody do używania jako wiadomości w sposób zorientowany obiektowo. Ale to ma dokuczliwy problem, który grzechotał mi w głowie.

Zacząłem podejrzewać, że dobrze napisany kod może jednocześnie przestrzegać zasad OO i zasad funkcjonalnych. Staram się pogodzić te pomysły z wielkim punktem spornym, na którym wylądowałem return.

Funkcja czysta ma dwie cechy:

  1. Wielokrotne wywoływanie tego samego wejścia zawsze daje ten sam rezultat. Oznacza to, że jest niezmienny. Jego stan jest ustawiany tylko raz.

  2. Nie powoduje żadnych skutków ubocznych. Jedyną zmianą wywołaną przez wywołanie tego jest wynik.

Jak więc przejść na temat bycia czysto funkcjonalnym, jeśli przysiągłeś, że używasz returnswojego sposobu komunikowania wyników?

Tell, nie pytaj prace pomysł za pomocą tego, co niektórzy uważają efekt uboczny. Kiedy mam do czynienia z przedmiotem, nie pytam go o jego stan wewnętrzny. Mówię mu, co muszę zrobić, i wykorzystuje swój stan wewnętrzny, aby dowiedzieć się, co zrobić z tym, co powiedziałem. Kiedy to powiem, nie pytam, co to zrobiło. Po prostu oczekuję, że coś to zrobiło z tym, co mu kazano.

Myślę, że Tell, Don't Ask to coś więcej niż inna nazwa enkapsulacji. Kiedy używam, returnnie mam pojęcia, co mnie wezwało. Nie mogę mówić, że to protokół, muszę go zmusić, by zajął się moim protokołem. Co w wielu przypadkach wyraża się jako stan wewnętrzny. Nawet jeśli to, co jest widoczne, nie jest dokładnie stanem, zwykle jest to tylko kilka obliczeń przeprowadzonych na argumencie stanu i wejściowym. Posiadanie interfejsu umożliwiającego reakcję daje szansę na zamaskowanie wyników w coś bardziej znaczącego niż stan wewnętrzny lub obliczenia. To jest przekazywanie wiadomości . Zobacz ten przykład .

Dawno temu, kiedy dyski faktycznie miały dyski, a napędem kciuka było to, co robiłeś w samochodzie, gdy koło było zbyt zimne, aby dotykać palcami, dowiedziałem się, jak denerwujące osoby uważają funkcje, które mają parametry. void swap(int *first, int *second)wydawało się to bardzo przydatne, ale zachęcano nas do pisania funkcji, które zwracały wyniki. Wziąłem to sobie do serca z wiarą i zacząłem za tym podążać.

Ale teraz widzę ludzi budujących architektury, w których obiekty pozwalają, w jaki sposób zostały zbudowane, kontrolować, gdzie wysyłają swoje wyniki. Oto przykładowa implementacja . Wstrzyknięcie obiektu portu wyjściowego ponownie przypomina trochę pomysł parametru out. Ale w ten sposób obiekty „nie mów” pytają inne obiekty o tym, co zrobiły.

Kiedy po raz pierwszy dowiedziałem się o skutkach ubocznych, pomyślałem o tym jak o parametrze wyjściowym. Powiedziano nam, aby nie zaskakiwać ludzi tym, że niektóre prace odbywają się w zaskakujący sposób, to znaczy nie przestrzegając return resultkonwencji. Teraz jestem pewien, że wiem, że jest stos równoległych problemów z asynchronicznym wątkiem, z którymi psują się skutki uboczne, ale powrót jest tak naprawdę tylko konwencją, w której pozostawiasz wynik wypchnięty na stos, więc niezależnie od tego, jak go nazwiesz, możesz go później usunąć. To wszystko, czym tak naprawdę jest.

O co tak naprawdę staram się zapytać:

Czy jest returnto jedyny sposób, aby uniknąć tych wszystkich niedoli związanych z efektami ubocznymi i uzyskać bezpieczeństwo nici bez zamków itp. Czy też mogę powiedzieć, nie pytać w czysto funkcjonalny sposób?

candied_orange
źródło
3
Jeśli zdecydujesz się nie ignorować rozdzielania zapytań poleceń, czy uważasz, że problem został rozwiązany?
rwong
30
Weź pod uwagę, że znalezienie się na nogach może wskazywać, że angażujesz się w projektowanie oparte na dogmatach, zamiast uzasadniać zalety i wady każdej konkretnej sytuacji.
Blrfl
3
Zasadniczo, gdy ludzie mówią, że „powrót” jest szkodliwy, mówią, że jest to sprzeczne z programowaniem strukturalnym, niefunkcjonalnym i pojedynczą instrukcją return na końcu procedury (i być może na końcu obu stron bloku if / else, który blokuje sam jest ostatnim elementem) nie jest w tym uwzględniony.
Random832
16
@jameslarge: Fałszywa dychotomia. Nie pozwalanie się kierować projektami przez dogmatyczne myślenie to nie to samo, co Dziki Zachód / wszystko idzie w kierunku, o którym mówisz. Chodzi o to, aby dogmat nie przeszkadzał w tworzeniu dobrego, prostego i oczywistego kodu. Prawo powszechne jest dla niedostatków; kontekst jest dla królów.
Nicol Bolas,
4
Jedno pytanie jest dla mnie niejasne: mówisz, że poprzysiągłeś, używając „return”, ale o ile mogę powiedzieć, nie powiążesz tego wprost z innymi pojęciami ani nie powiesz, dlaczego to zrobiłeś. W połączeniu z definicją funkcji czystej, w tym generowania wyniku, tworzysz coś, czego nie można rozwiązać.
Danikov

Odpowiedzi:

96

Jeśli funkcja nie wywołuje żadnych skutków ubocznych i niczego nie zwraca, oznacza to, że funkcja jest bezużyteczna. To takie proste.

Ale myślę, że możesz użyć niektórych kodów, jeśli chcesz postępować zgodnie z literą zasad i zignorować leżące u ich podstaw rozumowanie. Na przykład użycie parametru out oznacza, że ​​nie używa on znaku powrotu. Ale nadal robi dokładnie to samo, co zwrot, tylko w bardziej zawiły sposób. Więc jeśli uważasz, że zwrot jest z jakiegoś powodu zły , to użycie parametru out jest wyraźnie złe z tych samych podstawowych powodów.

Możesz użyć bardziej skomplikowanych kodów. Np. Haskell jest znany ze sztuczki monadowej IO, w której możesz mieć skutki uboczne w praktyce, ale wciąż nie mówiąc ściśle, mają skutki uboczne z teoretycznego punktu widzenia. Kolejnym trikiem jest styl kontynuacji przekazywania, który pozwala uniknąć zwrotów w cenie zamiany kodu w spaghetti.

Najważniejsze jest to, że w przypadku braku głupich sztuczek dwie zasady funkcji wolnych od skutków ubocznych i „brak zwrotów” są po prostu niezgodne. Co więcej, zwrócę uwagę, że obie są naprawdę złymi zasadami (naprawdę dogmaty), ale to inna dyskusja.

Reguły takie jak „mów, nie pytaj” lub „żadnych skutków ubocznych” nie mogą być stosowane uniwersalnie. Zawsze musisz wziąć pod uwagę kontekst. Program bez skutków ubocznych jest dosłownie bezużyteczny. Nawet czyste języki funkcjonalne to potwierdzają. Starają się raczej oddzielić czyste części kodu od tych z efektami ubocznymi. Celem monad Stanowych lub IO w Haskell nie jest to, że unikasz skutków ubocznych - ponieważ nie możesz - ale to, że obecność efektów ubocznych jest wyraźnie oznaczona przez sygnaturę funkcji.

Zasada tell-dont-ask dotyczy innego rodzaju architektury - stylu, w którym obiekty w programie są niezależnymi „aktorami” komunikującymi się ze sobą. Każdy aktor jest zasadniczo autonomiczny i zamknięty. Możesz wysłać mu wiadomość, która decyduje, jak na nią zareagować, ale nie możesz zbadać wewnętrznego stanu aktora z zewnątrz. Oznacza to, że nie można stwierdzić, czy komunikat zmienia wewnętrzny stan aktora / obiektu. Stan i skutki uboczne są ukryte w projekcie .

JacquesB
źródło
20
@CandiedOrange: Czy metoda ma skutki uboczne bezpośrednio lub pośrednio, chociaż wiele warstw wywołań niczego nie zmienia koncepcyjnie. To wciąż efekt uboczny. Ale jeśli chodzi o to, że skutki uboczne występują tylko przez wstrzyknięte obiekty, więc kontrolujesz, jakie rodzaje efektów ubocznych są możliwe, to brzmi to jak dobry projekt. Po prostu nie jest to efekt uboczny. Jest dobry OO, ale nie jest wyłącznie funkcjonalny.
JacquesB
12
@LightnessRacesinOrbit: grep w trybie cichym ma kod powrotu procesu, który zostanie użyty. Gdyby tego nie miał, byłby naprawdę bezużyteczny
unperson325680
10
@progo: Kod powrotu nie jest efektem ubocznym; to efekt. Wszystko, co nazywamy efektem ubocznym, nazywa się efektem ubocznym, ponieważ nie jest to kod powrotu;)
Lekkość ściga się z Moniką
8
@Cubic o to chodzi. Program bez skutków ubocznych jest bezużyteczny.
Jens Schauder
5
@mathreadler To efekt uboczny :)
Andres F.
32

Powiedz, nie pytaj zawiera kilka podstawowych założeń:

  1. Używasz przedmiotów.
  2. Twoje obiekty mają stan.
  3. Stan twoich obiektów wpływa na ich zachowanie.

Żadna z tych rzeczy nie dotyczy czystych funkcji.

Sprawdźmy więc, dlaczego mamy zasadę „mów, nie pytaj”. Ta reguła jest ostrzeżeniem i przypomnieniem. Można to streścić w następujący sposób:

Pozwól swojej klasie zarządzać własnym stanem. Nie pytaj go o jego stan, a następnie podejmij działania w oparciu o ten stan. Powiedz klasie, co chcesz i pozwól jej zdecydować, co robić na podstawie własnego stanu.

Innymi słowy, klasy ponoszą wyłączną odpowiedzialność za utrzymanie własnego stanu i działanie na nim. O to właśnie chodzi w enkapsulacji.

Od Fowlera :

Tell-Don't-Ask to zasada, która pomaga ludziom pamiętać, że orientacja obiektowa polega na łączeniu danych z funkcjami, które działają na tych danych. Przypomina nam, że zamiast pytać obiekt o dane i działać na podstawie tych danych, powinniśmy zamiast tego powiedzieć obiektowi, co ma robić. Zachęca nas to do przeniesienia zachowania do obiektu, aby przejść z danymi.

Powtarzając, nic z tego nie ma nic wspólnego z funkcjami czystymi, a nawet nieczystymi, chyba że narażasz stan klasy na świat zewnętrzny. Przykłady:

Naruszenie TDA

var color = trafficLight.Color;
var elapsed = trafficLight.Elapsed;
If (color == Color.Red && elapsed > 2.Minutes)
    trafficLight.ChangeColor(green);

To nie jest naruszenie TDA

var result = trafficLight.ChangeColor(Color.Green);

lub

var result = await trafficLight.ChangeColorWhenReady(Color.Green);     

W obu ostatnich przykładach sygnalizacja świetlna zachowuje kontrolę nad swoim stanem i działaniami.

Robert Harvey
źródło
Chwileczkę, zamknięcia mogą być czyste. mają stan (nazywają to zakresem leksykalnym). a zakres leksykalny może wpływać na ich zachowanie. Czy jesteś pewien, że TDA ma znaczenie tylko wtedy, gdy w grę wchodzą obiekty?
candied_orange
7
Zamknięcia @CandiedOrange są czyste, tylko jeśli nie zmodyfikujesz powiązań zamkniętych. W przeciwnym razie tracisz przejrzystość referencyjną podczas wywoływania funkcji zwróconej po zamknięciu.
Jared Smith
1
@JaredSmith i czy to nie to samo, gdy mówisz o przedmiotach? Czy to nie jest po prostu kwestia niezmienności?
candied_orange
1
@bdsl - A teraz mimowolnie wskazałeś dokładnie, gdzie każda dyskusja tego typu idzie z przykładem trafficLight.refreshDisplay. Przestrzeganie zasad prowadzi do bardzo elastycznego systemu, którego nikt inny niż oryginalny programista nie rozumie. Założę się nawet, że po kilku latach przerwy nawet oryginalny programista prawdopodobnie nie zrozumie, co zrobili.
Dunk
1
„Głupie”, jak w „Nie otwieraj innych obiektów i nie patrz na ich wnętrzności, ale zamiast tego rozlej swoje wnętrzności (jeśli masz jakieś) na metody innych obiektów; to jest Droga” - spokojnie.
Joker_vD
30

Kiedy mam do czynienia z przedmiotem, nie pytam go o jego stan wewnętrzny. Mówię mu, co muszę zrobić, i wykorzystuje swój stan wewnętrzny, aby dowiedzieć się, co zrobić z tym, co powiedziałem.

Nie pytasz tylko o jego stan wewnętrzny , nie pytasz też, czy ma w ogóle stan wewnętrzny .

Powiedz też , nie pytaj! nie nie oznacza, nie dostaję wynik w postaci wartości zwracanej (dostarczonych przez returnoświadczenie wewnątrz metody). To po prostu sugeruje , że nie obchodzi mnie, jak to robisz, ale rób to! . A czasami chcesz natychmiast wynik przetwarzania ...

Timothy Truckle
źródło
1
CQS sugerowałoby jednak, że modyfikacja stanu i uzyskiwanie wyników powinny być rozdzielone
jk.
7
@jk. Jak zwykle: na ogół powinieneś oddzielić zmianę stanu i zwracanie wyniku, ale w rzadkich przypadkach istnieją uzasadnione powody, aby to połączyć. Np .: next()metoda iteratorów powinna nie tylko zwrócić bieżący obiekt, ale także zmienić stan wewnętrzny iteratorów, aby następne wywołanie
zwróciło
4
Dokładnie. Myślę, że problem OP wynika po prostu z nieporozumień / niewłaściwego stosowania „powiedz, nie pytaj”. Naprawienie tego nieporozumienia sprawia, że ​​problem zniknie.
Konrad Rudolph
@KonradRudolph Plus, nie sądzę, że to jedyne nieporozumienie tutaj. Ich opis „czystej funkcji” obejmuje „Jej stan jest ustawiany tylko raz”. Inny komentarz wskazuje, że może to oznaczać kontekst zamknięcia, ale frazowanie brzmi dla mnie dziwnie.
Izkata
17

Jeśli uważasz, że returnjest „szkodliwy” (aby pozostać na zdjęciu), zamiast tworzyć podobną funkcję

ResultType f(InputType inputValue)
{
     // ...
     return result;
}

zbuduj go w sposób przekazujący wiadomości:

void f(InputType inputValue, Action<ResultType> g)
{
     // ...
     g(result);
}

Tak długo, jak fi gbez skutków ubocznych, łączenie ich ze sobą będzie również wolne od skutków ubocznych. Myślę, że ten styl jest podobny do tego, co jest również nazywane stylem Kontynuacja-przekazywanie .

Jeśli to naprawdę prowadzi do „lepszych” programów, jest dyskusyjne, ponieważ łamie niektóre konwencje. Niemiecki inżynier oprogramowania Ralf Westphal stworzył wokół tego cały model programistyczny, nazwał go „komponentami opartymi na zdarzeniach”, stosując technikę modelowania, którą nazywa „Flow Design”.

Aby zobaczyć kilka przykładów, zacznij od sekcji „Tłumaczenie na wydarzenia” tego wpisu na blogu . Aby uzyskać pełne podejście, polecam jego e-book „Wiadomości jako model programowania - robienie OOP tak, jakbyś miał na myśli” .

Doktor Brown
źródło
24
If this really leads to "better" programs is debatableWystarczy spojrzeć na kod napisany w JavaScript w pierwszej dekadzie tego stulecia. Jquery i jego wtyczki były podatne na wywołania zwrotne tego paradygmatu ... wszędzie tam . W pewnym momencie zbyt wiele zagnieżdżonych wywołań zwrotnych sprawiło, że debugowanie stało się koszmarem. Kod nadal musi być czytany przez ludzi bez względu na dziwactwa inżynierii oprogramowania i jej „zasad”
Laiv
1
nawet wtedy, w pewnym momencie, musisz wykonać akcję, która wywołuje efekt uboczny lub potrzebujesz sposobu, aby powrócić z części CPS
jk.
12
@Laiv CPS został wymyślony jako technologia optymalizacji kompilatorów, nikt tak naprawdę nie spodziewał się, że programiści będą pisać kod w ten sposób ręcznie.
Joker_vD
3
@CandiedOrange W formie hasła „powrót mówi tylko kontynuacji, co robić”. Rzeczywiście, stworzenie Schematu było motywowane próbą zrozumienia modelu aktora Hewitta i doszli do wniosku, że aktorzy i zamknięcia są tym samym.
Derek Elkins,
2
Hipotetycznie na pewno można napisać całą aplikację jako serię wywołań funkcji, które niczego nie zwracają. Jeśli nie masz nic przeciwko temu, że jest ściśle związany, nie potrzebujesz nawet portów. Ale większość rozsądnych aplikacji korzysta z funkcji, które zwracają różne rzeczy, ponieważ ... no cóż, są rozsądne. I jak wierzę, że odpowiednio wykazałem w mojej odpowiedzi, możesz uzyskiwać returndane z funkcji i nadal stosować się do instrukcji Tell Don't Ask, o ile nie kontrolujesz stanu obiektu.
Robert Harvey
7

Przekazywanie wiadomości jest z natury skuteczne. Jeśli powiesz obiektowi, aby coś zrobił , oczekujesz, że będzie to miało na coś wpływ. Jeśli moduł obsługi wiadomości był czysty, nie trzeba wysyłać mu wiadomości.

W systemach aktorów rozproszonych wynik operacji jest zwykle wysyłany jako wiadomość zwrotna do nadawcy pierwotnego żądania. Nadawca wiadomości jest domyślnie udostępniany przez środowisko wykonawcze aktora lub jest (zgodnie z konwencją) jawnie przekazywany jako część wiadomości. W synchronicznym przekazywaniu wiadomości pojedyncza odpowiedź jest podobna do returninstrukcji. W asynchronicznym przekazywaniu komunikatów używanie komunikatów odpowiedzi jest szczególnie przydatne, ponieważ pozwala na równoczesne przetwarzanie wielu aktorów, przy jednoczesnym dostarczaniu wyników.

Przekazywanie „nadawcy”, do którego wynik powinien zostać wyraźnie podany, zasadniczo modeluje styl przekazywania lub parametry budzące lęk - z wyjątkiem tego, że przekazuje im wiadomości zamiast mutować je bezpośrednio.

Bergi
źródło
5

Całe to pytanie uderza mnie jako „naruszenie poziomu”.

Masz (przynajmniej) następujące poziomy w dużym projekcie:

  • Poziom systemu, np. Platforma handlu elektronicznego
  • Poziom podsystemu, np. Sprawdzanie poprawności użytkownika: serwer, AD, interfejs użytkownika
  • Indywidualny poziom programu, np. Jeden z powyższych elementów
  • Poziom aktora / modułu [robi się mętny w zależności od języka]
  • Poziom metody / funkcji.

I tak dalej, aż do poszczególnych tokenów.

Tak naprawdę nie ma potrzeby, aby jednostka na poziomie metody / funkcji nie zwracała (nawet jeśli po prostu zwraca this). I nie ma (w twoim opisie) potrzeby by podmiot na poziomie aktora zwrócił cokolwiek (w zależności od języka, który może nawet nie być możliwy). Myślę, że zamieszanie polega na połączeniu tych dwóch poziomów i argumentowałbym, że należy je wyraźnie uzasadnić (nawet jeśli dany obiekt faktycznie obejmuje wiele poziomów).

Jared Smith
źródło
2

Wspominasz, że chcesz zachować zgodność zarówno z zasadą OOP „powiedz, nie pytaj”, jak i zasadą funkcjonalną funkcji czystych, ale nie do końca rozumiem, w jaki sposób doprowadziło to do uniknięcia instrukcji return.

Względnie powszechnym alternatywnym sposobem postępowania zgodnie z obiema tymi zasadami jest wejście all-in na instrukcje return i używanie niezmiennych obiektów tylko z getterami. Podejście polega zatem na tym, że niektóre pobierające zwracają podobny obiekt z nowym stanem, w przeciwieństwie do zmiany stanu oryginalnego obiektu.

Jednym z przykładów tego podejścia jest wbudowane Python tuplei frozensettypy danych. Oto typowe użycie zestawu zamrożonego:

small_digits = frozenset([0, 1, 2, 3, 4])
big_digits = frozenset([5, 6, 7, 8, 9])
all_digits = small_digits.union(big_digits)

print("small:", small_digits)
print("big:", big_digits)
print("all:", all_digits)

Które wypisze następujące, pokazując, że metoda unii tworzy nowy zestaw z własnym stanem bez wpływu na stare obiekty:

small: frozenset ({0, 1, 2, 3, 4})

duży: frozenset ({5, 6, 7, 8, 9})

wszystkie: frozenset ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})

Innym obszernym przykładem podobnych niezmiennych struktur danych jest biblioteka Immutable.js na Facebooku . W obu przypadkach zaczynasz od tych bloków konstrukcyjnych i możesz budować obiekty domeny wyższego poziomu, które działają zgodnie z tymi samymi zasadami, osiągając funkcjonalne podejście OOP, które pomaga łatwiej hermetyzować dane i uzasadniać je. Niezmienność pozwala również czerpać korzyść z możliwości udostępniania takich obiektów między wątkami bez martwienia się o zamki.

yoniLavi
źródło
1

Zacząłem podejrzewać, że dobrze napisany kod może jednocześnie przestrzegać zasad OO i zasad funkcjonalnych. Staram się pogodzić te pomysły, a najważniejszym punktem, na którym wylądowałem, jest powrót.

Staram się jak najlepiej pogodzić niektóre z korzyści, a konkretnie programowania imperatywnego i funkcjonalnego (oczywiście nie uzyskując wszystkich korzyści, ale staram się uzyskać lwią część obu), choć w returnrzeczywistości jest to fundamentalne prosta moda w wielu przypadkach.

Jeśli chodzi o próbę unikania returnstwierdzeń wprost, przez ostatnią godzinę próbowałem się nad tym zastanowić i po prostu kilka razy przepełniłem mój mózg. Widzę jego atrakcyjność pod względem egzekwowania najsilniejszego poziomu enkapsulacji i ukrywania informacji na rzecz bardzo autonomicznych obiektów, które są po prostu mówione, co robić, lubię odkrywać skrajności pomysłów, choćby po to, aby spróbować uzyskać lepszy zrozumienie ich działania.

Jeśli użyjemy przykładu sygnalizacji świetlnej, natychmiast naiwna próba chciałaby przekazać takiej wiedzy o całym świecie, który go otacza, a byłoby to z pewnością niepożądane z punktu widzenia łączenia. Więc jeśli dobrze rozumiem, streszczacie to i odsprzęgacie na rzecz uogólnienia koncepcji portów I / O, które dalej propagują komunikaty i żądania, a nie dane, poprzez potok, i zasadniczo wstrzykują te obiekty pożądanymi interakcjami / żądaniami między sobą nieświadomi siebie nawzajem.

Rurociąg węzłowy

wprowadź opis zdjęcia tutaj

I ten schemat jest o tyle, o ile próbowałem to naszkicować (i chociaż było to proste, musiałem ciągle go zmieniać i przemyślać). Natychmiast wydaje mi się, że projekt z takim poziomem oddzielenia i abstrakcji stałby się bardzo trudny do uzasadnienia w formie kodu, ponieważ orkiestrator (y), którzy łączą wszystkie te rzeczy w złożony świat, mogą mieć trudności z śledzić wszystkie te interakcje i żądania w celu stworzenia pożądanego potoku. Jednak w formie wizualnej może być dość proste, aby po prostu narysować te rzeczy jako wykres i połączyć wszystko i zobaczyć, jak rzeczy dzieją się interaktywnie.

Jeśli chodzi o skutki uboczne, widziałem, że jest to wolne od „skutków ubocznych” w tym sensie, że te żądania mogą, na stosie wywołań, prowadzić do łańcucha poleceń dla każdego wątku do wykonania, np. (Nie liczę tego jako „efekt uboczny” w sensie pragmatycznym, ponieważ nie zmienia on żadnego stanu istotnego dla świata zewnętrznego, dopóki takie polecenia nie zostaną faktycznie wykonane - praktycznym celem dla mnie w większości programów nie jest wyeliminowanie efektów ubocznych, ale odroczenie ich i scentralizowanie) . Co więcej, wykonanie polecenia może wygenerować nowy świat, zamiast mutować istniejący. Mój mózg jest naprawdę opodatkowany, po prostu próbując to wszystko zrozumieć, bez jakiejkolwiek próby prototypowania tych pomysłów. Ja też nie

Jak to działa

Aby wyjaśnić, wyobrażałem sobie, jak to właściwie programujesz. Sposób, w jaki widziałem, jak działa, był w rzeczywistości powyższym diagramem przechwytującym przepływ pracy użytkownika końcowego (programisty). Możesz przeciągnąć światłach na świat, przeciągnąć stoper, dać mu upływ czasu (po „zbudowaniu” go). Timer ma On Intervalzdarzenie (port wyjściowy), możesz podłączyć to do sygnalizacji świetlnej, aby w takich przypadkach informowała światło, aby zmieniało kolory.

Sygnalizacja świetlna może wtedy, po przełączeniu się na określone kolory, emitować sygnały wyjściowe (zdarzenia), takie jak, On Redw którym momencie możemy przeciągnąć pieszego do naszego świata i sprawić, że wydarzenie to nakazuje pieszemu zacząć chodzić ... lub możemy przeciągnąć ptaki do naszą scenę i spraw, aby gdy światło zmieniło kolor na czerwony, mówimy ptakom, aby zaczęły latać i trzepotać skrzydłami ... a może gdy światło zmieni kolor na czerwony, mówimy bomby, aby eksplodowała - cokolwiek chcemy, a obiekty są całkowicie nieświadomi siebie nawzajem i nie robiąc nic, tylko pośrednio mówiąc sobie, co robić poprzez tę abstrakcyjną koncepcję wejścia / wyjścia.

I w pełni opisują swój stan i nie ujawniają niczego na ten temat (chyba że te „zdarzenia” są uważane za TMI, w którym to momencie musiałbym dużo przemyśleć rzeczy), mówią sobie nawzajem, że powinni robić pośrednio, nie pytają. I są niesamowicie oddzielone. Nic nie wie o niczym oprócz tej uogólnionej abstrakcji portów wejścia / wyjścia.

Praktyczne przypadki użycia?

Widziałem, że tego rodzaju rzeczy są przydatne jako język osadzony wysokiego poziomu specyficzny dla domeny w niektórych domenach do koordynowania wszystkich tych autonomicznych obiektów, które nie wiedzą nic o otaczającym świecie, nie ujawniają niczego z ich wewnętrznej budowy stanu postu i po prostu propagują żądania między sobą, które możemy zmieniać i dostosowywać do treści naszych serc. W tej chwili wydaje mi się, że jest to bardzo specyficzne dla danej dziedziny, a może po prostu nie zastanawiałem się nad tym, ponieważ bardzo trudno jest mi otoczyć mózg typami rzeczy, które regularnie rozwijam (często pracuję z raczej niski kod średniego poziomu), gdybym zinterpretował Tell, Don't Ask do takich skrajności i chcę jak najsilniejszego poziomu enkapsulacji, jaki można sobie wyobrazić. Ale jeśli pracujemy z abstrakcjami wysokiego poziomu w określonej domenie,

Sygnały i automaty

Ten projekt wydawał mi się dziwnie znajomy, dopóki nie zdałem sobie sprawy, że to w zasadzie sygnały i gniazda, jeśli nie weźmiemy pod uwagę wielu niuansów, w jaki sposób jest on uwzględniany. Głównym pytaniem jest dla mnie, jak skutecznie możemy zaprogramować te poszczególne węzły (obiekty) na wykresie jako ściśle przestrzegające returninstrukcji Tell, Don't Ask, do stopnia unikania stwierdzeń i czy możemy ocenić wspomniany wykres bez mutacji (w równoległe, np. brak blokady). Właśnie tam magiczne korzyści nie polegają na tym, jak potencjalnie łączymy te rzeczy, ale jak można je wdrożyć do tego stopnia enkapsulacji przy braku mutacji. Oba te wydają mi się wykonalne, ale nie jestem pewien, jak szerokie byłoby to zastosowanie, i tutaj jestem trochę zakłopotany próbując przeanalizować potencjalne przypadki użycia.

Dragon Energy
źródło
0

Tutaj wyraźnie widzę przeciek pewności. Wydaje się, że „efekt uboczny” jest dobrze znanym i powszechnie rozumianym terminem, ale w rzeczywistości tak nie jest. W zależności od twoich definicji (których faktycznie brakuje w PO), działania niepożądane mogą być całkowicie konieczne (jak udało się wyjaśnić @JacquesB) lub bezlitośnie nieakceptowane. Lub, robiąc krok w kierunku wyjaśnienia, konieczne jest rozróżnienie pożądanych efektów ubocznych, których nie lubi się ukrywać (w tym momencie pojawia się słynne IO Haskella: jest to tylko sposób na wyrażenie) i niepożądanych efektów ubocznych jako wynik błędów w kodzie i tego typu rzeczy . Są to dość różne problemy i dlatego wymagają innego rozumowania.

Proponuję więc zacząć od przeredagowania siebie: „Jak definiujemy efekt uboczny i co dane definicje mówią o jego związku z instrukcją„ return ”?”.

Sereja Bogolubov
źródło
1
„w tym momencie pojawia się słynne IO Haskella: jest to tylko sposób, aby być wyraźnym” - wyraźność jest z pewnością korzyścią monadycznego IO Haskella, ale jest jeszcze jedna kwestia: zapewnia sposób odizolowania efektu ubocznego, aby był całkowicie poza językiem - choć popularne implementacje nie faktycznie to zrobić, głównie ze względu na obawy efektywności, koncepcyjnie jest to prawda: monada IO może być uważana za sposób powrocie dyspozycję do pewnego środowiska, które jest całkowicie z zewnątrz Program Haskell wraz z odniesieniem do funkcji, która ma być kontynuowana po zakończeniu.
Jules