Portál AbcLinuxu, 6. června 2024 14:24

Podpora pro rozměrovou analýzu v programovacích jazycích

25.4.2021 21:15 | Přečteno: 2554× | programování | Výběrový blog | poslední úprava: 25.4.2021 21:15

Kdysi dávno jsem na root.cz četl článek o programovacím jazyku Ada, kde autor mimo jiné ukazoval, jak lze silný typový systém jazyka použít k tomu, aby za nás překladač hlídal fyzikální rozměr hodnot v programu podobně, jako jsme na to zvyklí u běžných datových typů. Nedávno jsem si na to znovu vzpomněl, chvíli si s tím hrál a v tomto zápisku dal dohromady triviální demonstraci současných možností podpory jednotek v jazycích Ada, F# a Python.

Ada

Demonstrace z toho článku o jazyku Ada vypadala nějak takto:

type Metry is new Float;
type Ctverecni_Metry is new Float;

-- Přetížení operátoru násobení pro datový typ metry tak, aby vracel metry
-- čtvereční.
function "*" (Left, Right : Metry) return Ctverecni_Metry is
begin
  return Ctverecni_Metry(Float(Left)*Float(Right));
  -- před násobením jsme přetypovali na float, abychom zabránili rekurzi
  -- takto donutíme překladač použít standardní násobení pro typ Float
end;

declare
  vyska : Metry := 10.0;
  sirka : Metry := 15.0;
  plocha_a : Ctverecni_Metry;
  plocha_b : Metry;
begin
  plocha_a := vyska*sirka; -- tohle je ok
  plocha_b := vyska*sirka; -- zde nastane chyba prekladu
end;

Pamatuji si, že to na mě tenkrát udělalo dojem. Ale tehdy jsem moc programovacích jazyků neznal a ani jsem to dál nezkoumal. Na druhou stranu jsem si z toho taky mylně na chvíli odnesl dojem, že podobné věci jsou záležitostí silně typovaných a málo používaných jazyků jako Ada. Což ale není úplně přesné, jak si ještě ukážeme.

Když jsem se k tomu teď ze zvědavosti vrátil a chtěl si to vyzkoušet (v repozitáři Fedory nebo Debianu lze najít balíček s GNU Ada překladačem GNAT), ukázalo se, že je třeba ten kód trochu vylepšit, aby to ve skutečnosti opravdu fungovalo. Což v tomto případě znamená, aby to šlo přeložit s chybou, která demonstruje, jak to hlídání jednotek pěkně funguje (-:

procedure Example1 is
  type Meters is new Float;
  type Meters_Squared is new Float;
  function "*" (Left, Right : Meters) return Meters_Squared is
  begin
    return Meters_Squared(Float(Left)*Float(Right));
  end;
  function "*" (Left, Right : Meters) return Meters is abstract;
  len_a : Meters := 10.0;
  len_b : Meters := 15.0;
  surface : Meters_Squared;
  len_sum : Meters;
begin
  len_sum := len_a + len_b; -- ok
  surface := len_a * len_b; -- ok
  len_sum := len_a * len_b; -- invalid
end Example1;

Když opominu uhlazení dělající z toho příkladu samostatný Ada program, bylo třeba přidat deklaraci function "*" (Left, Right : Meters) return Meters is abstract, která tuto variantu násobení zděděnou z typu Float potlačí. A překlad pak opravdu chybu v rozměru zachytí, i když ta chybová hláška vypadá trochu zvláštně (zkoušeno s gcc-gnat-10.2.1-9 na Fedoře 33):

$ gnatmake -q example1.adb
example.adb:16:20: expected type "Meters" defined at line 2
example.adb:16:20: found type "Meters" defined at line 2
gnatmake: "example.adb" compilation error

Obecné modelování fyzikálních rozměrů tímto způsobem nemusí být zcela přímočaré ani praktické. I když jednodušší případy, kdy se obejdeme bez rozměrové analýzy, můžou fungovat pěkně, jak je vidět na příkladu práce s metry a mílemi z úvodního kurzu jazyka Ada.

Pro jazyk Ada v přehledu metod rozměrové analýzy se dozvíme, že tohle bylo jasné už někdy v 80. letech, kdy se tento problém začal řešit. Např. N. H. Gehani v článku z roku 1985 popisuje použití typového systému s přetížením operátorů (podobně jako to dělá naše ukázka výše) a dochází k tomu, že to obecně nefunguje:

Derived types only partially solve the problem of detecting the inconsistent usage of objects; some valid usages of objects are also not allowed. Moreover, the solution is inelegant and inconvenient to use.

To vedlo k návrhu různých knihoven zavádějících datové struktury obsahující hodnotu spolu s jednotkou a funkce pro práci s nimi. A něco takového je možné implementovat v libovolném jazyce, i když konkrétní přístup a garance, co knihovna programátorovi dává, se můžou v závislosti na možnostech jazyka dost lišit.

Překladač GNAT dnes implementuje systém pro kontrolu rozměrů veličin, který staví na tzv. Aspects ze standadru Ada 2012, a je doplněn knihovnou System.Dim.Mks s definicí základních fyzikálních jednotek dle SI.

S použitím tohoto rozšíření by náš příklad vypadal takto:

with System.Dim.Mks; use System.Dim.Mks;
procedure Example2 is
  len_a : Length := 10.0*m;
  len_b : Length := 15.0*m;
  surface : Area;
  len_sum : Length;
begin
  len_sum := len_a + len_b; -- ok
  surface := len_a * len_b; -- ok
  len_sum := len_a * len_b; -- invalid
end Example2;

A jak se můžeme přesvědčit, opravdu to funguje:

$ gnatmake -q -gnat2012 example2.adb
example2.adb:10:11: dimensions mismatch in assignment
example2.adb:10:11: left-hand side has dimension [L]
example2.adb:10:11: right-hand side has dimension [L**2]
gnatmake: "example2.adb" compilation error

Taková podpora jednotek je pak někde mezi implementací přímo v jazyce, a pouhou knihovnou.

F#

Jeden z mála programovacích jazyků s přímou podporou pro práci s jednotkami, o kterém jste možná už někdy slyšeli, je funkcionální jazyk F#. Typový systém tohoto jazyka totiž umožňuje s jednotkami přímo pracovat, takže např. typ float<m> reprezentuje desetinné číslo pro počet metrů, zatímco float je desetinné číslo bez jednotky. Popis jak to funguje najdete na stránce Units of measure.

Předchozí příklad přepsaný do jazyka F# by vypadal nějak takto:

[<Measure>] type m

let len_a = 10.0<m>
let len_b = 15.0<m>
let len_sum : float<m>   = len_a + len_b // ok
let surface : float<m^2> = len_a * len_b // ok
let len_c   : float<m>   = len_a * len_b // invalid

A když se jej pokusíme přeložit, skončíme na očekávané chybě v jednotkách:

$ dotnet run
/home/martin/projects/hello-fsharp/Program.fs(7,36): error FS0001: The unit of measure 'm' does not match the unit of measure 'm ^ 2' [/home/martin/projects/hello-fsharp/hello-fsharp.fsproj]

The build failed. Fix the build errors and run again.

Python

Knihoven pro práci s jednotkami pro jazyk Python existuje hned několik (viz přehled Python modulů pro rozměrovou analýzu). Pro ukázku jsem zvolil knihovnu Pint, čímž ale nechci tvrdit, že jde o nejlepší nebo nejpopulárnější modul tohoto typu (ostatní knihovny jsem nezkoušel).

Náš předchozí příklad musíme při převodu do pythonu trochu upravit. I když python typový systém má, proměnné nelze typ explicitně přiřadit, a navíc knihovna Pint typový systém pro reprezentaci jednotek stejně nepoužívá. Takže místo pokusu o přiřazení metrů čtverečních do proměnné s počtem metrů, zkusíme metry čtvereční s metry prostě sečíst:

import pint

ureg = pint.UnitRegistry()

len_a = 10 * ureg.m
len_b = 15 * ureg.m
len_sum = len_a + len_b # ok
surface = len_a * len_b # ok
len_c = surface + len_b # invalid

A vidíme, že při spuštění programu dostáváme očekávanou chybu:

$ python example.py
Traceback (most recent call last):
  File "/home/martin/tmp/example.py", line 9, in <module>
    len_c = surface + len_b
  File "/usr/lib/python3.9/site-packages/pint/quantity.py", line 1018, in __add__
    return self._add_sub(other, operator.add)
  File "/usr/lib/python3.9/site-packages/pint/quantity.py", line 110, in wrapped
    return f(self, *args, **kwargs)
  File "/usr/lib/python3.9/site-packages/pint/quantity.py", line 930, in _add_sub
    raise DimensionalityError(
pint.errors.DimensionalityError: Cannot convert from 'meter ** 2' ([length] ** 2) to 'meter' ([length])

Další rozdíl oproti předchozím příkladům pochopitelně je, že jde o běhovou chybu. Ale pokud vám záleží na odhalení těchto chyb už v době překladu, asi nebudete používat python.

Ale i v jazyce jako python se imho může hodit, že vám počítač s jednotkami pomáhá:

>>> import pint
>>> ureg = pint.UnitRegistry()
>>> current = (300 * ureg.watt) / (6 * ureg.volt)
>>> current
<Quantity(50.0, 'watt / volt')>
>>> current.dimensionality
<UnitsContainer({'[current]': 1})>
>>> current.to_base_units()
<Quantity(50.0, 'ampere')>
>>> (current * 30 * ureg.minute).to(ureg.ampere*ureg.hour)
<Quantity(25.0, 'ampere * hour')>

A klepne vás přes prsty, pokud po něm chcete nesmysl:

>>> (current * 30 * ureg.minute).to(ureg.watt*ureg.hour)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.9/site-packages/pint/quantity.py", line 605, in to
    magnitude = self._convert_magnitude_not_inplace(other, *contexts, **ctx_kwargs)
  File "/usr/lib/python3.9/site-packages/pint/quantity.py", line 554, in _convert_magnitude_not_inplace
    return self._REGISTRY.convert(self._magnitude, self._units, other)
  File "/usr/lib/python3.9/site-packages/pint/registry.py", line 944, in convert
    return self._convert(value, src, dst, inplace)
  File "/usr/lib/python3.9/site-packages/pint/registry.py", line 1804, in _convert
    return super()._convert(value, src, dst, inplace)
  File "/usr/lib/python3.9/site-packages/pint/registry.py", line 1410, in _convert
    return super()._convert(value, src, dst, inplace)
  File "/usr/lib/python3.9/site-packages/pint/registry.py", line 977, in _convert
    raise DimensionalityError(src, dst, src_dim, dst_dim)
pint.errors.DimensionalityError: Cannot convert from 'minute * watt / volt' ([current] * [time]) to 'hour * watt' ([length] ** 2 * [mass] / [time] ** 2)

(-:

Závěr

Pokud vás tohle téma zaujalo, doporučuji se podívat na článek Dimensional Analysis in Programming Languages, kde najdete rozsáhlý přehled implementací rozměrové analýzy v mnoha programovacích jazycích.

A pokud nějakou knihovnu pro práci s jednotkami ve svém kódu používáte, dejte vědět v komentářích.

       

Hodnocení: 100 %

        špatnédobré        

Anketa

Práci s jednotkami v nějakém programovacím jazyku jsem:
 (50 %)
 (9 %)
 (36 %)
 (5 %)
Celkem 22 hlasů

Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

Komentáře

Nástroje: Začni sledovat (2) ?Zašle upozornění na váš email při vložení nového komentáře. , Tisk

Vložit další komentář

26.4.2021 07:36 _
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Odpovědět | Sbalit | Link | Blokovat | Admin
Většinou na počítání, jak jinak ;-) Ale v kterem jayzku je ta kalkulačka napsaná fakt nevím.
26.4.2021 12:29 OldFrog {Ondra Nemecek} | skóre: 36 | blog: Žabákův notes | Praha
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Odpovědět | Sbalit | Link | Blokovat | Admin
Hodně zajímavé... díky.
-- OldFrog
26.4.2021 13:51 Tomáš
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Odpovědět | Sbalit | Link | Blokovat | Admin
Pro mě to je novinka, díky za ni. Momentálně to nepoužiju, ale je dobré vědět, že něco takového existuje.
Gréta avatar 26.4.2021 21:58 Gréta | skóre: 36 | blog: Grétin blogísek | 🇮🇱==❤️ , 🇵🇸==💩 , 🇪🇺==☭
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Odpovědět | Sbalit | Link | Blokovat | Admin

se muže hodit :D ;D

oslavná píseň na pana soudruha generalisima prezidentčíka Petra Pavla Pávka 🎶🫡🦚🎶
27.4.2021 07:03 kotrcka | skóre: 23 | blog: Onééé 2 | Praha
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Na čo? Adventný kód tým nevyriešiš a ani na mimibazar sa to nehodí :-D
Keďže tu účet nejde zrušiť, zmenil som si heslo na random a "zabudol ho".
27.4.2021 19:15 OldFrog {Ondra Nemecek} | skóre: 36 | blog: Žabákův notes | Praha
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Hodí se to všude kde se počítá. Typicky tam, kde má výpočet nějaký vztah k reálnému světu (počty, balení, rozměry, hmotnosti, fyzikální vlastnosti, koordináty...) nebo kde se kladou extra podmínky k tomu, aby měl výpočet smysl. Je překvapivé, jak opomíjené to je téma. Dobrý typový systém by měl být standard, realite je ovšem jiná.

V praxi se to dohání nejčastěji implementací pomocí knihoven - příklady: Geografická knihovna mi nedovolí sčítat zeměpisnou délku se šířkou a poskytne jakž takž nějakou záruku, že má prováděný výpočet reálný geografický smysl.

Další příklad: Matematické knihovny pro počítání s určitou kategorií čísel, například BigDecimal nebo BigInteger. Sice nepohlídá jednotky, ale alespoň garantuje určité vlastnosti kladené na danou kategorii čísel. To samé knihovny pro komplexní čísla.
-- OldFrog
27.4.2021 20:59 Tomáš
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Taky záleží na tom, jak se to projeví na výkonu. Třeba v Pythonu se nám vyplácelo některé úkony řešit poněkud humpolácky manuálně, než to nechat na Pythonu. Nějaké počítání s jednotkami by nám to úplně zabilo.

Taky je otázka, jak chytrá by taková knihovna mohla být. Třeba v geografických knihovnách je to samý sinus a kosinus, kde se dá nasekat plno chyb. Počítám, že tak chytré to asi nebude, aby to ohlídalo, že dosadím správný úhel.
27.4.2021 21:47 marbu | skóre: 31 | blog: hromada | Brno
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Ideálně se to na výkonu neprojeví, viz ten příklad v Adě nebo F#, kde se ta kontrola se provede v rámci kompilace. Pro výpočty v Pythonu se často používá numpy, a i když ta knihovna pint podporu pro numpy má, nějaký další overhead tam určitě bude.
There is no point in being so cool in a cold world.
30.4.2021 09:15 Jindřich Makovička | skóre: 17
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Jinak C++ má boost::units, kde se taky všechno řeší při kompilaci. A std::chrono už zvládá časové jednotky taky obstojně.
HO▽ORK△ avatar 28.4.2021 10:34 HO▽ORK△ | skóre: 4 | blog: HOVORKUV_CTVERECKOVANY_SVET_DO_KAZDE_DOMACNOSTI
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Odpovědět | Sbalit | Link | Blokovat | Admin

Mám tvého Tuxe!

28.4.2021 16:24 marbu | skóre: 31 | blog: hromada | Brno
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Neřekl bych, že tu existuje něco jako limit na počet tučňáků nebo zákon o zachování jejich počtu, ale můžu se mýlit :)
There is no point in being so cool in a cold world.
28.4.2021 11:20 luky
Rozbalit Rozbalit vše Re: Podpora pro rozměrovou analýzu v programovacích jazycích
Odpovědět | Sbalit | Link | Blokovat | Admin
V C toto jde hlidat nastrojem sparse.

Založit nové vláknoNahoru

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.