wyjątki kompletnie bez sensu

0
danek napisał(a):

Zamiast tesować nulle i wyjątki testujesz .isEmpty lub .getLeft() jeśli Either. Przecież samo zachowanie i odpowiedzialność programu się nie zmienia.

No tak, dobrze, jak by tak wszyscy robili. Lecz osobiście uważam testowanie IsEmpty, getLeft, jest bez sensu w krytycznych wypadkach, kiedy aplikacja nie działa tak jak powinna działać.

1
Gworys napisał(a):

Lecz osobiście uważam testowanie IsEmpty, getLeft, jest bez sensu w krytycznych wypadkach, kiedy aplikacja nie działa tak jak powinna działać.

Co przez to rozumiesz?

0
danek napisał(a):
Gworys napisał(a):

Lecz osobiście uważam testowanie IsEmpty, getLeft, jest bez sensu w krytycznych wypadkach, kiedy aplikacja nie działa tak jak powinna działać.

Co przez to rozumiesz?

Optionala albo Eithera zwracam tylko wtedy kiedy faktycznie jakiś zasób jest albo może być pusty, a nie np. wtedy kiedy nie mogę czegoś sprasować albo coś innego poszło nie tak.

2

No i co robisz jak nie uda się sparsować? Wyjątek? I tak musisz go gdzieś złapać i coś z nim zrobić. Po to jest taki Either żeby mieć w lewej 'cześci' ewentualny błąd, a w prawej prawidłową wartość.

0
danek napisał(a):

No i co robisz jak nie uda się sparsować? Wyjątek? I tak musisz go gdzieś złapać i coś z nim zrobić. Po to jest taki Either żeby mieć w lewej 'cześci' ewentualny błąd, a w prawej prawidłową wartość.

Robię tak, aby nie poleciał wyjątek - wykorzystując do tego odpowiednie wzorce, dzięki czemu nie kaleczę języka domenowego oraz modelu, lecz zależy to od faktycznego kontekstu owej sytuacji. Jeśli poleci wyjątek to znaczy, że coś robię nie tak i wtedy przydaje się stackTrace z wyjątku, no chyba, że wolisz przez breakpointy skakać do tyłu i przodu.

2

I takim odpowiednikiem odpowiedniego wzorca też mogą być monady, więc trudno tu mówić o kaleczeniu ;)

1
danek napisał(a):

I takim odpowiednikiem odpowiedniego wzorca też mogą być monady, więc trudno tu mówić o kaleczeniu ;)

Słowo Optiona albo Ether jest jakoś mało domenowe. :P

A przelecenie wszystkiego Mapem i FlatMapem w jednej metodzie nie zawsze musi być najbardziej eleganckim i czytelnym sposobem.

Jak powiedziałem, zależy to od sytuacji.

Jeśli mówisz: "Taka sytuacja nigdy nie może się przydarzyć", czyli jest to przypadek krytyczny, to bardziej pasuje wyjątek.

Jeśli przewidujesz, że taka sytuacja jak brak możliwości sparsowania jest ok i aplikacja może działać dalej, to tak jak mówisz Optional czy Either też jest Ok.

0

Coś takiego dziś wyskoczyło mi na twitterze : Exceptions? No, just Try them off!

1

@Kamil Żabiński: tylko Try z vavra powinno się używać do owrapowania wyjątków z jakichś zewnętrznych bibliotek na które nie masz wpływu. Jeśli używasz go tylko po to by mieć inną składnie niż try catch to tak na prawdę problem jest dalej ten sam

1

IMO ludzie maja problem z wyjątkami, bo mają w głowie przekonanie, że muszą je obsłużyć tu i teraz zaraz.
Właśnie takie podejście czyni z wyjątków wielki problem. Wystarczy chwila oddechu, stwierdzenie "nie muszę łapać tego tutaj", zastanowić się gdzie wyżej łapać wyjątki jeśli w ogóle.
Nawet w obecnym moim projekcie, widzę takie kwiatki. Zamiast jednego try catch w jednym miejscu kodu, to są rozsiane w +100 miejscach (przy chwili oddechu podczyszczę to).

2
Gworys napisał(a):

Słowo Optiona albo Ether jest jakoś mało domenowe. :P

I bardzo dobrze.to jest słowo zrozumiałe w kontekście programowania, tak samo jak map, flatmap. Alternatywy gdzie tworzymy business friendly nazwy jak Vernon proponuje (Completes?) to chaos dla programistów.
Jak już muszę mniejsze zło wybrać to wolę, żeby programiści sie w kodzie nie gubili, a biznes gubi się w nim i tak.

Mozę nie uwierzysz, ale chodzą na tym świecie takie kwiatki, które uważają, że wyjątki i nulle to efekt zacofania programistycznego a jak się używa optionala to nie ma potrzebny pisać testów, które sprawdzają nulle i wyjątki.

To zupełnie dziwnne co więcej Twoje dalsze wypowiedzi jakby zupełnie idą w innym kierunku.
Tym niemniej - dokładnie: jeśli stosujemy Optionale i Eithery to dlatego, żeby nie testować nulli i wyjątków. Jak ktoś wrzuci do argumentu Optional null ( a nie None) to znaczy, że prosi o NullPointerException i należy prośbę spełnić. Robienie testów na niepoprawny kod IMO jest sensu, zwłaszcza jak daje sietakie kwiatki dość łatwo wykryć.

Jedynym sensownym dodatkiem w bardziej krytycznych fragmentach kodu będą tu asercje.
(btw. z róznycmi dziwnymi ludźmi miałem do czynienia, ale generalnie wsadzanie nulli w argument Optional lub rzucanie wyjątku jak jest Either to rzadkość, zwykle po pierwszym wyjaśnieniu ludzie ogarniają).

0
jarekr000000 napisał(a):
Gworys napisał(a):

Słowo Optiona albo Ether jest jakoś mało domenowe. :P

I bardzo dobrze.to jest słowo zrozumiałe w kontekście programowania, tak samo jak map, flatmap. Alternatywy gdzie tworzymy business friendly nazwy jak Vernon proponuje (Completes?) to chaos dla programistów.
Jak już muszę mniejsze zło wybrać to wolę, żeby programiści sie w kodzie nie gubili, a biznes gubi się w nim i tak.

Im bardziej ogólnych nazw używasz, tym łatwiej można się zgubić nieważne komu, to typowy mankament języków deklaratywnych. Nie mam nic przeciwko Monadą FlatMapom i etc. ale większość ludzi ma tendencje do pisania bardzo długich łańcuchów funkcji co niekoniecznie jest czytelne.

Mozę nie uwierzysz, ale chodzą na tym świecie takie kwiatki, które uważają, że wyjątki i nulle to efekt zacofania programistycznego a jak się używa optionala to nie ma potrzebny pisać testów, które sprawdzają nulle i wyjątki.

To zupełnie dziwnne co więcej Twoje dalsze wypowiedzi jakby zupełnie idą w innym kierunku.
Tym niemniej - dokładnie: jeśli stosujemy Optionale i Eithery to dlatego, żeby nie testować nulli i wyjątków. Jak ktoś wrzuci do argumentu Optional null ( a nie None) to znaczy, że prosi o NullPointerException i należy prośbę spełnić. Robienie testów na niepoprawny kod IMO jest sensu, zwłaszcza jak daje sietakie kwiatki dość łatwo wykryć.

Nie rozumiesz, Jeśli zachowanie logiki wymaga jakiegoś testowania to dodanie Optionala nie sprawi, że te testy znikną, ponieważ zachowanie funkcji się nie zmienia jedynie implementacja.

Jedynym sensownym dodatkiem w bardziej krytycznych fragmentach kodu będą tu asercje.
(btw. z róznycmi dziwnymi ludźmi miałem do czynienia, ale generalnie wsadzanie nulli w argument Optional lub rzucanie wyjątku jak jest Either to rzadkość, zwykle po pierwszym wyjaśnieniu ludzie ogarniają).

A dlaczego jedynym sensownym.? W kontekście programowania samo sformułowanie "Jedynym sensownym" jakoś mało sensownie brzmi.

2

Tylko że Optional, Either sprawia, że wiesz czego się spodziewać. Bez tego nie wiesz czy jakiś null czy RuntimeException nie wyskoczy zza krzaka.

Optionale itp nie są po to, żeby testów było mniej, tylko po to, żeby kolejne osoby w przyszłości nie nadziały się na jakieś nullowe pułapki

0
danek napisał(a):

Tylko że Optional, Either sprawia, że wiesz czego się spodziewać. Bez tego nie wiesz czy jakiś null czy RuntimeException nie wyskoczy zza krzaka.

Optionale itp nie są po to, żeby testów było mniej, tylko po to, żeby kolejne osoby w przyszłości nie nadziały się na jakieś nullowe pułapki

Dlatego uważam, że to wspaniały dodatek do programowania obiektowego.

A co do tego RuntimeException to popatrz w moją stopkę.

0
danek napisał(a):

Tylko że Optional, Either sprawia, że wiesz czego się spodziewać. Bez tego nie wiesz czy jakiś null czy RuntimeException nie wyskoczy zza krzaka.

IMO bardzo słaby argument. Checked exception w Javie też sprawiają, że wiesz czego się spodziewać i jakoś nikt ich nie chwali.
Ogólnie całe pure functional jest fajne, ale do pewnego momentu o czym puryści zapominają. Aplikacje w prawdziwym świecie zależą od stanów. Ot taki prosty przypadek, insert do bazy danych - proszę zaprojektować funkcję, która niezależnie od stanu bazy danych będzie zwracała to samo. Ewentualnie jej bezbazodanowy odpowiednik, z replikacją i trwałością.
Oczywiście można potraktować bazę danych jako argument i wtedy w teorii dałoby się coś takiego zrobić. Ale wtedy zaraz trzeba będzie dorzucić całą gamę innych rzeczy, typu kontekst użytkownika/security, jakiekolwiek inne usługi zewnętrzne itp.

1

Aplikacje w prawdziwym świecie zależą od stanów.

W jaki sposób FP przeszkadza w stanowości?

proszę zaprojektować funkcję, która niezależnie od stanu bazy danych będzie zwracała to samo.

Proszę - funkcja, która niezależnie od stanu bazy danych zwraca to samo, w Rust (sam język nie jest czysto funkcyjny, lecz przytoczony przeze mnie poniżej kod już tak):

fn random() -> usize {
  4 // chosen by fair dice roll
}

Jeśli szukasz rzeczywistych przykładów kodu FP, który odwołuje się do baz danych, wpisz w Google haskell database tutorial ;-)

Oczywiście można potraktować bazę danych jako argument i wtedy w teorii dałoby się coś takiego zrobić

W OOP wrzuciłbyś bazę danych jako zależność przez DI - niczym nie różni się to od przekazywania jej bezpośrednio do funkcji przez argument.

Ale wtedy zaraz trzeba będzie dorzucić całą gamę innych rzeczy, typu kontekst użytkownika/security, jakiekolwiek inne usługi zewnętrzne itp.

W jaki sposób FP to uniemożliwia?

0
wartek01 napisał(a):

IMO bardzo słaby argument. Checked exception w Javie też sprawiają, że wiesz czego się spodziewać i jakoś nikt ich nie chwali.

Są pewne różnice jak np: dodając nowy wyjątek, musisz go obsłużyć, dodając nowy typ błądu w Either zazwyczaj używasz jakiegoś enum i nie musisz nigdzie więcej nic zmieniać

1

proszę zaprojektować funkcję, która niezależnie od stanu bazy danych będzie zwracała to samo. Ewentualnie jej bezbazodanowy odpowiednik, z replikacją i trwałością

A proszę bardzo, przykład w Eliksirze:

defmodule State do
  def start_link(state), do: spawn_link(__MODULE__, :run, [state])

  def get(pid) do
    ref = make_ref()
    send(pid, {:get, ref, self()})

    receive do
      {^ref, data} -> data
    end
  end

  def set(pid, state), do: send(pid, {:set, state})

  def run(state) do
    receive do
      {:get, ref, pid} ->
        send(pid, {ref, state})
        run(state)
      {:set, new_state} ->
        run(new_state)
    end
  end
end

Masz moduł, który przechowuje stan, a mimo tego nie ma w nim nic mutowalnego.

1
Patryk27 napisał(a):

W jaki sposób FP przeszkadza w stanowości?

Dla mnie w żadnym, ale tutaj była mowa o niewinności funkcji, czyli cytując:

Niewinność: brak (bezpośrednich) interakcji ze światem lub wewnętrznym stanem programu (inculpable)

W OOP wrzuciłbyś bazę danych jako zależność przez DI - niczym nie różni się to od przekazywania jej bezpośrednio do funkcji przez argument.

Tak, ale - uwaga - dlaczego nie przekazuje się tego przez argument? Sprawa jest prosta, bo wstrzyknięcie zależności jako konstruktor jest (w granicach rozsądku) jest bardziej czytelne, niż przekazywanie tego przez argument.

I tutaj dochodzimy do tego, co moim zdaniem najważniejsze - trzymanie się kilku dogmatów opisanych przez @Kamil Żabiński skończy się na tym, że dostaniesz jedną mega funkcję, której składowe części faktycznie będą niemutowalne, ale będą miały kilkadziesiąt argumentów - kilka na bazy danych, inne na jakieś kolejki, inne jeszcze na jakieś zależności zewnętrzne itp. - i przez to będą nieczytelne. A pisząc w językach programowania - zwłaszcza jeśli się pisze wysokopoziomowo - bardzo ważna jest czytelność kodu.

Ale wtedy zaraz trzeba będzie dorzucić całą gamę innych rzeczy, typu kontekst użytkownika/security, jakiekolwiek inne usługi zewnętrzne itp.

W jaki sposób FP to uniemożliwia?

Nie piszę, że uniemożliwia. Po prostu taki kod będzie trudno czytelny, bo za każdym razem będziesz musiał przekazywać te N argumentów.

1

Po prostu taki kod będzie trudno czytelny, bo za każdym razem będziesz musiał przekazywać te N argumentów.

Ale o tym, że w FP istnieją struktury, to ty wiedz. Więc możesz mieć jedną strukturę tworzoną na starcie zawierającą wszystkie opcje potrzebne do działania programu i zwyczajnie "przepychasz" ją w dół.

1

Dla mnie w żadnym, ale tutaj była mowa o niewinności funkcji, czyli cytując:

Raz jeszcze: w jaki sposób ta definicja kłóci się ze stanowością (Aplikacje w prawdziwym świecie zależą od stanów)?

Mówi ona jedynie tyle, że od czapy nie możesz sobie zmodyfikować wewnętrznego stanu programu, co powinno być przestrzegane nawet w językach imperatywnych, skoro zależy CI na czytelności kodu.

wstrzyknięcie zależności jako konstruktor jest (w granicach rozsądku) jest bardziej czytelne, niż przekazywanie tego przez argument.

Brzmi subiektywnie, więc IMO nie ma co wchodzić w ten temat - coś w stylu taby > spacje itd.

będą miały kilkadziesiąt argumentów - (...) - i przez to będą nieczytelne

No tak, bo gdy wstrzykniesz dwadzieścia serwisów przez konstruktor w OOP to otrzymasz wyjątkowo czytelną i łatwą w utrzymaniu klasę ;-]

0
Patryk27 napisał(a):

Dla mnie w żadnym, ale tutaj była mowa o niewinności funkcji, czyli cytując:

Raz jeszcze: w jaki sposób ta definicja kłóci się ze stanowością (Aplikacje w prawdziwym świecie zależą od stanów)?

Jeszcze raz - mamy trzy rzeczy:

Totalność: zwracają wartość dla każdego możliwego argumentu (total)
Deterministyczność: za każdym razem zwracają tę samą wartość dla tego samego argumentu (deterministic)
Niewinność: brak (bezpośrednich) interakcji ze światem lub wewnętrznym stanem programu (inculpable)

Zacznijmy od pierwszego - to jest wykonywalne, głównie za pomocą monad Try/Either. Tutaj nie dyskutuję.

Drugie - deterministyczność.
I teraz mamy bazę danych - powiedzmy MySQL. Teraz chcesz wykonać zapytanie do tej bazy danych i musisz to zrobić za pomocą funkcji, która ZAWSZE zwróci ci tę samą wartość dla tego samego argumentu.
Połączenie może ci się wywalić w dowolnym momencie więc niemożliwym jest napisanie funkcji, która przyjmie np. jakkolwiek byś nazwał Connection i zwróci ci za każdym razem wynik SELECTa - choćby dlatego, że połączenie w dowolnym momencie może ci się wywalić. Oczywiście da się to obsłużyć za pomocą either czy try ale to już nie będzie deterministyczne - bo niedeterministyczna część pochodzi z zewnątrz.
Jakkolwiek nisko nie podejdziesz to takiego zewnętrznego stanu nie przeskoczysz, możesz sobie używać cache'u i na koniec dnia zostaniesz z funkcją, która albo zadziała, albo nie.

Trzecie - niewinność. Jeśli masz stan i chcesz go modyfikować to musi zaistnieć funkcja, która ten stan bezpośrednio zmodyfikuje, będzie miała bezpośrednią zależność od tego stanu.

Fajnie by było odkryć takiego Świętego Graala programowania i stworzyć szereg zasad uniwersalnych, które będą dobrymi zasadami we wszystkich możliwych przypadkach. A FP nie jest uniwersalnym zbiorem prawd nawet w względnie małym wycinku programowania znanego jako "programowanie aplikacji biznesowych".

2

choćby dlatego, że połączenie w dowolnym momencie może ci się wywalić

Czyli połączenie się zmieni - dla tego samego, prawidłowego połączenia powinieneś otrzymać ten sam wynik ;-)

Zresztą nawet nie o to chodzi - odnoszę wrażenie, że usilnie próbujesz "obalić" FP, odlatując przy tym w niepragmatyczny kosmos: w FP chodzi przede wszystkim o to, aby to Twoja aplikacja i Twoja logika trzymała się tych zasad (dzięki temu znacznie prościej się taki kod analizuje, ponieważ nagle jakiś event handler z trzeciego modułu po kisielu nie przeora Ci połowy pól w encji); świat zewnętrzny jest siłą rzeczy mutowalny w przeróżny sposób i tych zasad trzymać już się nie musi, stąd powstają takie "leaking abstraction" jak np. połączenia do baz danych.

Przy czym nawet takie abstrakcje jak TCP nie wpływają na samą ideę FP; gdybyś zasymulował połączenie do bazy w różnych stanach (np. w prawidłowym, w broken pipe itd.), to Twoja funkcja dla każdego z tych stanów powinna działać deterministycznie - a to już jesteś w stanie bez problemu zaprogramować i przetestować.

A FP nie jest uniwersalnym zbiorem prawd (...)

Kto twierdzi, że jest?

https://en.wikipedia.org/wiki/Straw_man

1
wartek01 napisał(a):

Teraz chcesz wykonać zapytanie do tej bazy danych i musisz to zrobić za pomocą funkcji, która ZAWSZE zwróci ci tę samą wartość dla tego samego argumentu.

To wtedy baza nie jest postrzebna. :)

0
Patryk27 napisał(a):

choćby dlatego, że połączenie w dowolnym momencie może ci się wywalić

Czyli połączenie się zmieni - dla tego samego, prawidłowego połączenia powinieneś otrzymać ten sam wynik ;-)

Dokładnie, bo połączenie jest stanowe - i jakkolwiek byś się nie starał to tego nie zrobisz.

Zresztą nawet nie o to chodzi - odnoszę wrażenie, że usilnie próbujesz "obalić" FP,

Moim celem nie jest obalanie FP (bo sam napisałem, że jest fajne) ponieważ zanim ono zrobiło się popularne kilka mądrych osób łopatą siłą nałożyła mi do głowy wiele rzeczy, które później zobaczyłem w tymże paradygmacie (choćby to, żeby dbać o jak najczystsze funkcyjnie metody, nie trzymać stanów tam gdzie nie trzeba itp.).

Zareagowałem wtedy, kiedy zobaczyłem podane kilka regułek "do wierzenia" jak to zrobił @Kamil Żabiński w swoim poście. Na prosty przypadek - ktoś spieprzył obsługę wyjątku i OP postanowił się wyżalić - kolo przedstawił trzy reguły zupełnie oderwane od najważniejszego zagadnienia - co się pisze.

Przy czym nawet takie abstrakcje jak TCP nie wpływają na samą ideę FP; gdybyś zasymulował połączenie do bazy w różnych stanach (np. w prawidłowym, w broken pipe itd.), to Twoja funkcja dla każdego z tych stanów powinna działać deterministycznie - a to już jesteś w stanie bez problemu zaprogramować i przetestować.

Połączenie może zmienić stan w dowolnym momencie, a odczyt z socketa nie jest operacją atomową - więc nawet jak przekażesz aktywne połączenie to w którymś momencie . To miałem na myśli przez "w rzeczywistym świecie".

A FP nie jest uniwersalnym zbiorem prawd (...)

Kto twierdzi, że jest?

Odsyłam do wyżej wspomnianego postu i do sytuacji. Zauważ, że nigdzie nie atakuję FP jako takiego (nawet nazywam fajnym), tylko zauważam, że są jednak sytuacje gdy zasady trzeba naginać.

0

Nie rozumiem, o co wy się kłócicie. Przecież wyrzyskie języki funkcyjne w tym Haskell dopuszczają możliwość pisania funkcji z side effekt tylko, że z odpowiednią sygnaturą. Niby jak chcecie coś zapisać do db, bez efektów ubocznych.? Przecież sam zapis jest już efektem ubocznym.

1

Niby jak chcecie coś zapisać do db, bez efektów ubocznych.? Przecież sam zapis jest już efektem ubocznym.

Mutowalność != efekt uboczny

0
Patryk27 napisał(a):

Niby jak chcecie coś zapisać do db, bez efektów ubocznych.? Przecież sam zapis jest już efektem ubocznym.

Mutowalność != efekt uboczny

Ach... To czym jest efekt uboczny.? :D

0

side effect dotyczy sytuacji, w której funkcja np. zaczyna operować na czymś "z czapy" (czego nie otrzymała w argumentach) - przykład w jakimś pseudo-języku:

fn insert_z_czapy() {
  var db = service_container.get_service("database");
  db.insert("users", /* ... */);
}

fn insert_bez_efektow_ubocznych(db: Database) -> Database {
  db.insert("users", /* ... */)
}
0
Patryk27 napisał(a):

side effect dotyczy sytuacji, w której funkcja np. zaczyna operować na czymś "z czapy" (czego nie otrzymała w argumentach) - przykład w jakimś pseudo-języku:

fn insert_z_czapy() {
  var db = service_container.get_service("database");
  db.insert("users", /* ... */);
}

fn insert_bez_efektow_ubocznych(db: Database) -> Database {
  db.insert("users", /* ... */)
}

Nie, jesteś w błędzie. "Side Efect" to po prostu zmiana stanu systemu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1