Używacie monady Option/Maybe w swoich projektach?

0

Czytałem sobie ostatnio co Rust, jako względnie młody język programowania, wprowadził ciekawego do swojej składni i jedną z takich rzeczy jest brak istnienia wartości "null". Zamiast tego zwraca się enum typu Option, który podobno niejako zmusza programistę do obsłużenia możliwości wystąpienia wartości None zanim dobierze się on do trzymanej w niej wartości.

NullReferenceException jest dość częstym wyjątkiem pojawiającym się w logach moich projektów, stąd też ciekawi mnie czy ktoś próbował to kiedyś stosować i z jakim efektem. Pierwsza z brzegu biblioteka oferuje w C# bliźniaczy mechanizm: https://github.com/louthy/language-ext?tab=readme-ov-file#option

3

W C# przecież jest nullable 🤔 po co komplikować projekt jakimiś bibliotekami?

0

Szukając odpowiedzi natrafiłem na taki wpis https://web.archive.org/web/20231205143119/https://ericbackhage.net/c/nullable-reference-types-compared-to-the-option-monad/ (strona niestety obecnie leży, także wrzuciłem webarchive).

The problem with null is that it has no value, it’s the abscence of a value. An Option on the other hand is always a valid instance.

since we always have a valid instance to work with, an Option, we can just push it through the workflow and let the framework (language-ext) handle the error cases.

3

I czym się różni przypisanie domyślnego obiektu od przypisania Option?

Visual Studio informuje warningiem, że tak nie powinno być, bo może być null, ale wszyscy czytają tylko error'y
Zrzut ekranu 2024-03-11 214639.png

Wystarczy zrobić

public class Foo
{
  public IEnumerable<string> FooList { get; set; } = Enumerable.Empty<string>();
}

albo w najnowszych C#'ach

public class Foo
{
  public IEnumerable<string> FooList { get; set; } = [];
}
0

Mapowanie Option/Eitherów jest super. Jednak jak we wszystkim trzeba mieć umiar. Myślę, że wolałbym użyć jakiejś mniej skomplikowanej biblioteki. Taki kod do mnie nie przemawia:

//https://github.com/louthy/language-ext/blob/main/Samples/BankingAppSample/BankingAppSample/Program.cs
static void Main(string[] args)
        {
            var bank = Bank.Empty;

            var action = from accountId in CreateAccount(PersonName(Title("Mr"), FirstName("Paul"), Surname("Louth")))
                         from _1        in ShowBalance(accountId)
                         from amount1   in Deposit(Amount(100m), accountId)
                         from _2        in ShowBalance(accountId)
                         from amount2   in Withdraw(Amount(75m), accountId)
                         from _3        in ShowBalance(accountId)
                         from _4        in ShowTransactions()
                         select accountId;

            var result = Interpreter.Interpret(action, bank);
        }

        /// These functions demonstrates that once you have captured all of the
        /// actions that represent an interaction with the 'world' (i.e IO,
        /// databases, global state, etc.) then you can compose those actions
        /// without having to add new types to the BankingFree monad.


        static BankingFree<Unit> ShowBalance(AccountId id) =>
            from ac in AccountDetails(id)
            from ba in Balance(id)
            from _1 in Show($"Balance of account {ac.Name} is: ${ba}")
            from wa in WithdrawalAccountDetails
            from _2 in Show($"\tBalance of {wa.Name} is: ${wa.Balance}")
            from da in DepositAccountDetails
            from _3 in Show($"\tBalance of {da.Name} is: ${da.Balance}")
            select unit;

        static BankingFree<Unit> ShowTransactions() =>
            from t in BankTransactions
            from _ in ShowTransactions(t)
            select unit;

        static BankingFree<Unit> ShowTransactions(Seq<Transaction> transactions) =>
            transactions.Match(
                ()      => Return(unit),
                t       => Show(t),
                (t, ts) => from a in ShowTransactions(ts)
                           from b in Show(t)
                           select unit);
    }
2
AdamWox napisał(a):

W C# przecież jest nullable 🤔 po co komplikować projekt jakimiś bibliotekami?

I nie działa, bo nawet typy które nie są nullable, np List list, mogą dostać null. 👏

1

Co to znaczy "nie działa"? Oczywiście, że może i widać to przecież na moim screenie, że VS informuje o tym. Jeśli chcesz poinformować siebie, że ta lista może przyjąć null to robisz List? list, wtedy VS ci "podkreśli" jak będziesz chciał zrobić list.Where() albo inne linq. To nie ma zablokować kompilacji, bo kod jest poprawny przecież. To problem programisty, czy to obsłuży, czy nie.

1

To nie ma zablokować kompilacji, bo kod jest poprawny przecież.

Jak pusta wartosc nie jest obsluzona to znaczy, ze kod nie jest poprawny. Dziekuje, do widzenia.

1
Riddle napisał(a):

I nie działa, bo nawet typy które nie są nullable, np List list, mogą dostać null. 👏

To akurat działa, bo jak masz włączone nullable (nie mylić z typem Nullable<T>) to dostajesz od razu warning, a jak chcesz to jedną linijką w .csproj konfigurujesz żeby takie warningi były traktowane jako errory. 👏

0
AdamWox napisał(a):

Oczywiście, że może i widać to przecież na moim screenie, że VS informuje o tym. Jeśli chcesz poinformować siebie, że ta lista może przyjąć null to robisz List? list, wtedy VS ci "podkreśli" jak będziesz chciał zrobić list.Where() albo inne linq. To nie ma zablokować kompilacji, bo kod jest poprawny przecież. To problem programisty, czy to obsłuży, czy nie.

A jak chcę typ który nie może przyjąć nulla, to jak mam napisać?

PS: Pewnie powiesz że się nie da, bo "C# tak zaprojektowany", bla, bla - ale mnie taki argument nie przekonuje. Po co mieć typy "nullable", skoro każdy typ jest nullable (bo każdy może przyjąć nulla).

0
Riddle napisał(a):

A jak chcę typ który nie może przyjąć nulla, to jak mam napisać?

PS: Pewnie powiesz że się nie da, bo "C# tak zaprojektowany", bla, bla - ale mnie taki argument nie przekonuje. Po co mieć typy "nullable", skoro każdy typ jest nullable (bo każdy może przyjąć nulla).

To piszesz List list.

Bredzisz, bo nie doczytałeś czym jest nullable reference types wprowadzone w C# 8 - https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references

0
some_ONE napisał(a):

To piszesz List list.

No i taki typ nadal może przyjąć null.

3

Różnica jest tylko w tym, że jak się odwołasz do takiego obiektu to dla nullable VS cie poinformuje i podkreśli kod, że może być null. Microsoft od lat nie potrafił sobie poradzić z NullReferenceException, więc zwalił to na programistę tworząc nullable. Dodatkowo dla takiego int? number masz dodatkowe opcje jak HasValue().

Zaznaczam, że wątek jest o przypisaniu śmiesznego Option z zewnętrzenej biblioteki, aby nulla nie było, a to się kompletnie niczym nie różni od przypisania temu obiektowi domyślnej wartości bez biblioteki 🤔 już pomijając, czy nullable w C# ma sens, czy nie

1

Pisząc w C# trzeba się przyzwyczaić do tego, że si-szaropowcy w kontekście monady Maybe są na poziomie Optional.isPresent z Javy i żadnymi mapami/flatmapami nie są zainteresowani.

3
Riddle napisał(a):
some_ONE napisał(a):

To piszesz List list.

No i taki typ nadal może przyjąć null.

To pokaż mi jak przypisujesz null do takiego typu mając <Nullable>enable</Nullable> i <WarningsAsErrors>Nullable</WarningsAsErrors>.

Pomijając jakieś cuda z refleksją żeby celowo popsuć kod.

0
AdamWox napisał(a):

Różnica jest tylko w tym, że jak się odwołasz do takiego obiektu to dla nullable VS cie poinformuje i podkreśli kod, że może być null. Microsoft od lat nie potrafił sobie poradzić z NullReferenceException, więc zwalił to na programistę tworząc nullable. Dodatkowo dla takiego int? number masz dodatkowe opcje jak HasValue().

Zaznaczam, że wątek jest o przypisaniu śmiesznego Option z zewnętrzenej biblioteki, aby nulla nie było, a to się kompletnie niczym nie różni od przypisania temu obiektowi domyślnej wartości bez biblioteki 🤔 już pomijając, czy nullable w C# ma sens, czy nie

No i dlatego powiedziałem że nullable nie działa, bo tak czy tak nie można mieć typów które nie wpuszczają null. Ergo, wszystkie typy (nawet te bez ?) w C# przyjmują null (niezależnie od opcji nullable), ergo wszystkie metody tak czy tak muszą je obsługiwać.

1

Nie trzeba obsługiwać nulli w metodach bo jak cały kod opatrzysz ifami żeby nie mieć nigdzie nulla to null nie przyjdzie do metody i nie musisz sie nim martwić.

2
AbcDefGhi napisał(a):

Nie trzeba obsługiwać nulli w metodach bo jak cały kod opatrzysz ifami żeby nie mieć nigdzie nulla to null nie przyjdzie do metody i nie musisz sie nim martwić.

Brawo! 🥂 Nie trzeba obsługiwać nulli w metodach bo jak cały kod opatrzysz ifami . Nie trzeba być wegetarianinem, wystarczy nie jeść mięsa. Nie trzeba być martwym, wystarczy nie umierać.

Przyjacielu, @AbcDefGhi to "opatrywanie ifami" to jest właśnie ta obsługa o której mówiłem. Typy w językach programowania są właśnie po to żebyś nie musiał robić takich checków sam.

0
Riddle napisał(a):
AbcDefGhi napisał(a):

Nie trzeba obsługiwać nulli w metodach bo jak cały kod opatrzysz ifami żeby nie mieć nigdzie nulla to null nie przyjdzie do metody i nie musisz sie nim martwić.

Brawo! 🥂 Nie trzeba obsługiwać nulli w metodach bo jak cały kod opatrzysz ifami . Nie trzeba być wegetarianinem, wystarczy nie jeść mięsa. Nie trzeba być martwym, wystarczy nie umierać.

Przyjacielu, @AbcDefGhi to "opatrywanie ifami" to jest właśnie ta obsługa o której mówiłem. Typy w językach programowania są właśnie po to żebyś nie musiał robić takich checków sam.

Wiadomo, że można przypisać nulla do List<int>, tylko wtedy dostaniesz ostrzeżenie o tym, że takie coś wystąpiło i jak to zignorujesz to sam sobie jesteś winny. To sprawdzanie robisz tylko tam gdzie napiszesz np List<int>? , jak napiszesz samo List<int> to nie musisz sprawdzać bo jak nigdzie nie masz ostrzeżeń to znaczy, że nulla tam nie ma.

Cel tego jest taki, aby dać możliwość uniknięcia niespodziewanego nulla. Owszem czasami trzeba sie naprodukować, żeby to osiągnąć, ale da się. A dodatkowo przez znak ? masz szybką możliwość określenia czy oczekujesz nulla czy nie, bez potrzeby tworzenia nowego typu specjalnie przeznaczonego do trzymania wartosci domyslnej.

Jak dla mnie nie ma w praktyce różnicy, czy będzie typ który pozwoli przypisać nulla pomimo tego, że jest nienullowalny czy nie skoro i tak trzeba to określać czy chcemy nulla czy nie, i na podstawie tego w trakcie pisania kodu widać juz gdzie są problemy i można je rozwiązać.

Gdyby posiadać typy faktycznie nienullowalne to i tak trzebaby je opatrzeć w jakieś wartości domyślne i sprawdzać te wartości domyślne jeżeli ich nie chcemy. Różnica tylko taka, że z obecnym nullable opieramy się na analizie kodu, a nie na faktycznych wartościach (które i tak by trzeba sprawdzać, jeśli byłyby nieczekiwane).

Można dyskutować czy lepszy jest twardy zakaz przypisania nulla do typu nienullowalnego czy samo ostrzeżenie wystarczy, tylko dużej różnicy nie ma skoro w obu przypadkach można wszystko osiągnąć, kwestia tylko upodobania.

1
AbcDefGhi napisał(a):
Riddle napisał(a):
AbcDefGhi napisał(a):

Nie trzeba obsługiwać nulli w metodach bo jak cały kod opatrzysz ifami żeby nie mieć nigdzie nulla to null nie przyjdzie do metody i nie musisz sie nim martwić.

Brawo! 🥂 Nie trzeba obsługiwać nulli w metodach bo jak cały kod opatrzysz ifami . Nie trzeba być wegetarianinem, wystarczy nie jeść mięsa. Nie trzeba być martwym, wystarczy nie umierać.

Przyjacielu, @AbcDefGhi to "opatrywanie ifami" to jest właśnie ta obsługa o której mówiłem. Typy w językach programowania są właśnie po to żebyś nie musiał robić takich checków sam.

Wiadomo, że można przypisać nulla do List<int>, tylko wtedy dostaniesz ostrzeżenie o tym, że takie coś wystąpiło i jak to zignorujesz to sam sobie jesteś winny. To sprawdzanie robisz tylko tam gdzie napiszesz np List<int>? , jak napiszesz samo List<int> to nie musisz sprawdzać bo jak nigdzie nie masz ostrzeżeń to znaczy, że nulla tam nie ma.

Cały czas mówię to samo.

Po to masz system typów, żebyś nie musiał sam tego robić.

tylko wtedy dostaniesz ostrzeżenie o tym, że takie coś wystąpiło i jak to zignorujesz to sam sobie jesteś winny Jak masz normalny system typów, jak np w Kotlinie, i przekażesz nulla w miejscu które go nie przyjmuje to nie dostajesz jakiegoś głupiego ostrzeżenia, tylko błąd kompilacji po prostu. To jest normalny system typów.

AbcDefGhi napisał(a):

Cel tego jest taki, aby dać możliwość uniknięcia niespodziewanego nulla. Owszem czasami trzeba sie naprodukować, żeby to osiągnąć, ale da się. A dodatkowo przez znak ? masz szybką możliwość określenia czy oczekujesz nulla czy nie, bez potrzeby tworzenia nowego typu specjalnie przeznaczonego do trzymania wartosci domyslnej.

Domyślam się. W normalnych jezykach, jak np Kotlin masz: List<int> nie przyjmuje null, List<int>? przyjmuje.

W C# jest inaczej. w List<int>? możesz wsadzić null, ale w List<int> (bez ?) też możesz. Owszem, dostaniesz ostrzeżenie, ale nadal możesz. System typów tego nie odrzuci, mimo że powinien.

Dlatego powiedziałem ze to jest zepsute. Jeśli wsadzasz do języka "nullable types", czyli np. List<int>? to normalną konsekwencją tego jest to, że jak typ nie ma tego ?, to wsadzenie null jest traktowany jako błąd typów - w c# nie jest, mimo że powinien. Podobnie jest w PHP ?array przyjmuje null, ale jak masz array to wsadzenie null kończy się błędem.

AbcDefGhi napisał(a):

Można dyskutować czy lepszy jest twardy zakaz przypisania nulla do typu nienullowalnego czy samo ostrzeżenie wystarczy,

No oczywiście że lepsze byłoby zakaz wsadzenia null do typu nienulowalnego bez dwóch zdań.

AbcDefGhi napisał(a):

tylko dużej różnicy nie ma skoro w obu przypadkach można wszystko osiągnąć, kwestia tylko upodobania.

Jeśli taki argument wyciągasz, to równie dobrze możnaby się w ogóle pozbyć sprawdzania typów - wtedy też dałoby się wszystko osiągnąć.

0
Riddle napisał(a):
AbcDefGhi napisał(a):

tylko dużej różnicy nie ma skoro w obu przypadkach można wszystko osiągnąć, kwestia tylko upodobania.

Jeśli taki argument wyciągasz, to równie dobrze możnaby się w ogóle pozbyć sprawdzania typów - wtedy też dałoby się wszystko osiągnąć.

Tu bym tak nie przesadzał. Wtedy byś musiał manualnie sprawdzać czy jest null, czy go nie ma, a tak po analizie kodu już wiesz i nie musisz pisać ifa.

Generalnie zgodze się, że mogliby kiedyś zablokować przypisywania nulla do typu bez ?, jednak dla mnie osobiście nie jest to jakiś game-changer, bo i tak kodu to nie zmieni, tylko latwiej będzie zauważyć błąd ludzki.

0
AbcDefGhi napisał(a):

jednak dla mnie osobiście nie jest to jakiś game-changer, bo i tak kodu to nie zmieni, tylko latwiej będzie zauważyć błąd ludzki.

No tak.

Przecież to jest cały sens istnienia typów w języku 😐 Jeśli nie ludzki błą

0

@Riddle
A co ma zrobić kompilator w momencie kiedy ustawisz obiekt, że nie przyjmuje nulla, pobierasz ten obiekt z bazy, a jego tam nie ma? Kompilator zakłada, że ten obiekt nigdy nie będzie null, a mimo to, na złość tobie, ktoś wszedł i w bazie usunął jakiś słownikowy wpis, czysto hipotetycznie 🤔

0
AdamWox napisał(a):

@Riddle
A co ma zrobić kompilator w momencie kiedy ustawisz obiekt, że nie przyjmuje nulla, pobierasz ten obiekt z bazy, a jego tam nie ma? Kompilator zakłada, że ten obiekt nigdy nie będzie null, a mimo to, na złość tobie, ktoś wszedł i w bazie usunął jakiś słownikowy wpis, czysto hipotetycznie 🤔

To samo co jak wsadziłby obiekt złego typu. Np zamienił inta na stringa. Czyli najpewniej wyrzucić błąd w runtime. Poza tym, co ma jedno do drugiego? Język to język, baza to baza.

Kończę temat, bo rozmowa miała być o Monadach Option i Maybe.

Odpowiadając na pytanie: w Kotlinie nullable types są dobrze zrobione, i wolę ich używać. w C# nullable totalnie nie działa. To miała być tylko mała wstawka.

Przepraszam czytających że tak się rozrósł temat w złą stronę.

0

Ja uważam, że patrzysz przez zły pryzmat dlatego wydaje ci się to bezsensu i dlatego twierdzisz, że nullable w C# nie działa. Owszem, mógł Microsoft bardziej restrykcyjnie podejść do tematu nulli w C#, ale obawiam się, że to by był major change w samym języku i stare projekty by się na potęgę sypały, albo byś musiał tydzień poświęcić na ogarnięcie samych nulli.

8

Jakaś dyskusja na wiele postów tutaj się rozciągnęła o tym, że system typów powinien zablokować możliwość wsadzenia nulla do typu, który na nulle nie pozwala i narzekanie, że kompilator C# wywala tylko warningi.

Ale przecież w drugim swoim poście tutaj pokazałem jak zrobić żeby wywalał błąd kompilacji, więc o co tutaj tak na prawdę chodzi?

Cytując:

To pokaż mi jak przypisujesz null do takiego typu mając <Nullable>enable</Nullable> i <WarningsAsErrors>Nullable</WarningsAsErrors>.

0

Po to masz system typów, żebyś nie musiał sam tego robić.

tylko wtedy dostaniesz ostrzeżenie o tym, że takie coś wystąpiło i jak to zignorujesz to sam sobie jesteś winny Jak masz normalny system typów, jak np w Kotlinie, i przekażesz nulla w miejscu które go nie przyjmuje to nie dostajesz jakiegoś głupiego ostrzeżenia, tylko błąd kompilacji po prostu. To jest normalny system typów.

Ale C# ma taki system typów, tylko jest opcjonalny(z oficjalną rekomendacja ze trzeba go ręcznie ustawić). Jest opcjonalny z przyczyn kompatybilności wstecznej oraz względnej młodości tego podejścia. Cały legancy kod by się wywalił gdyby włączał się automatycznie, lub nie które zależności mogą nie obsługiwać go poprawnie. Jest tez jeden lub dwa przypadki gdy analizatory głupieją. Z tych przyczyn nullable jest opcjonalne. W projekcie pisanym całkowicie od zera, w najnowszyn .net, można mieć system typów w C# który eliminuje możliwość null'a na amen.

0
_flamingAccount napisał(a):

Ale C# ma taki system typów, tylko jest opcjonalny(z oficjalną rekomendacja ze trzeba go ręcznie ustawić). Jest opcjonalny z przyczyn kompatybilności wstecznej oraz względnej młodości tego podejścia.

To nie znaczy że dobry.

2

Tak, staram się używać Result<T> gdzie tylko mogę.

@Saalin

Pisząc w C# trzeba się przyzwyczaić do tego, że si-szaropowcy w kontekście monady Maybe są na poziomie Optional.isPresent z Javy i żadnymi mapami/flatmapami nie są zainteresowani.

tru, nie jestem zainteresowany.

0
Riddle napisał(a):
AdamWox napisał(a):

W C# przecież jest nullable 🤔 po co komplikować projekt jakimiś bibliotekami?

I nie działa, bo nawet typy które nie są nullable, np List list, mogą dostać null. 👏

Do Optionala też możesz wstawić null i wszystko się sypie. W C# jest nullable, masz ??, możesz pisać extensiony do nullable jeśli chcesz podobny experience do Optionali i kod jest dużo bardziej czytelny.

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