Discriminated unions v C++

Discriminated unions v C++
« kdy: 21. 10. 2020, 23:09:51 »
C++ obsahuje únie, ktoré umožňujú na jedno miesto v pamäti uložiť rozličné typy. Práca s úniami je však krkolomná a všetko si musí ošéfovať porogramátor.

STL obsahuje typ std::variant. Ten má nevýhodu zas v tom, že nerozlišuje hodnoty podľa názvov tagu, ale podľa číselného indexu alebo typu. Taktiež nemôže obsahovať rovnaký typ označený odlišným tagom

Niekto si možno položí otázku načo potrebujem mať v únii rovnaký typ viac krát. Tak uvediem príklad:

Kód: [Vybrat]
type Currency =
| USD of decimal
| EUR of decimal
| BTC of decimal
| Another of (currencyName : string) * decimal
| None

let toUSD (value : Currency) =
     match value with
     | USD val -> val
     | EUR val -> val * usdEurCourse
     | BTC val -> val * usdBtcCourse
     | _ -> failwith "Unsupported currency"

let comodityMarketsValues = Map [
      "Gold", USD(5.75M)
      "Platinium DAX", EUR(5.23M)
      "FSTE Oil", Another("GBP", 1023.22M)
]

let comodityMarketsValuesInUSD = comodityMarketsValues |> Map.map(fun key value -> value |> toUSD)

Neviete o niečom takom aj pre C++? Ideálne keby to bolo súčasťou STL alebo Boostu.
« Poslední změna: 21. 10. 2020, 23:11:57 od fortran1986 »


Re:Discriminated unions v C++
« Odpověď #1 kdy: 22. 10. 2020, 09:33:46 »
Co takhle si udělat otagovaný decimal a cpát do variantu ten? Pak budou dolary a eura různé typy.

Re:Discriminated unions v C++
« Odpověď #2 kdy: 22. 10. 2020, 19:23:05 »
Já teda C++ skoro neznám (dělám v Rustu, ale ze zvědavosti sleduju tvoje příspěvky, a vždycky potají pláču nad tím, s čím se programátoři v C++ musí potýkat), ale je určitě pravda jak píšeš, že

STL obsahuje typ std::variant. Ten má nevýhodu zas v tom, že nerozlišuje hodnoty podľa názvov tagu, ale podľa číselného indexu alebo typu. Taktiež nemôže obsahovať rovnaký typ označený odlišným tagom

?

Když se dívám na https://en.cppreference.com/w/cpp/utility/variant, píší tam že A variant is permitted to hold the same type more than once, and to hold differently cv-qualified versions of the same type. .

Tedy mi to vyznívá, že to umí to co potřebuješ, klasický součtový typ. Ale možná mi něco uniká..

alex6bbc

  • *****
  • 1 432
    • Zobrazit profil
    • E-mail
Re:Discriminated unions v C++
« Odpověď #3 kdy: 22. 10. 2020, 19:49:15 »
vsecko je to v tomto pripade jenom double cislo s ruznou menou.
proc by nestacila struktura obsahujici hodnotu jako double a typ jako enum {dollar, eu, kacka, kuna, .....}

Re:Discriminated unions v C++
« Odpověď #4 kdy: 22. 10. 2020, 19:57:41 »
S tými menami to je len príklad, alebo naozaj riešený problém? Prečo chceš na to použiť niečo variant/union a nie jednoducho napr. struct { double value; std::string currency; } ?
Zdá sa mi to jednoduchšie, prehľadnejšie, meny nie sú zadrátované v kóde, ale môžu byť v databáze. Podľa mňa uniony a varianty sú niečo používané len výnimočne, ale ožno sa pohybuješ v špecializovanej oblasti :)


Re:Discriminated unions v C++
« Odpověď #5 kdy: 23. 10. 2020, 09:27:51 »
Myslím, že se tu trochu zaměňuje variant/union a pattern matching. Pattern matching v C++ skutečně není. Otagovat si nějaká data ale není problém.
Kód: [Vybrat]
#include <variant>

enum class CurrencyTag {
    Eur,
    Usd,
    Btc   
};

template<CurrencyTag Tag>
struct CurrencyType {
    double value;
};

template<CurrencyTag... Tags>
using CurrencyVariant = std::variant<CurrencyType<Tags>...>;

using Currency = CurrencyVariant<CurrencyTag::Eur, CurrencyTag::Usd, CurrencyTag::Btc>;

constexpr double EUR_TO_USD = 1/1.1;
constexpr double BTC_TO_USD = 12000.;

constexpr double toUsd(const Currency &currency) {
    struct ToUsd {
        constexpr double operator()(const CurrencyType<CurrencyTag::Eur>& currency) const {
            return currency.value * EUR_TO_USD;
        }
        constexpr double operator()(const CurrencyType<CurrencyTag::Usd>& currency) const {
            return currency.value;
        }
        constexpr double operator()(const CurrencyType<CurrencyTag::Btc>& currency) const {
            return currency.value * BTC_TO_USD;
        }
    };
    return std::visit(ToUsd{}, currency);
}

int main() {
    static_assert(toUsd(CurrencyType<CurrencyTag::Btc>{1.}) == BTC_TO_USD);
    return 0;
}

Re:Discriminated unions v C++
« Odpověď #6 kdy: 23. 10. 2020, 10:44:39 »
Myslím, že najväčší problém je, že sa k tomu vyjadrujú tí, ktorí nielen že nepoznajú odpoveď a ani nechápu otázku, čo sú, podľa mňa, všetci okrem #1 a #5.

Pattern matching priamo zabudovaný C++ zatiaľ nemá, ale dá sa napísať aj jednoduchšie ako v tej peknej ukážke v #5, a skoro sa to na pattern matching podobá.


Re:Discriminated unions v C++
« Odpověď #7 kdy: 23. 10. 2020, 10:50:26 »
... omylom som to odoslal skôr ako som chcel.

Dovolil som si kód z #5 trochu upraviť:

Kód: [Vybrat]
#include <variant>

template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>;

enum class CurrencyTag {
    Eur,
    Usd,
    Btc   
};

template<CurrencyTag Tag>
struct CurrencyType {
    double value;
};

template<CurrencyTag... Tags>
using CurrencyVariant = std::variant<CurrencyType<Tags>...>;

using Currency = CurrencyVariant<CurrencyTag::Eur, CurrencyTag::Usd, CurrencyTag::Btc>;

constexpr double EUR_TO_USD = 1/1.1;
constexpr double BTC_TO_USD = 12000.;

constexpr double toUsd(const Currency &currency) {
    return std::visit(overload {
[](const CurrencyType<CurrencyTag::Eur>& currency) { return currency.value * EUR_TO_USD; },
[](const CurrencyType<CurrencyTag::Usd>& currency) { return currency.value; },
[](const CurrencyType<CurrencyTag::Btc>& currency) { return currency.value * BTC_TO_USD; }
    },
    currency);
}

int main() {
    static_assert(toUsd(CurrencyType<CurrencyTag::Btc>{1.}) == BTC_TO_USD);
    return 0;
}


Re:Discriminated unions v C++
« Odpověď #8 kdy: 23. 10. 2020, 16:19:21 »
Myslím, že najväčší problém je, že sa k tomu vyjadrujú tí, ktorí nielen že nepoznajú odpoveď a ani nechápu otázku, čo sú, podľa mňa, všetci okrem #1 a #5.

Pattern matching priamo zabudovaný C++ zatiaľ nemá, ale dá sa napísať aj jednoduchšie ako v tej peknej ukážke v #5, a skoro sa to na pattern matching podobá.

No, zdá se mi, že jsi si nepřečetl pořádně otázku. Podle té má mít typ Currency další dvě varianty,

Kód: [Vybrat]
Another of (currencyName : string) * decimal
| None

které se ale v toUSD nepoužijí (není to úplná funkce). Vyřešil jsi tedy jinou otázku. Tím nechci říct, že tvá odpověď neobsahuje užitečné informace, spíš tě chválím, že jsi své odpovědi #6 a #7 vynechal ze seznamu těch, u kterých tvrdíš, že porozuměly zadání.

Mimochodem, jak by se pomocí std::variant rozšířil typ Currency tak, aby odpovídal původnímu zadání, a jak by vypadala příslušná toUsd, to by mě docela zajímalo.

Re:Discriminated unions v C++
« Odpověď #9 kdy: 23. 10. 2020, 16:39:46 »
Myslím, že najväčší problém je, že sa k tomu vyjadrujú tí, ktorí nielen že nepoznajú odpoveď a ani nechápu otázku, čo sú, podľa mňa, všetci okrem #1 a #5.

Pattern matching priamo zabudovaný C++ zatiaľ nemá, ale dá sa napísať aj jednoduchšie ako v tej peknej ukážke v #5, a skoro sa to na pattern matching podobá.

No, zdá se mi, že jsi si nepřečetl pořádně otázku. Podle té má mít typ Currency další dvě varianty,

Kód: [Vybrat]
Another of (currencyName : string) * decimal
| None

které se ale v toUSD nepoužijí (není to úplná funkce). Vyřešil jsi tedy jinou otázku. Tím nechci říct, že tvá odpověď neobsahuje užitečné informace, spíš tě chválím, že jsi své odpovědi #6 a #7 vynechal ze seznamu těch, u kterých tvrdíš, že porozuměly zadání.

Mimochodem, jak by se pomocí std::variant rozšířil typ Currency tak, aby odpovídal původnímu zadání, a jak by vypadala příslušná toUsd, to by mě docela zajímalo.
To rozšíření je celkem jednoduché, ne?
Pro another tam přidat :
Kód: [Vybrat]
struct AnotherCurrency
{
  std::string name;
  double value;
};
A jako prázdný chlívek pro none je v C++ knihovně prázdný typ std::monostate, který je myšlený právě pr prázdný variant.
Samozřejmě že s těmahle variantama se toUSD jako totální funkce napsat nedá.

A pak je taky samozřejmě problém double. Cpát peníze do doublu není dobrý nápad. Pro čísla ekonomická je nějaká astronomická přesnost naprosto nedostatečná. Takže to chce nějakou arbitrary precision libku. V boostu myslím nějaká byla.

Re:Discriminated unions v C++
« Odpověď #10 kdy: 23. 10. 2020, 23:56:58 »
Dobrý deň, ďakujem Vám za odpovede. Zatiaľ som ich preletel len veľmi rýchlo. Zajtra si ich prečítam a poodpisujem. (Doteraz som kôli práci nestíhal).

Len v skratke: viem, že existuje viacero spôsobov, ako niečo podobné docieliť a tiež viem, že sa to dá spraviť aj flexibilnejšie. Bol to len príklad, ktorý ma v rýchlosti napadol pre vysvetlenie aby ste pochopili ako to funguje a čo vlastne v C++ hľadám. Sú veci na, ktoré sa discriminated unions hodia. Už len taký typ option(v haskelli sa mu hovorí maybe) je discriminated union:

Kód: [Vybrat]
type Option<'t> =
| Some of 't
| None

alebo si môžete nadefinovať typ:

Kód: [Vybrat]
type LoginResult =
| Auth of {| Uid : int64; Login : string; Password : string; FirstName : string; Roles : Roles; Profile : Profile |}
| InvalidField of int * string * string
| Error of int * string

A vyhnete sa tak používaniu výnimiek

alebo si môžete vytvoriť rekurzívnu discriminated union

Kód: [Vybrat]
type MultiTree<'a> =
| Leaf of 'a
| Node of MultiTree<'a> list

prípadne

Kód: [Vybrat]
type ast =
| Nil
| Unit
| Identifier of string
| IdentifiersPath of string list
| Block of ast list
| ExprList of ast list
| Root of ast list
...

Pointa je, že by som potreboval niečo, čo sa vzdialenie podobá na úniu z Ocaml, dá sa do nej vložiť napríklad aj viacero typov, ale nechcem aby únia rozlišovala hodnoty podľa typu, ale podľa tagu.

S tým zadrátovaním máte pravdu, tento príklad by som v praxi aj ja riešil inak.

Že sa niečo v Ruste dá riešiť elegantnejšie viem. Rust je veľmi pekný jazyk. Ja tiež používam jazyky z rodiny ML, ktoré sú ideálne na rýchly vývoj aplikácií a v ktorých je radosť programovať, ale C++ baví z viacerých dôvodov (je univerzálne, je rozšírené, dá sa tam hrať z detailami a optimalizovať, je to hlavný jazyk pre Unreal Engine, je to jazyk orientovaný na rýchlosť, nič pred programátorom neskrýva atď). Ale máte pravdu, že má aj veľa nedostatkov.
« Poslední změna: 23. 10. 2020, 23:58:57 od fortran1986 »

Re:Discriminated unions v C++
« Odpověď #11 kdy: 24. 10. 2020, 10:17:42 »
C++ baví z viacerých dôvodov (je univerzálne, je rozšírené, dá sa tam hrať z detailami a optimalizovať, je to hlavný jazyk pre Unreal Engine, je to jazyk orientovaný na rýchlosť, nič pred programátorom neskrýva atď).

Řekl bych, že o Rustu to všechno platí taky až na "rozšířené" (to se doufám změní :-) a "hlavní jazyk pro Unreal Engine" (to chápu, že může být blocker). Ale nechci ho nikomu vnucovat.

BoneFlute

  • *****
  • 1 981
    • Zobrazit profil
Re:Discriminated unions v C++
« Odpověď #12 kdy: 24. 10. 2020, 14:17:30 »
C++ baví z viacerých dôvodov (je univerzálne, je rozšírené, dá sa tam hrať z detailami a optimalizovať, je to hlavný jazyk pre Unreal Engine, je to jazyk orientovaný na rýchlosť, nič pred programátorom neskrýva atď).

Řekl bych, že o Rustu to všechno platí taky až na "rozšířené" (to se doufám změní :-) a "hlavní jazyk pro Unreal Engine" (to chápu, že může být blocker). Ale nechci ho nikomu vnucovat.

Naprosto souhlas.

IMHO Rust sestřelil C na úroveň udržovacího legaci kódu. Má všechny výhody C a málo z jeho nevýhod.

Re:Discriminated unions v C++
« Odpověď #13 kdy: 24. 10. 2020, 15:57:25 »
... omylom som to odoslal skôr ako som chcel.

Dovolil som si kód z #5 trochu upraviť:
.............................

tamten trailing return ze čtvrtýho řádku
Kód: [Vybrat]
template<class... Ts> overload(Ts...) -> overload<Ts...>;
neni v c++20 potřeba protože tam std::visit jakoby umí správně uhádnout návratovej typ a nemusí se mu radit hele :) ;)

účelu týdletý template z řádků 18-21
Kód: [Vybrat]
template<CurrencyTag... Tags>
using CurrencyVariant = std::variant<CurrencyType<Tags>...>;

using Currency = CurrencyVariant<CurrencyTag::Eur, CurrencyTag::Usd, CurrencyTag::Btc>;
nerozumim ale mam takovej pocit žeje bezpečný ji min pro c++20 nahradit takle jednoduše :o :o
Kód: [Vybrat]
using Currency = std::variant<CurrencyTag::Eur, CurrencyTag::Usd, CurrencyTag::Btc>;
táákže výslednej upravenej zdrojáček takle nějak

Kód: [Vybrat]
#include <variant>

template<class... Ts> struct overload : Ts... { using Ts::operator()...; };

enum class CurrencyTag {
    Eur,
    Usd,
    Btc   
};

template<CurrencyTag Tag>
struct CurrencyType {
    double value;
};

using Currency = std::variant<CurrencyTag::Eur, CurrencyTag::Usd, CurrencyTag::Btc>;

constexpr double EUR_TO_USD = 1/1.1;
constexpr double BTC_TO_USD = 12000.;

constexpr double toUsd(const Currency &currency) {
    return std::visit(overload {
[](const CurrencyType<CurrencyTag::Eur>& currency) { return currency.value * EUR_TO_USD; },
[](const CurrencyType<CurrencyTag::Usd>& currency) { return currency.value; },
[](const CurrencyType<CurrencyTag::Btc>& currency) { return currency.value * BTC_TO_USD; }
    },
    currency);
}

int main() {
    static_assert(toUsd(CurrencyType<CurrencyTag::Btc>{1.}) == BTC_TO_USD);
    return 0;
}


a samozdřejmě to de furt postaru overloadingem/přetěžováním jakože bez použití lambda funkcí variadic templates hele std::variant hele a visitoru takle nadivoko :D ;D ;D ;)
Kód: [Vybrat]
#include <iostream>

enum class CurrencyTag {
    Dolary,
    Tolary,
    Rubly   
};

template<CurrencyTag Tag>
struct CurrencyType {
    double value;
};

constexpr double TOLARY_TO_DOLARY = 123.456;
constexpr double RUBLY_TO_DOLARY = 0.0001;

constexpr double toUSD(const CurrencyType<CurrencyTag::Dolary> & platidlo){return platidlo.value;}
constexpr double toUSD(const CurrencyType<CurrencyTag::Tolary> & platidlo){return platidlo.value * TOLARY_TO_DOLARY;}
constexpr double toUSD(const CurrencyType<CurrencyTag::Rubly> & platidlo){return platidlo.value * RUBLY_TO_DOLARY;}

int main()
{
    CurrencyType<CurrencyTag::Tolary> hrnecPlnejTolaru = {5};
    CurrencyType<CurrencyTag::Rubly> cenaVodky = {1};
    std::cout << toUSD(hrnecPlnejTolaru) << std::endl;
    std::cout << toUSD(cenaVodky) << std::endl;
    return 0;
}
von už si to kompilátor jakoby nějak rozumě přežvejkne :D ;D ;) ;)
lidi postižený environmentálním žalem hele choděj za ekopsycholožkama hele 🤡 💆 🤡 💆

Re:Discriminated unions v C++
« Odpověď #14 kdy: 24. 10. 2020, 17:44:40 »
Mě též přijde enum měna ok. Ale prosím, nedržte stav účtu v double. Použijte celočíselný typ. Ušetříte hodně peněz.