Vestavěné operátory programovacího jazyka Standard ML
Co se dozvíte v článku
- Vestavěné operátory programovacího jazyka Standard ML
- Zápis standardních operátorů
- Přetěžování operátorů
- Definice vlastních operátorů
- Volba asociativity operátorů
- Nový operátor s nízkou prioritou
- Zápis operátoru speciálními znaky
- Strukturované uživatelské datové typy: od jednoduchých výčtových typů po rekurzivní datové struktury
- Definice výčtového typu s osmi základními barvami
- Definice nového unifikovaného typu color a funkce pro převod barvy na trojici složek RGB
- Přidání možnosti modifikace základních barev o atribut světlost
- Přidání podpory pro reprezentaci barvy ve stupních šedi
- Zjednodušení zápisu funkce color_as_string s využitím nového operátoru kolony
- Poslední typ barvy: mix dvou barev (rekurzivní datový typ)
- Transpřeklad poslední varianty colors6.sml do jazyka Lua a JavaScriptu
- Příloha: Makefile pro transpřeklad všech demonstračních příkladů
- Repositář s demonstračními příklady
- Literatura
- Předchozí články o jazyku ML/SML i o jazycích OCaml a F#
- Odkazy na Internetu
Vzhledem k tomu, že překladač (resp. přesněji řečeno transpřekladač) LunarML, kterým se v této sérií článků zabýváme, je jednou z implementací programovacího jazyka Standard ML, bude možné dnešní článek založit právě na specifikaci Standard ML (což je mimochodem jeden z mála mainstreamových programovacích jazyků, které jsou formálně popsány a navíc i definovány tímto formálním popisem).
V programovacím jazyku Standard ML nalezneme relativně velké množství různých operátorů. Musíme si však uvědomit, že operátory jsou ve skutečnosti definovány jako běžné funkce a samotný zápis operátoru je pouhý syntaktický cukr (ovšem velmi praktický), který spočívá v tom, že se (infixové) operátory zapisují mezi operandy a nepoužívají se závorky okolo operandů (pokud to není nutné z jiných důvodů – například při určení priority atd.). Pokud na chvíli zůstaneme u infixových operátorů, tak to konkrétně znamená, že zápis:
<výraz1> <operátor;> <výraz2>
je ve skutečnosti ze sémantického pohledu totožný se zápisem:
<operátor;>(<výraz1>, <výraz2>)
<operátor;> <výraz1> <výraz2>
V následující tabulce jsou vypsány všechny standardní operátory programovacího jazyka Standard ML. Ty se od sebe liší (pochopitelně) nejenom prováděnou operací, ale taktéž prioritou a asociativitou. Priorita určuje (stejně jako v jiných jazycích), v jakém pořadí se budou operátory vyhodnocovat v případě, že jsou použity v jediném výrazu (například ve výrazu 1+2*3 jsou zapsány dva operátory a pořadí vyhodnocení závisí na jejich prioritě). A asociativita určuje uzávorkování operátorů se stejnou prioritou, tj. ovlivní, zda se provede (1+2)+3 nebo naopak 1+(2+3).
Z níže uvedené tabulky je patrné, že většina operátorů je takzvaně asociativní zleva, ovšem skupina dvou operátorů s prioritou 5 má asociativitu zprava. V tomto případě se konkrétně jedná o operátory určené pro spojení řetězců popř. seznamů, s nimiž jsme se ve stručnosti seznámili v úvodním článku o transpřekladači LunarML:
| Operátor | Priorita | Asociativita | Poznámka |
|---|---|---|---|
| * | 7 | zleva | pro int i real |
| / | 7 | zleva | operandy musí být typu real |
| div | 7 | zleva | operandy musí být typu int |
| mod | 7 | zleva | operandy musí být typu int |
| + | 6 | zleva | pro int i real |
| – | 6 | zleva | pro int i real |
| ^ | 6 | zleva | spojení řetězců |
| :: | 5 | zprava | konstrukce seznamu připojením dalšího prvku |
| @ | 5 | zprava | spojení seznamů |
| = | 4 | zleva | rovnost |
| <> | 4 | zleva | nerovnost |
| > | 4 | zleva | |
| >= | 4 | zleva | |
| < | 4 | zleva | |
| <= | 4 | zleva | |
| := | 3 | zleva | přiřazení do měnitelné proměnné |
| o | 3 | zleva | kompozice funkcí |
| before | 0 | zleva | většinou definovaný v knihovně |
Booleovské operace andalso a orelse nejsou v tabulce uvedeny, protože se jedná o speciální konstrukce realizující zkrácené vyhodnocování. Z tohoto důvodu je nelze definovat formou funkcí (u funkcí se totiž parametry vyhodnocují před zavoláním funkce, kdežto v případě andalso a orelse tomu tak být nemusí).
Zápis standardních operátorů
Všechny operátory, které byly uvedeny v předchozí kapitole, jsou interně implementovány formou (běžných) funkcí a dále jsou definovány klíčovými slovy infix a v některých případech infixr. Samotná implementace operátorů není tématem dnešního článku, ovšem zajímavý je druhý krok, tedy použití klíčových slov infix a infixr, které vlastně v době překladu mění syntaxi programovacího jazyka:
infix 7 * / div mod infix 6 + - ^ infixr 5 :: @ infix 4 = <> > >= < <= infix 3 := o infix 0 before
Přetěžování operátorů
V mnoha programovacích jazycích se setkáme s takzvanými přetíženými operátory. Jedná se o operátory, jejichž funkce a vlastnosti se mění podle typu operandů. Pravděpodobně nejznámější formou přetížených operátorů jsou operátory určené pro provádění základních aritmetických operací, které bývají definovány pro různé numerické datové typy. Ovšem například operátor + může být přetížen vícekrát, typicky pro operaci spojení řetězců (viz například Java) či například n-tic a seznamů (Python). V těchto případech se nejenom mění funkce operátoru (tj. součet je zcela rozdílná operace od spojení řetězců), ale i jejich další vlastnosti, v tomto případě například komutativita (a nepřímo taktéž asociativita v případě hodnot s plovoucí řádovou čárkou).
Některé programovací jazyky umožňují definici zcela nových operátorů popř. přetížení existujících operátorů. Z tohoto pohledu můžeme programovací jazyky rozdělit do čtyř skupin na základě dvou vlastností (které jsou na sobě nezávislé):
- Lze definovat nové operátory?
- Lze přetížit stávající operátory?
Zajímavé je, že každá ze čtyř kombinací zmíněných v předchozí kapitole je obsazena nějakým programovacím jazykem (a to zcela nezávisle na dalších vlastnostech daného jazyka, tedy nezávisle na typovém systému, podpoře OOP, maker atd.):
Nové operátory nelze přidávat a existující operátory nelze dále přetěžovat:
| BASIC |
| C |
| Go |
| Java |
| JavaScript |
| Modula-2 |
| Objective-C |
| Pascal |
| TypeScript |
| Visual Basic |
Nové operátory nelze přidávat, ale existující operátory je možné přetěžovat:
| Ada |
| C# |
| C++ |
| D |
| Dart |
| FreeBASIC |
| Groovy |
| Java |
| Kotlin |
| Lua |
| MATLAB |
| Object Pascal |
| PHP |
| Perl |
| Python |
| Ruby |
| Rust |
| Visual Basic .NET |
Lze definovat nové operátory, existující operátory ovšem nelze dále přetěžovat:
| Standard ML (téma dnešního článku) |
| Pico |
| Prolog |
| Smalltalk |
Lze definovat nové operátory, navíc je možné přetěžovat existující operátory:
| ALGOL 68 |
| Eiffel |
| Fortran |
| F# |
| Haskell |
| Io |
| Nim |
| R |
| Raku |
| Scala |
| Swift |
Definice vlastních operátorů
V programovacím jazyku Standard ML sice není (prakticky) možné přetěžovat standardní operátory (viz též předchozí kapitolu), ovšem samotný jazyk nám nezabraňuje ve vytváření operátorů nových. U nových operátorů lze určit jejich prioritu i asociativitu, což je velmi užitečné (podle mého skromného názoru dokonce mnohem užitečnější, než pouhé přetěžování existujících operátorů).
Prvním krokem při vytváření nového operátoru je definice funkce se dvěma parametry, která bude následně zaregistrována jako nový binární operátor. Můžeme si například vyzkoušet zadefinovat funkci pro umocnění mn. S touto funkcí jsme se již setkali v úvodním článku o LunarML, takže jen ve zkratce:
fun pow(m, 0) = 1 | pow(m, n) = m * pow(m, n-1);
Tuto funkci následně převedeme na operátor, konkrétně na operátor asociativní zleva s prioritou 8 (priorita je tedy vyšší, než u součinu a podílu, což je dobře):
infix 8 pow;
Nyní je již možné nový operátor začít používat, a to se všemi výhodami, které přináší statický typový systém programovacího jazyka ML:
2 pow 3; 2 pow 3 + 1;
infixr 8 pow;
Vše si ověříme na uceleném příkladu:
(* Nový operátor pow *) fun pow(m, 0) = 1 | pow(m, n) = m * pow(m, n-1); infix 8 pow; val point = 1 pow 1; val square = 2 pow 2; val cube = 3 pow 3; print(Int.toString (point)); print "\n"; print(Int.toString (square)); print "\n"; print(Int.toString (cube)); print "\n";
Výsledky vypsané po překladu a spuštění:
1 4 27
Taktéž se podíváme na výsledek (trans)překlad do jazyka Lua. Převedení je dosti přímočaré:
local function pow(m, n)
if n == 0 then
return 1
end
local tmp = pow(m, _Int_sub(n, 1))
return _Int_mul(m, tmp)
end
local point = pow(1, 1)
local square = pow(2, 2)
cube = pow(3, 3)
local tmp = gsub(tostring(point), "-", "~")
outputAndFlush(stdOut, tmp)
outputAndFlush(stdOut, "\n")
local tmp1 = gsub(tostring(square), "-", "~")
outputAndFlush(stdOut, tmp1)
outputAndFlush(stdOut, "\n")
Výsledek transpřekladu do JavaScriptu vypadá podobně:
let pow;
pow = function(m, n) {
if (n === 0) {
return 1;
} else {
const tmp16 = pow(m, _Int54_sub(n, 1));
return _Int54_mul(m, tmp16);
}
};
const point = pow(1, 1);
const square = pow(2, 2);
const cube = pow(3, 3);
const tmp13 = toString(point);
outputAndFlush(stdOut[0], tmp13);
outputAndFlush(stdOut[0], Uint8Array.of(10));
const tmp14 = toString(square);
outputAndFlush(stdOut[0], tmp14);
outputAndFlush(stdOut[0], Uint8Array.of(10));
const tmp15 = toString(cube);
outputAndFlush(stdOut[0], tmp15);
outputAndFlush(stdOut[0], Uint8Array.of(10));
Volba asociativity operátorů
V předchozím textu byla vypsána pravidla platná pro nově definované operátory. Mj. jsme se zmínili o asociativitě, tedy o tom, zda bude pro nějaký operátor op zápis:
x op y op z
vyhodnocen takto:
(x op y) op z
či naopak takto:
x op (y op z)
Operátor definovaný klíčovým slovem infix bude mít prioritu zleva:
(* Nový operátor pow *) fun pow(m, 0) = 1 | pow(m, n) = m * pow(m, n-1); infix 8 pow; val result = 2 pow 3 pow 2; print(Int.toString (result)); print "\n";
Což vede k výpočtu (23)2=82=64:
64
Naproti tomu se operátor s asociativitou zprava definuje klíčovým slovem infixr (s „r“ na konci):
(* Nový operátor pow *) fun pow(m, 0) = 1 | pow(m, n) = m * pow(m, n-1); infixr 8 pow; val result = 2 pow 3 pow 2; print(Int.toString (result)); print "\n";
Nyní bude proveden výpočet 232=29=512:
512
Transpřeklad první verze do jazyka Lua bude následující:
local function pow(m, n)
if n == 0 then
return 1
end
local tmp = pow(m, _Int_sub(n, 1))
return _Int_mul(m, tmp)
end
local tmp = pow(2, 3)
local result = pow(tmp, 2)
Transpřeklad první verze do JavaScriptu:
pow = function(m, n) {
if (n === 0) {
return 1;
} else {
const tmp15 = pow(m, _Int54_sub(n, 1));
return _Int54_mul(m, tmp15);
}
};
const tmp13 = pow(2, 3);
const result = pow(tmp13, 2);
Transpřeklad druhé verze do jazyka Lua bude odlišný:
local function pow(m, n)
if n == 0 then
return 1
end
local tmp = pow(m, _Int_sub(n, 1))
return _Int_mul(m, tmp)
end
local tmp = pow(3, 2)
local result = pow(2, tmp)
Totéž platí i pro transpřeklad do JavaScriptu:
pow = function(m, n) {
if (n === 0) {
return 1;
} else {
const tmp15 = pow(m, _Int54_sub(n, 1));
return _Int54_mul(m, tmp15);
}
};
const tmp13 = pow(3, 2);
const result = pow(2, tmp13);
Nový operátor s nízkou prioritou
Podívejme se nyní na ukázku dalšího uživatelsky definovaného operátoru. Užitečný může být operátor nazvaný exists, který bude používán pro detekci, zda seznam obsahuje určitý prvek či nikoli (lepší by bylo operátor pojmenovat in, toto klíčové slovo je však již obsazeno). Vlastní implementace funkce exists je relativně snadná:
fun exists(element, []) = false
| exists(element, head::tail) = if element = head then true
else exists(element, tail);
Funkci (a jedná se o běžnou funkci) si samozřejmě můžeme otestovat:
exists(2, [1,2,3]); exists(0, [1,2,3]); exists(42, [42]); exists(42, []);
A následně lze funkci převést na operátor, a to konkrétně operátor s druhou nejnižší prioritou (nejnižší priorita je nastavena na nulu):
infix 1 exists;
Nyní se bude test existence prvku v seznamu zapisovat skutečně formou operátoru:
2 exists [1,2,3]; 0 exists [1,2,3]; 42 exists [42]; 42 exists [];
Sami si vyzkoušejte, jaký vliv má druhá nejnižší priorita (1) na tyto výrazy:
1+1 exists [1,2,3]; 3*1 exists [1,2,3]; 1+1 exists [1,2] @ [3,4];
Otestování chování v reálném příkladu:
(* Operátor exists *)
fun exists(element, []) = false
| exists(element, head::tail) = if element = head then true
else exists(element, tail);
infix 1 exists;
fun bool_to_string true = "true"
| bool_to_string false = "false";
fun print_bool b = print (bool_to_string b ^ "\n");
print_bool(1+1 exists [1,2,3]);
print "\n";
print_bool(3*1 exists [1,2,3]);
print "\n";
print_bool(1+1 exists [1,2] @ [3,4]);
print "\n";
Po spuštění by se měla zobrazit trojice řádků:
true true true
Opět se podíváme na výsledek (trans)překlad do jazyka Lua. Převod je dosti přímočarý:
local function exists(eq1)
return function(a)
if a[2] == nil then
return false
elseif a[2] ~= nil then
local element = a[1]
local tmp1 = a[2][1]
local tmp2 = a[2][2]
local tmp3 = eq1(element, tmp1)
if tmp3 then
return true
end
local tmp4 = exists(eq1)
return tmp4({element, tmp2})
else
_raise(_Match, "exists.sml:3:5")
end
end
end
Výsledek transpřekladu do JavaScriptu:
exists = function(eq1) {
return _wrap(function(a) {
if (a[1] === null) {
return [true, false];
} else if (! (a[1] === null)) {
const element = a[0];
const tmp22 = a[1][0];
const tmp23 = a[1][1];
const tmp24 = eq1(element, tmp22);
if (tmp24) {
return [true, true];
} else {
const tmp25 = exists(eq1);
return [false, tmp25, [[element, tmp23]]];
}
} else {
throw _Match;
}
});
Zápis operátoru speciálními znaky
Náš nový operátor pro umocnění byl prozatím pojmenován pow, což nevypadá jako běžný operátor. Ovšem jazyk Standard ML umožňuje použít pro zápis operátoru i některé speciální znaky. V praxi to tedy znamená, že například umocnění můžeme zapsat dvojicí znaků ** (stříška je již zabrána), takže si předchozí příklady zopakujme s novými jmény:
(* Nový operátor ** *) fun **(m, 0) = 1 | **(m, n) = m * **(m, n-1); infix 8 **; val point = 1 ** 1; val square = 2 ** 2; val cube = 3 ** 3; print(Int.toString (point)); print "\n"; print(Int.toString (square)); print "\n"; print(Int.toString (cube)); print "\n";
(* Nový operátor ** *) fun **(m, 0) = 1 | **(m, n) = m * **(m, n-1); infix 8 **; val result = 2 ** 3 ** 2; print(Int.toString (result)); print "\n";
(* Nový operátor ** *) fun **(m, 0) = 1 | **(m, n) = m * **(m, n-1); infixr 8 **; val result = 2 ** 3 ** 2; print(Int.toString (result)); print "\n";
Jen v rychlosti se podívejme i na způsob transpřekladu, kde se musí (kvůli omezení jazyků Lua i JavaScript) identifikátor funkce s realizací operátoru pojmenovat generovaným jménem:
local function _ASTER_ASTER(m, n)
if n == 0 then
return 1
end
local tmp = _ASTER_ASTER(m, _Int_sub(n, 1))
return _Int_mul(m, tmp)
end
local point = _ASTER_ASTER(1, 1)
local square = _ASTER_ASTER(2, 2)
cube = _ASTER_ASTER(3, 3)
let $ASTER$ASTER;
$ASTER$ASTER = function(m, n) {
if (n === 0) {
return 1;
} else {
const tmp16 = $ASTER$ASTER(m, _Int54_sub(n, 1));
return _Int54_mul(m, tmp16);
}
};
const point = $ASTER$ASTER(1, 1);
const square = $ASTER$ASTER(2, 2);
const cube = $ASTER$ASTER(3, 3);
Strukturované uživatelské datové typy: od jednoduchých výčtových typů po rekurzivní datové struktury
Ve druhé části dnešního článku se ještě jednou vrátíme k typovému systému tohoto zajímavého (a možná poněkud přehlíženého) programovacího jazyka. Ukážeme si, jakým způsobem lze realizovat datový typ pro reprezentaci barvy, a to v různých barvových modelech. Navíc bude podporována i definice barvy, která vznikne smícháním dvou jiných barev, což vlastně v důsledku musí znamenat, že datový typ je rekurzivní. Řešení, které využijeme, bude založeno na „neobjektovém“ přístupu, protože objekty v klasickém slova smyslu v jazyku Standard ML nenajdeme (ovšem například ideově velmi blízký jazyk OCaml práci s nimi podporuje). Ukážeme a zopakujeme si jak vlastnosti typového systému, tak i pattern matching (s jeho možným zkráceným zápisem).
tree = datatype 'a tree = Node of {data:'a, left:'a tree, right:'a tree}
| Leaf
Definice výčtového typu s osmi základními barvami
Popis typového systému Standard ML začneme datovým typem, který bychom již měli dobře znát z běžných programovacích jazyků, i když pod jiným jménem. Jedná se o takzvané disjunktní zobrazení, které obsahuje pouze výčet jmen. Tento datový typ použijeme pro reprezentaci základních osmi barev v barvové paletě:
datatype basic_color =
Black
| Red
| Green
| Yellow
| Blue
| Magenta
| Cyan
| White
;
Na rozdíl od jazyka OCaml se před název první barvy znak | nezapisuje, protože náš víceřádkový zápis vlastně znamená:
datatype basic_color = Black | Red | Green | Yellow | Blue | Magenta | Cyan | White ;
Jen na okraj: v jazyku OCaml vypadá definice disjunktního zobrazení nepatrně odlišně:
type basic_color = | Black | Red | Green | Yellow | Blue | Magenta | Cyan | White ;;
Definice nového unifikovaného typu color a funkce pro převod barvy na trojici složek RGB
V dalším textu budeme postupně budovat datový typ, který bude možné využít pro uložení barvy v jakékoli reprezentaci. První verze tohoto typu bude obsahovat jen jedinou možnost – již výše definovaný typ basic_color:
datatype color =
BasicColor of basic_color;
Postupně budeme přidávat i další možnosti.
Dále se pokusme definovat funkci, která převede barvu ze základní palety osmi barev na tři složky RGB. Tyto složky můžeme reprezentovat trojicí celočíselných hodnot v rozsahu 0 až 255 a samotný převod lze realizovat funkcí (nazvěme ji basic_color_to_rgb) s pattern matchingem. Realizace takové funkce je sice poněkud naivní, ale zcela korektní. Spočívá ve výčtu všech možných způsobů volání této funkce. Žádný jiný způsob volání této funkce možný není – to za nás ověří překladač (tudíž nikdy nevznikne výjimka typu „špatný parametr“):
fun basic_color_to_rgb Black = (0, 0, 0) | basic_color_to_rgb Red = (255, 0, 0) | basic_color_to_rgb Green = (0, 255, 0) | basic_color_to_rgb Yellow = (255, 255, 0) | basic_color_to_rgb Blue = (0, 0, 255) | basic_color_to_rgb Magenta = (255, 0, 255) | basic_color_to_rgb Cyan = (0, 255, 255) | basic_color_to_rgb White = (255, 255, 255) ;
Pro otestování funkcionality nového typu příklad doplníme o pomocné funkce provedeme konstrukci barev s výpisem výsledků:
datatype basic_color =
Black
| Red
| Green
| Yellow
| Blue
| Magenta
| Cyan
| White
;
datatype color =
BasicColor of basic_color;
fun basic_color_to_rgb Black = (0, 0, 0)
| basic_color_to_rgb Red = (255, 0, 0)
| basic_color_to_rgb Green = (0, 255, 0)
| basic_color_to_rgb Yellow = (255, 255, 0)
| basic_color_to_rgb Blue = (0, 0, 255)
| basic_color_to_rgb Magenta = (255, 0, 255)
| basic_color_to_rgb Cyan = (0, 255, 255)
| basic_color_to_rgb White = (255, 255, 255)
;
fun i2s integer = Int.toString(integer);
fun color_as_string (BasicColor c) =
let val (r, g, b) = basic_color_to_rgb c
in "red=" ^ i2s(r) ^ ", green=" ^ i2s(g) ^ ", blue=" ^ i2s(b) ^ "\n"
end
;
val c1 = BasicColor(Black);
print(color_as_string c1);
val c2 = BasicColor(Yellow);
print(color_as_string c2);
val c3 = BasicColor(White);
print(color_as_string c3);
Výsledky ukazují, že vše je (prozatím) implementováno korektně:
red=0, green=0, blue=0 red=255, green=255, blue=0 red=255, green=255, blue=255
Způsob překladu funkce color_as_string do jazyka Lua je přímočarý:
color_as_string = function(a)
local c = a
local r, g, b
if c == "Black" then
b = 0
g = 0
r = 0
elseif c == "Red" then
b = 0
g = 0
r = 255
elseif c == "Green" then
b = 0
g = 255
r = 0
elseif c == "Yellow" then
b = 0
g = 255
r = 255
elseif c == "Blue" then
b = 255
g = 0
r = 0
elseif c == "Magenta" then
b = 255
g = 0
r = 255
elseif c == "Cyan" then
b = 255
g = 255
r = 0
elseif c == "White" then
b = 255
g = 255
r = 255
else
_raise(_Match, "colors_2.sml:17:5")
end
local tmp = gsub(tostring(r), "-", "~")
local tmp1 = "red=" .. tmp .. ", green="
local tmp2 = gsub(tostring(g), "-", "~")
local tmp3 = tmp1 .. tmp2 .. ", blue="
local tmp4 = gsub(tostring(b), "-", "~")
return tmp3 .. tmp4 .. "\n"
end
Překlad do JavaScriptu je prakticky totožný, pouze se zde využívá vícenásobné přiřazení:
const color_as_string = function(a) {
const c = a;
let r, g, b;
if (c === "Black") {
[r, g, b] = [0, 0, 0];
} else if (c === "Red") {
[r, g, b] = [255, 0, 0];
} else if (c === "Green") {
[r, g, b] = [0, 255, 0];
} else if (c === "Yellow") {
[r, g, b] = [255, 255, 0];
} else if (c === "Blue") {
[r, g, b] = [0, 0, 255];
} else if (c === "Magenta") {
[r, g, b] = [255, 0, 255];
} else if (c === "Cyan") {
[r, g, b] = [0, 255, 255];
} else if (c === "White") {
[r, g, b] = [255, 255, 255];
} else {
throw _Match;
}
const tmp16 = toString(r);
const tmp17 = _String_append(_String_append(Uint8Array.of(114, 101, 100, 61), tmp16), Uint8Array.of(44, 32, 103, 114, 101, 101, 110, 61));
const tmp18 = toString(g);
const tmp19 = _String_append(_String_append(tmp17, tmp18), Uint8Array.of(44, 32, 98, 108, 117, 101, 61));
const tmp20 = toString(b);
return _String_append(_String_append(tmp19, tmp20), Uint8Array.of(10));
};
Přidání možnosti modifikace základních barev o atribut světlost
V navazujícím kroku provedeme další rozšíření našeho systému barev. Přidáme možnost určit u základních osmi barev jejich světlost, tj. atribut Dark či Bright. Typ tohoto atributu je triviální a lze ho zapsat opět s využitím disjunktního zobrazení:
datatype brightness =
Dark
| Bright
;
Důležitější ovšem je, že se změní i konstruktor pro základní barvy. Kromě jména barvy (Red atd.) se musí specifikovat i její světlost, takže výsledkem je (typově bezpečná) dvojice:
datatype color =
BasicColor of basic_color * brightness;
A pochopitelně musíme modifikovat i přepočet základních barev na RGB. Pokud má být základní barva světlá, neprovedeme žádnou změnu, ale pokud má být tmavá, podělíme všechny její barvové složky dvěma (to pro jednoduchost – v praxi se spíše násobí konstantou 2/3):
fun darker (r,g,b) = (r div 2, g div 2, b div 2) ; fun brightness rgb Dark = darker rgb | brightness rgb Bright = rgb ;
Výpočet si ověříme:
datatype basic_color =
Black
| Red
| Green
| Yellow
| Blue
| Magenta
| Cyan
| White
;
datatype brightness =
Dark
| Bright
;
datatype color =
BasicColor of basic_color * brightness;
fun darker (r,g,b) =
(r div 2, g div 2, b div 2)
;
fun brightness rgb Dark = darker rgb
| brightness rgb Bright = rgb
;
fun basic_color_to_rgb Black = (0, 0, 0)
| basic_color_to_rgb Red = (255, 0, 0)
| basic_color_to_rgb Green = (0, 255, 0)
| basic_color_to_rgb Yellow = (255, 255, 0)
| basic_color_to_rgb Blue = (0, 0, 255)
| basic_color_to_rgb Magenta = (255, 0, 255)
| basic_color_to_rgb Cyan = (0, 255, 255)
| basic_color_to_rgb White = (255, 255, 255)
;
fun i2s integer = Int.toString integer;
fun rgb_as_string r g b =
"red=" ^ i2s(r) ^ ", green=" ^ i2s(g) ^ ", blue=" ^ i2s(b) ^ "\n"
;
fun color_as_string (BasicColor c) =
let val (color, brightness_level) = c
val rgb = basic_color_to_rgb color
val (r, g, b) = brightness rgb brightness_level
in rgb_as_string r g b
end
;
val c1 = BasicColor(Black, Dark);
print(color_as_string c1);
val c2 = BasicColor(Black, Bright);
print(color_as_string c2);
val c3 = BasicColor(Yellow, Dark);
print(color_as_string c3);
val c4 = BasicColor(Yellow, Bright);
print(color_as_string c4);
val c5 = BasicColor(White, Dark);
print(color_as_string c5);
val c6 = BasicColor(White, Bright);
print(color_as_string c6);
Ověření spuštěním tohoto příkladu:
red=0, green=0, blue=0 red=0, green=0, blue=0 red=127, green=127, blue=0 red=255, green=255, blue=0 red=127, green=127, blue=127 red=255, green=255, blue=255
Přidání podpory pro reprezentaci barvy ve stupních šedi
Podporu barev rozšíříme o možnost reprezentace barvy ve stupních šedi. Pro jednoduchost budeme předpokládat, že taková barva bude specifikována jednou celočíselnou hodnotou v rozsahu 0..255 (prozatím nebudeme kontrolovat meze). Datový typ „barva“ tedy bude muset být rozšířen o další pojmenovaný prvek:
datatype color =
BasicColor of basic_color * brightness
| Gray of int
| RGB of int * int * int
;
A rozšířit budeme muset i funkci color_as_string pro převod barvy na trojici složek RGB. Pro jednoduchost bude převod ze stupňů šedi realizován tak, že se vstupní hodnota rozkopíruje do všech třech složek RGB (což ale nemusí být přesné – záleží na konkrétním barvovém prostoru):
fun color_as_string (BasicColor c) =
let val (color, brightness_level) = c
val rgb = basic_color_to_rgb color
val (r, g, b) = brightness rgb brightness_level
in rgb_as_string r g b
end
| color_as_string (Gray(g)) = rgb_as_string g g g
| color_as_string (RGB (r,g,b)) = rgb_as_string r g b
;
Zdrojový kód takto upraveného programu se sice prodlouží, ale jak definice datového typu color, tak i pomocných funkcí je stále velmi přehledná:
datatype basic_color =
Black
| Red
| Green
| Yellow
| Blue
| Magenta
| Cyan
| White
;
datatype brightness =
Dark
| Bright
;
datatype color =
BasicColor of basic_color * brightness
| Gray of int
| RGB of int * int * int
;
fun darker (r,g,b) =
(r div 2, g div 2, b div 2)
;
fun brightness rgb Dark = darker rgb
| brightness rgb Bright = rgb
;
fun basic_color_to_rgb Black = (0, 0, 0)
| basic_color_to_rgb Red = (255, 0, 0)
| basic_color_to_rgb Green = (0, 255, 0)
| basic_color_to_rgb Yellow = (255, 255, 0)
| basic_color_to_rgb Blue = (0, 0, 255)
| basic_color_to_rgb Magenta = (255, 0, 255)
| basic_color_to_rgb Cyan = (0, 255, 255)
| basic_color_to_rgb White = (255, 255, 255)
;
fun i2s integer = Int.toString integer;
fun rgb_as_string r g b =
"red=" ^ i2s(r) ^ ", green=" ^ i2s(g) ^ ", blue=" ^ i2s(b) ^ "\n"
;
fun color_as_string (BasicColor c) =
let val (color, brightness_level) = c
val rgb = basic_color_to_rgb color
val (r, g, b) = brightness rgb brightness_level
in rgb_as_string r g b
end
| color_as_string (Gray(g)) = rgb_as_string g g g
| color_as_string (RGB (r,g,b)) = rgb_as_string r g b
;
val c1 = BasicColor(Black, Dark);
print(color_as_string c1);
val c2 = BasicColor(Black, Bright);
print(color_as_string c2);
val c3 = BasicColor(Yellow, Dark);
print(color_as_string c3);
val c4 = BasicColor(Yellow, Bright);
print(color_as_string c4);
val c5 = BasicColor(White, Dark);
print(color_as_string c5);
val c6 = BasicColor(White, Bright);
print(color_as_string c6);
val c7 = Gray 128;
print(color_as_string c7);
val c8 = Gray 255;
print(color_as_string c8);
val c9 = RGB(10, 20, 30);
print(color_as_string c9);
Ověření funkcionality:
red=0, green=0, blue=0 red=0, green=0, blue=0 red=127, green=127, blue=0 red=255, green=255, blue=0 red=127, green=127, blue=127 red=255, green=255, blue=255 red=128, green=128, blue=128 red=255, green=255, blue=255 red=10, green=20, blue=30
Zjednodušení zápisu funkce color_as_string s využitím nového operátoru kolony
Funkce color_as_string je ve variantě použité v předchozím demonstračním příkladu sice použitelná a korektní, ale interně je příliš komplikovaná (minimálně z pohledu vývojářů používajících jazyky z větve ML):
fun color_as_string (BasicColor c) =
let val (color, brightness_level) = c
val rgb = basic_color_to_rgb color
val (r, g, b) = brightness rgb brightness_level
in rgb_as_string r g b
end
| color_as_string (Gray(g)) = rgb_as_string g g g
| color_as_string (RGB (r,g,b)) = rgb_as_string r g b
;
Ovšem funkci můžeme zjednodušit, a to poměrně elegantním způsobem. Nejdříve si nadefinujeme nový operátor, konkrétně operátor realizující kolonu (pipe). Bude se jednat o infixový operátor s nejnižší prioritou (ušetříme za závorky), který realizuje resp. umožňuje zřetězení funkcí. Levým operandem je parametru, pravým funkce. Operátor bude zapisován dvojicí znaků |>:
infix 0 |>; fun x |> f = f x;
Funkce color_as_string se zkrátí na jediný řádek, který převede barvu na trojici složek RGB a následně tuto trojici převede na řetězec:
fun color_as_string c =
c |> to_rgb |> rgb_as_string
;
Tím se celý převod stal čitelnější, ovšem zavádí novou funkci to_rgb. To je v pořádku; jedná se o rozložení problému na menší, snadno popsatelné části (mimochodem, i zde se používá pattern matching):
fun to_rgb (BasicColor c) : int * int * int =
let val (color, brightness_level) = c
val rgb = basic_color_to_rgb color
in brightness rgb brightness_level
end
| to_rgb (Gray(g)) = (g,g,g)
| to_rgb (RGB(r,g,b)) = (r,g,b)
;
Upravená varianta demonstračního příkladu bude vypadat následovně:
datatype basic_color =
Black
| Red
| Green
| Yellow
| Blue
| Magenta
| Cyan
| White
;
datatype brightness =
Dark
| Bright
;
datatype color =
BasicColor of basic_color * brightness
| Gray of int
| RGB of int * int * int
;
infix 0 |>
fun x |> f = f x;
fun darker (r,g,b) =
(r div 2, g div 2, b div 2)
;
fun brightness rgb Dark = darker rgb
| brightness rgb Bright = rgb
;
fun basic_color_to_rgb Black = (0, 0, 0)
| basic_color_to_rgb Red = (255, 0, 0)
| basic_color_to_rgb Green = (0, 255, 0)
| basic_color_to_rgb Yellow = (255, 255, 0)
| basic_color_to_rgb Blue = (0, 0, 255)
| basic_color_to_rgb Magenta = (255, 0, 255)
| basic_color_to_rgb Cyan = (0, 255, 255)
| basic_color_to_rgb White = (255, 255, 255)
;
fun i2s integer = Int.toString integer;
fun rgb_as_string (r, g, b) =
"red=" ^ i2s(r) ^ ", green=" ^ i2s(g) ^ ", blue=" ^ i2s(b) ^ "\n"
;
fun to_rgb (BasicColor c) : int * int * int =
let val (color, brightness_level) = c
val rgb = basic_color_to_rgb color
in brightness rgb brightness_level
end
| to_rgb (Gray(g)) = (g,g,g)
| to_rgb (RGB(r,g,b)) = (r,g,b)
;
fun color_as_string c =
c |> to_rgb |> rgb_as_string
;
val c1 = BasicColor(Black, Dark);
print(color_as_string c1);
val c2 = BasicColor(Black, Bright);
print(color_as_string c2);
val c3 = BasicColor(Yellow, Dark);
print(color_as_string c3);
val c4 = BasicColor(Yellow, Bright);
print(color_as_string c4);
val c5 = BasicColor(White, Dark);
print(color_as_string c5);
val c6 = BasicColor(White, Bright);
print(color_as_string c6);
val c7 = Gray 128;
print(color_as_string c7);
val c8 = Gray 255;
print(color_as_string c8);
val c9 = RGB(10, 20, 30);
print(color_as_string c9);
Poslední typ barvy: mix dvou barev (rekurzivní datový typ)
Nyní se dostáváme k nejzajímavější části námi vytvářeného typového systému barev. Přidáme do něj možnost definovat novou barvu mixem dvou jiných barev, přičemž bude možné specifikovat poměr obou barev. Například tedy budeme moci říci: „nová barva je z 10% tvořena základní červenou barvou a z 90% je tvořena RGB barvou (10, 20, 30)“ atd. Nebo samozřejmě můžeme novou barvu vytvořit mixem barev, které vznikly mixem jiných barev. Výsledkem je rekurzivní datový typ:
datatype color =
BasicColor of basic_color * brightness
| Gray of int
| RGB of int * int * int
| Mix of real * color * color
;
Samozřejmě budeme muset přidat novou větev do funkce to_rgb. V případě, že budeme chtít použít novou variantu Mix, je nutné z obou barev vypočítat jejich smíchané RGB složky. To jsem vyřešil vnořenou funkcí, protože zde dochází ke vzájemné rekurzi:
fun to_rgb (BasicColor c) : int * int * int =
let val (color, brightness_level) = c
val rgb = basic_color_to_rgb color
in brightness rgb brightness_level
end
| to_rgb (Gray(g)) = (g,g,g)
| to_rgb (RGB(r,g,b)) = (r,g,b)
| to_rgb (Mix(ratio, c1, c2)) =
let
fun mix_colors ratio color1 color2 =
let
val (r1,g1,b1) = to_rgb color1
val (r2,g2,b2) = to_rgb color2
in
(mix_component ratio r1 r2,
mix_component ratio g1 g2,
mix_component ratio b1 b2)
end
in mix_colors ratio c1 c2
end
;
Samotný výpočet smíchané barvy není nijak komplikovaný, ovšem má jeden problém – voláme v něm opět funkci to_rgb, protože míchané barvy je nejprve nutné převést na jejich RGB složky:
fun mix_colors ratio color1 color2 =
let
val (r1,g1,b1) = to_rgb color1
val (r2,g2,b2) = to_rgb color2
in
(mix_component ratio r1 r2,
mix_component ratio g1 g2,
mix_component ratio b1 b2)
end
Celý projekt s realizací poměrně komplikovaného datového typu color byl vytvořen pouze s využitím typového systému jazyka Standard ML (rekurzivní typy + n-tice + disjunktní zobrazení) a pattern matchingu. Jak je patrné, je celé řešení vlastně řešeno ortogonálně v porovnání s objektovým přístupem. V OO řešení by se (pravděpodobně) vytvořila hierarchie tříd, každá třída by měla vlastní realizaci to_rgb a stav i vlastní hodnota barvy by byla zapouzdřena a dostupná přes nějaké metody. Řešení na úrovni typů je odlišné – typ je jeden (i když rekurzivní) a i to_rgb je řešeno na jediném místě. Každý z použitých přístupů má pochopitelně své přednosti a zápory, které se týkají rozšiřitelnosti, čitelnosti, testovatelnosti apod.
Dále si povšimněte, že se v celém kódu setkáme pouze s jedním místem, v němž musíme konkrétně specifikovat datové typy (další dvě místa se týkají konverzních funkcí, ty jsou ale v jiných implementacích jazyka Standard ML součástí standardní knihovny). Jedná se o samotnou definici typu color. Ostatní zdrojový kód je již zcela založen na automaticky prováděné typové inferenci jazyka Standard ML:
datatype basic_color =
Black
| Red
| Green
| Yellow
| Blue
| Magenta
| Cyan
| White
;
datatype brightness =
Dark
| Bright
;
datatype color =
BasicColor of basic_color * brightness
| Gray of int
| RGB of int * int * int
| Mix of real * color * color
;
infix 0 |>;
fun x |> f = f x;
fun darker (r,g,b) =
(r div 2, g div 2, b div 2)
;
fun brightness rgb Dark = darker rgb
| brightness rgb Bright = rgb
;
fun basic_color_to_rgb Black = (0, 0, 0)
| basic_color_to_rgb Red = (255, 0, 0)
| basic_color_to_rgb Green = (0, 255, 0)
| basic_color_to_rgb Yellow = (255, 255, 0)
| basic_color_to_rgb Blue = (0, 0, 255)
| basic_color_to_rgb Magenta = (255, 0, 255)
| basic_color_to_rgb Cyan = (0, 255, 255)
| basic_color_to_rgb White = (255, 255, 255)
;
fun i2s integer = Int.toString integer;
fun rgb_as_string (r, g, b) =
"red=" ^ i2s(r) ^ ", green=" ^ i2s(g) ^ ", blue=" ^ i2s(b) ^ "\n"
;
fun real_of_int (i:int) : real =
Real.fromInt i;
fun int_of_real (r:real) : int =
Real.floor r;
fun mix_component ratio c1 c2 =
int_of_real ((real_of_int c1) * ratio + (real_of_int c2) * (1.0 - ratio));
fun to_rgb (BasicColor c) : int * int * int =
let val (color, brightness_level) = c
val rgb = basic_color_to_rgb color
in brightness rgb brightness_level
end
| to_rgb (Gray(g)) = (g,g,g)
| to_rgb (RGB(r,g,b)) = (r,g,b)
| to_rgb (Mix(ratio, c1, c2)) =
let
fun mix_colors ratio color1 color2 =
let
val (r1,g1,b1) = to_rgb color1
val (r2,g2,b2) = to_rgb color2
in
(mix_component ratio r1 r2,
mix_component ratio g1 g2,
mix_component ratio b1 b2)
end
in mix_colors ratio c1 c2
end
;
fun color_as_string c =
c |> to_rgb |> rgb_as_string
;
val c1 = BasicColor(Black, Dark);
print(color_as_string c1);
val c2 = BasicColor(Black, Bright);
print(color_as_string c2);
val c3 = BasicColor(Yellow, Dark);
print(color_as_string c3);
val c4 = BasicColor(Yellow, Bright);
print(color_as_string c4);
val c5 = BasicColor(White, Dark);
print(color_as_string c5);
val c6 = BasicColor(White, Bright);
print(color_as_string c6);
val c7 = Gray 128;
print(color_as_string c7);
val c8 = Gray 255;
print(color_as_string c8);
val c9 = RGB(10, 20, 30);
print(color_as_string c9);
val c10 = Mix(0.5, BasicColor(Red, Bright), RGB(100, 100, 100));
print(color_as_string c10);
val c11 = Mix(0.5, BasicColor(Black, Dark), BasicColor(White, Bright));
print(color_as_string c11);
Otestování funkcionality:
red=0, green=0, blue=0 red=0, green=0, blue=0 red=127, green=127, blue=0 red=255, green=255, blue=0 red=127, green=127, blue=127 red=255, green=255, blue=255 red=128, green=128, blue=128 red=255, green=255, blue=255 red=10, green=20, blue=30 red=177, green=50, blue=50 red=127, green=127, blue=127
Transpřeklad poslední varianty colors6.sml do jazyka Lua a JavaScriptu
Na závěr, vzhledem k tomu, že je tento miniseriál věnován mj. i transpřekladači LunarML, si ukážeme, jakým způsobem se zdrojový kód z předchozí kapitoly přeložil do jazyků Lua i do JavaScriptu.
Začneme variantou přeloženou do formy kompatibilní s jazykem Lua. Výsledek vlastně ani není příliš dlouhý, i když Lua je (sémanticky) jazykem méně mocným, než Standard ML:
Black = "Black"
Red = "Red"
Yellow = "Yellow"
White = "White"
Dark = "Dark"
Bright = "Bright"
rgb_as_string = function(r, g, b)
local tmp = gsub(tostring(r), "-", "~")
local tmp1 = "red=" .. tmp .. ", green="
local tmp2 = gsub(tostring(g), "-", "~")
local tmp3 = tmp1 .. tmp2 .. ", blue="
local tmp4 = gsub(tostring(b), "-", "~")
return tmp3 .. tmp4 .. "\n"
end
local mix_component = function(ratio, c1)
return function(c2)
local tmp = 1.0
local tmp1 = c1 * tmp
local tmp2 = tmp1 * ratio
local tmp3 = 1.0
local tmp4 = c2 * tmp3
local tmp5 = tmp2 + tmp4 * (1.0 - ratio)
local result = floor(tmp5)
if math_type(result) == "integer" then
return result
elseif tmp5 ~= tmp5 then
_raise(Domain, "real-1.sml:281:26")
else
_raise(_Overflow, "real-1.sml:283:26")
end
end
end
to_rgb = function(a)
if a.tag == "BasicColor" then
local c = a.payload
local color = c[1]
local brightness_level = c[2]
local rgb
if color == "Black" then
rgb = {0, 0, 0}
elseif color == "Red" then
rgb = {255, 0, 0}
elseif color == "Green" then
rgb = {0, 255, 0}
elseif color == "Yellow" then
rgb = {255, 255, 0}
elseif color == "Blue" then
rgb = {0, 0, 255}
elseif color == "Magenta" then
rgb = {255, 0, 255}
elseif color == "Cyan" then
rgb = {0, 255, 255}
elseif color == "White" then
rgb = {255, 255, 255}
else
_raise(_Match, "colors_6.sml:40:5")
end
if brightness_level == "Dark" then
local r = rgb[1]
local g = rgb[2]
local b = rgb[3]
local tmp = r // 2
local tmp1 = g // 2
return {tmp, tmp1, b // 2}
elseif brightness_level == "Bright" then
return rgb
else
_raise(_Match, "colors_6.sml:35:5")
end
elseif a.tag == "Gray" then
local g = a.payload
return {g, g, g}
elseif a.tag == "RGB" then
local r = a.payload[1]
local g = a.payload[2]
return {r, g, a.payload[3]}
elseif a.tag == "Mix" then
local ratio, b1, b2, tmp, tmp1
do
ratio = a.payload[1]
local c1 = a.payload[2]
local c2 = a.payload[3]
local exp = to_rgb(c1)
local r1 = exp[1]
local g1 = exp[2]
b1 = exp[3]
local exp1 = to_rgb(c2)
local r2 = exp1[1]
local g2 = exp1[2]
b2 = exp1[3]
local tmp2 = mix_component(ratio, r1)
tmp = tmp2(r2)
local tmp3 = mix_component(ratio, g1)
tmp1 = tmp3(g2)
end
local tmp2 = mix_component(ratio, b1)
local tmp3 = tmp2(b2)
return {tmp, tmp1, tmp3}
else
_raise(_Match, "colors_6.sml:68:5")
end
end
Dtto, ale ve verzi pro JavaScript:
const tmp12 = [NO_BUF];
const stdOut = [{buffer: [null], buffer_mode: tmp12, writer: tmp11}];
const Black = "Black";
const Red = "Red";
const Yellow = "Yellow";
const White = "White";
const Dark = "Dark";
const Bright = "Bright";
const rgb_as_string = function(r, g, b) {
const tmp28 = toString(r);
const tmp29 = _String_append(_String_append(Uint8Array.of(114, 101, 100, 61), tmp28), Uint8Array.of(44, 32, 103, 114, 101, 101, 110, 61));
const tmp30 = toString(g);
const tmp31 = _String_append(_String_append(tmp29, tmp30), Uint8Array.of(44, 32, 98, 108, 117, 101, 61));
const tmp32 = toString(b);
return _String_append(_String_append(tmp31, tmp32), Uint8Array.of(10));
};
const mix_component = function(ratio, c1) {
return function(c2) {
const tmp28 = c1 * ratio + c2 * (1.0 - ratio);
const tmp29 = floor(tmp28);
const tmp30 = isNaN(tmp29);
if (tmp30) {
throw Domain;
} else if (tmp29 < - 2.147483648e9 || tmp29 > 2.147483647e9) {
throw _Overflow;
} else {
const tmp31 = tmp29 | 0;
return tmp31;
}
};
};
let to_rgb;
to_rgb = function(a) {
if (a.tag === "BasicColor") {
const c = a.payload;
const color = c[0];
const brightness_level = c[1];
let rgb;
if (color === "Black") {
rgb = [0, 0, 0];
} else if (color === "Red") {
rgb = [255, 0, 0];
} else if (color === "Green") {
rgb = [0, 255, 0];
} else if (color === "Yellow") {
rgb = [255, 255, 0];
} else if (color === "Blue") {
rgb = [0, 0, 255];
} else if (color === "Magenta") {
rgb = [255, 0, 255];
} else if (color === "Cyan") {
rgb = [0, 255, 255];
} else if (color === "White") {
rgb = [255, 255, 255];
} else {
throw _Match;
}
if (brightness_level === "Dark") {
const r = rgb[0];
const g = rgb[1];
const b = rgb[2];
const tmp28 = 0 + Math.floor(r / 2);
const tmp29 = 0 + Math.floor(g / 2);
return [tmp28, tmp29, 0 + Math.floor(b / 2)];
} else if (brightness_level === "Bright") {
return rgb;
} else {
throw _Match;
}
} else if (a.tag === "Gray") {
const g = a.payload;
return [g, g, g];
} else if (a.tag === "RGB") {
const r = a.payload[0];
const g = a.payload[1];
return [r, g, a.payload[2]];
} else if (a.tag === "Mix") {
const ratio = a.payload[0];
const c1 = a.payload[1];
const c2 = a.payload[2];
const exp = to_rgb(c1);
const r1 = exp[0];
const g1 = exp[1];
const b1 = exp[2];
const exp1 = to_rgb(c2);
const r2 = exp1[0];
const g2 = exp1[1];
const b2 = exp1[2];
const tmp28 = mix_component(ratio, r1);
const tmp29 = tmp28(r2);
const tmp30 = mix_component(ratio, g1);
const tmp31 = tmp30(g2);
const tmp32 = mix_component(ratio, b1);
const tmp33 = tmp32(b2);
return [tmp29, tmp31, tmp33];
} else {
throw _Match;
}
};
Příloha: Makefile pro transpřeklad všech demonstračních příkladů
Všechny dnes použité demonstrační příklady i příklady ukázané v článcích a byly přeloženy z jazyka Standard ML do jazyků Lua a JavaScript následujícím souborem Makefile:
programs := \
accumulate.lua \
add_1.lua \
add_2.lua \
add_3.lua \
add_4.lua \
add_5.lua \
anonymous_add_1.lua \
anonymous_add_2.lua \
anonymous_inc_1.lua \
anonymous_inc_2.lua \
colors_1.lua \
colors_2.lua \
colors_3.lua \
colors_4.lua \
colors_5.lua \
colors_6.lua \
exc_1.lua \
exc_2.lua \
exc_3.lua \
exc_4.lua \
exc_5.lua \
exists.lua \
fibonacci_func_call_1.lua \
fibonacci_func_call_2.lua \
fibonacci_naive.lua \
fibonacci_pattern_matching.lua \
hello.lua \
length.lua \
map.lua \
mutual_recursion.lua \
pipe.lua \
pow_curry_1.lua \
pow_curry_2.lua \
pow_operator_1.lua \
pow_operator_2.lua \
pow_operator_3.lua \
pow_real_operator_1.lua \
pow_real_operator_2.lua \
pow_real_operator_3.lua \
some_none.lua \
some_none_2.lua \
string_join.lua \
sum_int.lua \
sum_real.lua \
tuples.lua \
accumulate.js \
add_1.js \
add_2.js \
add_3.js \
add_4.js \
add_5.js \
anonymous_add_1.js \
anonymous_add_2.js \
anonymous_inc_1.js \
anonymous_inc_2.js \
colors_1.js \
colors_2.js \
colors_3.js \
colors_4.js \
colors_5.js \
colors_6.js \
exc_1.js \
exc_2.js \
exc_3.js \
exc_4.js \
exc_5.js \
exists.js \
fibonacci_func_call_1.js \
fibonacci_func_call_2.js \
fibonacci_naive.js \
fibonacci_pattern_matching.js \
hello.js \
length.js \
map.js \
mutual_recursion.js \
pipe.js \
pow_curry_1.js \
pow_curry_2.js \
pow_operator_1.js \
pow_operator_2.js \
pow_operator_3.js \
pow_real_operator_1.js \
pow_real_operator_2.js \
pow_real_operator_3.js \
some_none.js \
some_none_2.js \
string_join.js \
sum_int.js \
sum_real.js \
tuples.js
all: $(programs)
clean:
rm -f *.lua
.PHONY: all clean
%.lua: %.sml
docker run -v work1:/work -w /work ghcr.io/minoki/lunarml:latest lunarml compile $<
%.js: %.sml
docker run -v work1:/work -w /work ghcr.io/minoki/lunarml:latest lunarml compile --webjs $<
Repositář s demonstračními příklady
Všechny výše popsané demonstrační příklady (a to jak zdrojové kódy v SML, tak i výsledek transpřekladu do jazyků Lua a JavaScript) byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/ml-examples/. V tabulce umístěné pod tímto odstavcem jsou uvedeny odkazy na tyto příklady:
Literatura
- ML for the Working Programmer
https://www.cl.cam.ac.uk/~lp15/MLbook/pub-details.html - Elements of ML Programming, 2nd Edition (ML97)
http://infolab.stanford.edu/~ullman/emlp.html - A tour of Standard ML
https://saityi.github.io/sml-tour/tour/welcome - The History of Standard ML
https://smlfamily.github.io/history/SML-history.pdf - The Standard ML Basis Library
https://smlfamily.github.io/Basis/ - Programming in Standard ML
http://www.cs.cmu.edu/~rwh/isml/book.pdf - Programming in Standard ML '97: A Tutorial Introduction
http://www.lfcs.inf.ed.ac.uk/reports/97/ECS-LFCS-97–364/ - Programming in Standard ML '97: An On-line Tutorial
https://homepages.inf.ed.ac.uk/stg/NOTES/ - The OCaml system release 4.13
https://ocaml.org/releases/4.13/htmlman/index.html - Real World OCaml: Functional programming for the masses
https://dev.realworldocaml.org/ - OCaml from the Very Beginning
http://ocaml-book.com/ - OCaml from the Very Beginning: More OCaml : Algorithms, Methods & Diversions
http://ocaml-book.com/more-ocaml-algorithms-methods-diversions/ - Unix system programming in OCaml
http://ocaml.github.io/ocamlunix/ - OCaml for Scientists
https://www.ffconsultancy.com/products/ocaml_for_scientists/index.html - Using, Understanding, and Unraveling The OCaml Language
https://caml.inria.fr/pub/docs/u3-ocaml/ - Developing Applications With objective Caml
https://caml.inria.fr/pub/docs/oreilly-book/index.html - Introduction to Objective Caml
http://courses.cms.caltech.edu/cs134/cs134b/book.pdf - How to Think Like a (Functional) Programmer
https://greenteapress.com/thinkocaml/index.html - Types and Programming Languages
https://i.warosu.org/data/sci/img/0163/64/1725651701869705.pdf - Purely Functional Data Structures (Chris Okasaki)
https://www.cs.cmu.edu/~rwh/students/okasaki.pdf
Předchozí články o jazyku ML/SML i o jazycích OCaml a F#
Jak již bylo napsáno v úvodu dnešního článku, na stránkách Roota jsme se již seznámili jak se základními koncepty jazyka ML/Standard ML, tak i s jazyky OCaml a F#, které z původního ML vychází:
- ML – funkcionální jazyk s revolučním typovým systémem
https://www.root.cz/clanky/ml-funkcionalni-jazyk-s-revolucnim-typovym-systemem/ - Funkce a typový systém programovacího jazyka ML
https://www.root.cz/clanky/funkce-a-typovy-system-programovaciho-jazyka-ml/ - Curryfikace (currying), výjimky a vlastní operátory v jazyku ML
https://www.root.cz/clanky/curryfikace-currying-vyjimky-a-vlastni-operatory-v-jazyku-ml/ - Funkcionální programovací jazyk F#
https://www.root.cz/clanky/funkcionalni-programovaci-jazyk-f/ - Programovací jazyk OCaml
https://www.root.cz/clanky/programovaci-jazyk-ocaml/ - Programovací jazyk F#: proměnné, funkce a datové typy
https://www.root.cz/clanky/programovaci-jazyk-f-promenne-funkce-a-datove-typy/ - Proměnné, funkce a datové typy v jazyku OCaml
https://www.root.cz/clanky/promenne-funkce-a-datove-typy-v-jazyku-ocaml/ - Rekurze a pattern matching v programovacím jazyku F#
https://www.root.cz/clanky/rekurze-a-pattern-matching-v-programovacim-jazyku-f/ - Práce se seznamy v jazyce F#
https://www.root.cz/clanky/prace-se-seznamy-v-jazyce-f/ - Programovací jazyk OCaml: rekurze, pattern matching a práce se seznamy
https://www.root.cz/clanky/programovaci-jazyk-ocaml-rekurze-pattern-matching-a-prace-se-seznamy/ - Datové typy Option, Result a Array v programovacím jazyku F#
https://www.root.cz/clanky/datove-typy-option-result-a-array-v-programovacim-jazyku-f/ - Datové typy Option, Result a Array v programovacím jazyku OCaml
https://www.root.cz/clanky/datove-typy-option-result-a-array-v-programovacim-jazyku-ocaml/ - Operátory v programovacím jazyku OCaml
https://www.root.cz/clanky/operatory-v-programovacim-jazyku-ocaml/ - Operátory v programovacím jazyku F#
https://www.root.cz/clanky/operatory-v-programovacim-jazyku-f/ - Definice uživatelských datových typů v jazyku F#
https://www.root.cz/clanky/definice-uzivatelskych-datovych-typu-v-jazyku-f/ - Definice uživatelských datových typů v jazyku OCaml
https://www.root.cz/clanky/definice-uzivatelskych-datovych-typu-v-jazyku-ocaml/ - Rekurzivní datové typy v jazyku OCaml
https://www.root.cz/clanky/rekurzivni-datove-typy-v-jazyku-ocaml/ - Řídicí konstrukce v programovacím jazyku OCaml
https://www.root.cz/clanky/ridici-konstrukce-v-programovacim-jazyku-ocaml/ - LunarML: až překvapivě kvalitní transpiler z jazyka Standard ML do jazyků Lua a JavaScript
https://www.root.cz/clanky/lunarml-az-prekvapive-kvalitni-transpiler-z-jazyka-standard-ml-do-jazyku-lua-a-javascript/ - LunarML: definice a volání různých variant funkcí
https://www.root.cz/clanky/lunarml-definice-a-volani-ruznych-variant-funkci/
Odkazy na Internetu
- LunarML documentation
https://lunarml.readthedocs.io/en/latest/index.html - Standard ML of New Jersey
https://www.smlnj.org/ - Programming Languages: Standard ML – 1 (a navazující videa)
https://www.youtube.com/watch?v=2sqjUWGGzTo - 6 Excellent Free Books to Learn Standard ML
https://www.linuxlinks.com/excellent-free-books-learn-standard-ml/ - SOSML: The Online Interpreter for Standard ML
https://sosml.org/ - ML (Computer program language)
https://www.barnesandnoble.com/b/books/other-programming-languages/ml-computer-program-language/_/N-29Z8q8Zvy7 - Strong Typing
https://perl.plover.com/yak/typing/notes.html - What to know before debating type systems
http://blogs.perl.org/users/ovid/2010/08/what-to-know-before-debating-type-systems.html - Types, and Why You Should Care (Youtube)
https://www.youtube.com/watch?v=0arFPIQatCU - DynamicTyping (Martin Fowler)
https://www.martinfowler.com/bliki/DynamicTyping.html - DomainSpecificLanguage (Martin Fowler)
https://www.martinfowler.com/bliki/DomainSpecificLanguage.html - Language Workbenches: The Killer-App for Domain Specific Languages?
https://www.martinfowler.com/articles/languageWorkbench.html - Effective ML (Youtube)
https://www.youtube.com/watch?v=-J8YyfrSwTk - Why OCaml (Youtube)
https://www.youtube.com/watch?v=v1CmGbOGb2I - CSE 341: Functions and patterns
https://courses.cs.washington.edu/courses/cse341/04wi/lectures/03-ml-functions.html - Comparing Objective Caml and Standard ML
http://adam.chlipala.net/mlcomp/ - What are the key differences between Standard ML and OCaml?
https://www.quora.com/What-are-the-key-differences-between-Standard-ML-and-OCaml?share=1 - Cheat Sheets (pro OCaml)
https://www.ocaml.org/docs/cheat_sheets.html - Syllabus (FAS CS51)
https://cs51.io/college/syllabus/ - Abstraction and Design In Computation
http://book.cs51.io/ - Learn X in Y minutes Where X=Standard ML
https://learnxinyminutes.com/docs/standard-ml/ - CSE307 Online – Summer 2018: Principles of Programing Languages course
https://www3.cs.stonybrook.edu/~pfodor/courses/summer/cse307.html - CSE307 Principles of Programming Languages course: SML part 1
https://www.youtube.com/watch?v=p1n0_PsM6hw - CSE 307 – Principles of Programming Languages – SML
https://www3.cs.stonybrook.edu/~pfodor/courses/summer/CSE307/L01_SML.pdf - SML, Some Basic Examples
https://cs.fit.edu/~ryan/sml/intro.html - History of programming languages
https://devskiller.com/history-of-programming-languages/ - History of programming languages (Wikipedia)
https://en.wikipedia.org/wiki/History_of_programming_languages - Jemný úvod do rozsáhlého světa jazyků LISP a Scheme
https://www.root.cz/clanky/jemny-uvod-do-rozsahleho-sveta-jazyku-lisp-a-scheme/ - The Evolution Of Programming Languages
https://www.i-programmer.info/news/98-languages/8809-the-evolution-of-programming-languages.html - Evoluce programovacích jazyků
https://ccrma.stanford.edu/courses/250a-fall-2005/docs/ComputerLanguagesChart.png - Poly/ML Homepage
https://polyml.org/ - PolyConf 16: A brief history of F# / Rachel Reese
https://www.youtube.com/watch?v=cbDjpi727aY - Programovací jazyk Clojure 18: základní techniky optimalizace aplikací
https://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/ - Moscow ML Language Overview
https://itu.dk/people/sestoft/mosml/mosmlref.pdf - ForLoops
http://mlton.org/ForLoops - Funkcionální dobrodružství v JavaScriptu
https://blog.kolman.cz/2015/12/funkcionalni-dobrodruzstvi-v-javascriptu.html - Recenze knihy Functional Thinking (Paradigm over syntax)
https://www.root.cz/clanky/recenze-knihy-functional-thinking-paradigm-over-syntax/ - Currying
https://sw-samuraj.cz/2011/02/currying/ - Používání funkcí v F#
https://docs.microsoft.com/cs-cz/dotnet/fsharp/tutorials/using-functions - Funkce vyššího řádu
http://naucte-se.haskell.cz/funkce-vyssiho-radu - Currying (Wikipedia)
https://en.wikipedia.org/wiki/Currying - Currying (Haskell wiki)
https://wiki.haskell.org/Currying - Haskell Curry
https://en.wikipedia.org/wiki/Haskell_Curry - Moses Schönfinkel
https://en.wikipedia.org/wiki/Moses_Sch%C3%B6nfinkel - Fibonacci sequence
https://en.wikipedia.org/wiki/Fibonacci_sequence - Moonscript: jazyk inspirovaný CoffeeScriptem určený pro ekosystém jazyka Lua
https://www.root.cz/clanky/moonscript-jazyk-inspirovany-coffeescriptem-urceny-pro-ekosystem-jazyka-lua/ - Moonscript: jazyk inspirovaný CoffeeScriptem určený pro ekosystém jazyka Lua (2)
https://www.root.cz/clanky/moonscript-jazyk-inspirovany-coffeescriptem-urceny-pro-ekosystem-jazyka-lua-2/ - Moonscript: jazyk inspirovaný CoffeeScriptem určený pro ekosystém jazyka Lua (dokončení)
https://www.root.cz/clanky/moonscript-jazyk-inspirovany-coffeescriptem-urceny-pro-ekosystem-jazyka-lua-dokonceni/ - Programovací jazyk Lua v roli skriptovacího jazyka pro WWW stránky
https://www.root.cz/clanky/programovaci-jazyk-lua-v-roli-skriptovaciho-jazyka-pro-www-stranky/ - Transcrypt: technologie umožňující použití Pythonu v prohlížeči
https://www.root.cz/clanky/transcrypt-technologie-umoznujici-pouziti-pythonu-v-prohlizeci/ - GopherJS: transpřekladač z jazyka Go do JavaScriptu
https://www.root.cz/clanky/gopherjs-transprekladac-z-jazyka-go-do-javascriptu/ - Rychlost CPythonu 3.11 a 3.12 v porovnání s JIT a AOT překladači
https://www.root.cz/clanky/rychlost-cpythonu-3–11-a-3–12-v-porovnani-s-jit-a-aot-prekladaci-pythonu/ - Seriál F# a OCaml
https://www.root.cz/serialy/f-a-ocaml/ - Haskell or Standard ML for beginners?
https://stackoverflow.com/questions/810409/haskell-or-standard-ml-for-beginners#813646 - Awesome transpilers
https://github.com/milahu/awesome-transpilers - Languages that compile to JS (from CoffeeScript wiki)
https://gist.github.com/matthiasak/c3c9c40d0f98ca91def1
