FP a error handling

FP a error handling
« kdy: 02. 12. 2025, 18:31:29 »
Nechápem, prečo keď FP zavrhuje výnimky a namiesto toho odporúča používať union typy ako Result alebo Option, tak F# aj tak podporuje klasické výnimky z .NET-u a dokonca má aj vlastné, neobjektové výnimky nazývané exception (čo je špecialita F#, v C# ani v iných jazykoch to nefunguje):

.NET exceptions:
Kód: [Vybrat]
open System
raise(Exception(message = "Klasicka .NET exception"))

F# exceptions:
Kód: [Vybrat]
exception Chyba of string
raise(Chyba "Typovo cista F# exception")

typ result:
Kód: [Vybrat]
let (/) x y =
    match y with
    | 0 -> Error("Delenie nulou neni povolene")
    | _ -> Ok (x / y)

Okrem toho má ešte aj asynchrónne výnimky cez Async.Catch, a zároveň poskytuje typy Result aj Option. Nehovorím, že je to blbosť – z pohľadu výkonu je Result lepší ako výnimky. Ale aj tak nechápem, prečo sa vo F# dá robiť jedna vec piatimi rôznymi spôsobmi. Začína to pôsobiť, akoby F# bobtnal podobne ako C++.

Aký spôsob error handlingu používate v FP jazykoch?


BoneFlute

  • *****
  • 2 095
    • Zobrazit profil
Re:FP a error handling
« Odpověď #1 kdy: 03. 12. 2025, 03:30:56 »
Nemůže to být tím, že hodně FP jazyků směřuje ke statickému typování a tam jsou výjimky tak nějak komplikací - jiný kanál?

Co se týče syntaxe, tak třeba Rust se svýma "?" je docela zkousnutelný.

Kód: [Vybrat]
(a div (b - c))? + 4

Re:FP a error handling
« Odpověď #2 kdy: 03. 12. 2025, 08:01:31 »
(Předesílám, že F# ani .NET svět vůbec neznám)
Proč podporuje .NETové výjimky bych chápal, prostě to běží na .NET a musí to s ním nějak fungovat.
Z rychlého googlení ty "neobjektové výjimky" jsou klasické .NET výjimky (dědí od System.Exception), takhle se prostě v F# deklarujou.
A Result prý existuje až od F# 4.0, předtím se asi používaly výjimky i ve "funkcionálním" jazyce (prostě z toho tehdy ten .NET víc trčel).

Re:FP a error handling
« Odpověď #3 kdy: 03. 12. 2025, 08:14:04 »
Ono i historicky F# vychází z OCamlu, který používal výjimky mnohem více než .NET - např. i funkce find pro hledání v Hashtbl vyhazovala výjimku Not_found, když klíč nenašla (find_opt tehdy neexistovala).

Výjimky jde pak přirozeně zobecnit na algebraické efekty, což udělal OCaml 5. Takže po vyhození a zpracování se můžete vrátit do místa vyhození (např. ošetříte chybu a pokračujete z místa, kde vznikla) třeba s náhradní hodnotou.

Jinak bych řekl, že výkon Result v .NET bude horší než v případě výjimek, pokud chyba moc často nenastává. Důvodem je, že výsledek zbytečně obalujete a pak ho musíte vybalit.

BoneFlute

  • *****
  • 2 095
    • Zobrazit profil
Re:FP a error handling
« Odpověď #4 kdy: 04. 12. 2025, 18:50:04 »
algebraické efekty

Algebraické efekty jsou super věc. Další z těch, které teprve čekají na objevení v mainstreimových jazycích. A taky GADTs, toho se taky jen tak nedočkáme.


Re:FP a error handling
« Odpověď #5 kdy: 05. 12. 2025, 09:12:14 »
V ryzím FP nemá chytání výjimek svoje místo. Házení výjimek problém není – každý výraz se může buď vyhodnotit, nebo jeho vyhodnocení může selhat (např. skončit nekonečnou sérií tail callů – každý Turing-complete jazyk musí něco podobného umožnit), a výjimky jsou jen jiným druhem selhání. A kde o užitečnější druh selhání než nikdy nekončící výpočet. Ale chytání výjimek je teoreticky problematické – když se nějaký podvýraz není schopen vyhodnotit, najednou to může způsobit úplně jiný výsledek.

V reálných jazycích bývá typicky snaha o nějaký pragmatický přístup k FP, ne o ryzí FP. Jo, je to rozdíl mezi Haskellem a F#. A jak bylo zmíněno, F# má fungovat na platformě .NET s dalšími jazyky, a tyto jazyky mohou házet výjimky, takže F# je umí házet a nejspíš i chytat.

Disclaimer: F# jsem viděl jen z rychlíku (s Haskellem jsem na tom lépe), nicméně ten dotaz je celkem obecný, tak jsem si na něj troufl odpovědět.

Re:FP a error handling
« Odpověď #6 kdy: Dnes v 18:24:37 »
No, F# bylo mj. navržené tak, aby byla možná interoperabilita s .NET knihovnami, ze kterých se do F# plíží logicky jak nulls, tak i exceptions z System.Exception. Knihoven čistě funkcionálních, které mají na výstupu Option nebo Result, je málo (např. Thoth.Json).

Mimochodem, F# má ještě zjednodušenou funkci failwith, která vyhodí běžnou System.Exception. Co vím, vyhazování exceptions se používá jen výjimečně, kdy je neúčelné vytahovat Result type někde z útrob kódu.

V F# musíš manuálně převádět exceptions/nulls do Result/Option. No, jako, nemusíš, když nechceš, ale pak neprogramuješ funkcionálně. A proč tedy Result/Option, je tvá hlavní otázka, že? Můžeš se zeptat ChataGPT, ale jeho odpověď ti asi nedá takovou představu, jako když si to zkusíš osobně a uvidíš sám, jak se v code flow pracuje s Option/Result (můj subjektivní názor je, že je to super sqělé) a FP jazyky na to mají prostředky, takže nemáš všude pyramid of doom (plno vnořených větvení), jak se zmiňoval nějaký FP hater neznající funkcionální prvky. Domnívám se, že je to asi jediná cesta k plnému pochopení vhodnosti Option/Result (v Haskellu a jiných jazycích se to nazývá jinak, ale jedná se stejný princip). Koneckonců pokud bys chtěl spolupracovat s některou FP firmou, budou chtít Option/Result, ne exceptions, viděl jsem to přímo v textech inzerátů (připrav se na něco takového https://github.com/MiroslavHustak/FSharp-Coding-Guidelines ).

Všimni si, že máš většinou dva stavy - vyhovující a nevyhovující (žádoucí výsledek/null, žádoucí výsledek/exception), no, a k tomu pasuje discriminated union (sum type, enum atd. v jiných jazycích), což jsou Option/Result types. Takže taháš obou-stavové řešení bezpečně až třeba do nějaké transformation layer mezi FE a BE https://github.com/MiroslavHustak/OdisTimetableDownloaderMAUI/blob/master/ApplicationDesign/KODIS_Record4.fs. Ale jak jsem psal, nejlépe je si to zkusit sám.

Async.Catch neháže exceptions, ale převádí je na typ Choice<'a,'exn>, což je předchůdce dnešního Result (takže dnes se tam přidá Result.ofChoice a je to tam, kde to chceme mít).

Sorry za používání anglických výrazů, ale jsem jen samouk všechno studující z anglických podkladů.
« Poslední změna: Dnes v 18:30:17 od mira9998 »

Re:FP a error handling
« Odpověď #7 kdy: Dnes v 22:12:30 »
Když už jsem se zmínil o prostředcích, které F# má pro zacházení s Result type, tak je to třeba toto computation expression (result je jméno builderu):

Kód: [Vybrat]
result
     {
         let! relative = relativePath preparedSource entry
         let! dest =
             Path.Combine(target, relative)
             |> Option.ofNullEmpty
             |> Option.toResult "Failed getting combined path"
     
         return! moveEntry entry dest
     }

Lze ještě použít monadic composition nebo ROP-style function composition (to jsou všechny ty Result.bind/map/either a kyble dalších funkcí). Bez nich nebo bez výše uvedeného CE bychom měli tři vnořené větve pattern matchingu.

Re:FP a error handling
« Odpověď #8 kdy: Dnes v 22:38:54 »
Ty vnořené větve kódu by vypadaly nějak takto:

Kód: [Vybrat]
match relativePath preparedSource entry with
 | Error err -> Error err
 | Ok relative
     ->
     match Path.Combine(target, relative) |> Option.ofNullEmpty with
     | None
         -> Error "Failed getting combined path"
     | Some dest
         ->
         match moveEntry entry dest with
         | Ok result -> Ok result
         | Error err -> Error err