Obsah
1. Transpiling – překlad zdrojových kódů Pythonu do dalších programovacích jazyků
3. Syntaktické a sémantické rozdíly mezi podporovanými jazyky
5. Instalace transpřekladače py2many
7. Transpřeklad programu typu „Hello, world!“
8. Program obsahující pouze definici jediné funkce
9. Definice funkce s voláním této funkce
10. Výpočet faktoriálu realizovaný funkcí bez typových informací
11. Ackermannova funkce definovaná bez použití datových typů
12. Ackermannova funkce definovaná s využitím typových informací
13. Funkce s implementací algoritmu bublinkového řazení
14. Datové typy v programovacím jazyce Python i v jazycích, do nichž je transpilován
15. Základní operace se seznamy: varianta s datovými typy i bez typů
16. Výsledek transpilace kódu, v němž nebyly použity typové informace
17. Výsledek transpilace kódu, v němž byly použity typové informace
18. Tabulka se srovnáním úspěchu transpřekladu jednotlivých příkladů
19. Repositář s demonstračními příklady
1. Transpiling – překlad zdrojových kódů Pythonu do dalších programovacích jazyků
Na stránkách Roota jsme se již několikrát setkali s projekty, ve kterých byla realizována nějaká forma transpilace (nebo transkompilace). Takže si jen ve stručnosti připomeňme, že takzvaný transcompiler, popř. zkráceně transpiler slouží k převodu zdrojového kódu z jednoho programovacího jazyka do jazyka jiného, na rozdíl od běžných překladačů (compiler), které provádí překlad buď do nativního kódu nebo do bajtkódu (bytecode). Překladače do nativního kódu asi netřeba představovat – patří sem totiž klasické překladače céčka, Rustu, Go atd. (což ve skutečnosti je nepatrně složitější v případě LLVM, to je však téma na samostatný článek). A ani překladače do bajtkódu nejsou ve světě moderní informatiky neznámé (spíš naopak), protože z používaných prostředků sem spadá především Java, Python (soubory .pyc) a překladače podporující WebAssemly (WASM).
Aktivně používaných transpilerů taktéž v současnosti existuje celá řada, přičemž některé z nich mají za úkol „snížení úrovně abstrakce“, tedy například transpilaci z Pythonu či jiného vysokoúrovňového jazyka do programovacího jazyka C s následným překladem výsledného kódu s využitím nějakého kvalitního a optimalizujícího překladače céčka (příkladem může být projekt Cython, který již taktéž známe). Další transpilery vznikly pro podporu dalších programovacích jazyků v těch ekosystémech, které jsou (z různých důvodů) navázány na jediný programovací jazyk. Dnes je naprosto nejtypičtějším příkladem takového ekosystému svět webových prohlížečů, který se ustálil na podpoře JavaScriptu a projekty psané v jiných programovacích jazycích je tak nutné buď transpilovat právě do JavaScriptu (popř. do jeho podmnožiny – sem spadá projekt ams.js) nebo mít v JavaScriptu či WASM naprogramovaný interpret daného jazyka se všemi z toho plynoucími důsledky (rychlost, objem stahovaných dat apod.).
2. Projekt py2many
V dnešním článku se seznámíme s projektem nazvaným py2many, který si klade zajímavý cíl. Tímto cílem je umožnění transpilace zdrojových kódů z jazyka Python do několika dalších programovacích jazyků, tedy nikoli pouze do jediného cílového jazyka. V současnosti v tomto projektu existuje podpora pro několik cílových jazyků. Zejména zde nalezneme Rust a C++, na které se soustřeďuje další vývoj (zejména na Rust, to ostatně uvidíme i v demonstračních příkladech), ovšem nalezneme zde i další cílové jazyky, a to konkrétně programovací jazyky Go, Julia, Kotlin, Nim, Dart a taktéž jazyk V.
Ovšem jak uvidíme v dalším textu, podpora nějakého cílového jazyka neznamená, že by bylo možné vzít složitý projekt psaný v Pythonu, transpřeložit ho do (řekněme) zdrojových kódů psaných v Go a poté ho bez problémů přeložit překladačem Go a spustit. Na cestě stojí několik více či méně závažných překážek, ale i přesto může být tento projekt zajímavý a to především proto, že programovací jazyk Python (resp. jeho „osekaná“ varianta) je dnes často používán v učebnicích, odborných článcích atd. pro zápis popisovaných algoritmů; Python zde tedy vystupuje v roli abstraktního jazyka vhodného pro tyto účely, tedy pro zápis pseudokódu (ale opět – jedná se typicky o Python „osekaný“ o některé nové vlastnosti). A díky py2many je možné takový algoritmus ve více či méně korektní podobě získat i pro další konkrétní programovací jazyk.
3. Syntaktické a sémantické rozdíly mezi podporovanými jazyky
Ještě předtím, než si ukážeme demonstrační příklady, na nichž budou patrné vlastnosti (a především nedostatky) projektu py2many, je dobré si uvědomit, jak odlišné jsou programovací jazyky, do nichž je programový kód napsaný v Pythonu transpilován. Liší se pochopitelně syntaxe, klíčová slova, pravidla pro psaní závorek a středníků atd. To však není vlastně příliš důležité, protože mnohem větší rozdíly nalezneme v sémantice použitých programovacích jazyků.
Začněme Pythonem, což je programovací jazyk, v němž nalezneme podporu pro funkce jako plnohodnotných datových typů, je možné používat uzávěry, funkce mohou mít proměnný počet parametrů, funkce mohou akceptovat keyword parametry, Python podporuje objektově orientované programování založené na třídách, lze vytvářet konstrukce pro zachytávání výjimek, podporován je pattern matching, aplikace dekorátorů a v neposlední řadě i asynchronní programování realizované mj. i klíčovými slovy async a await. A navíc Python podporuje i zdánlivou maličkost – přímý zápis příkazů, které nemusí být umístěny v žádné funkci nebo v metodě.
4. Python vs. ostatní jazyky
Naproti tomu u prakticky všech programovacích jazyků, do nichž nástroj py2many dokáže Python transpilovat, některou z těchto vlastností nenalezneme (resp. většinou jich nenalezneme větší množství). Z těchto důvodů si v praktické části dnešního článku postupně ukážeme, jak dobře, špatně, či dokonce vůbec jsou některé jazykové konstrukce Pythonu překládány do dalších jazyků. Trošku sice předběhneme výsledky, ke kterým postupně dospějeme, ale ukazuje se, že nejlépe je podporován programovací jazyk Rust. To může vypadat zvláštně, protože Rust nepodporuje například klasické objektově orientované programování realizované třídami nebo programovou konstrukci typu try-catch-finally. Pro další konstrukce je korektní (trans)překlad i do programovacího jazyka Julia, ovšem například C++ či Go dopadají v tomto porovnání poněkud hůře (a to se nesnažím být příliš kritický).
5. Instalace transpřekladače py2many
Samotný nástroj py2many může být nainstalován a spouštěn i bez toho, aby byly k dispozici nainstalované cílové programovací jazyky, ovšem v některých případech nebudou určité operace provedeny (například naformátování výsledného kódu). Nejprve si tedy py2many nainstalujeme, což je ve skutečnosti velmi snadné, protože má jen minimální závislosti a lze ho nainstalovat standardním nástrojem pip z ekosystému programovacího jazyka Python:
$ pip3 install --user py2many
Průběh instalace naznačuje, že závislosti na dalších balíčcích Pythonu jsou jen minimální:
Collecting py2many Downloading py2many-0.4.tar.gz (231 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 231.8/231.8 kB 2.1 MB/s eta 0:00:00 Preparing metadata (setup.py) ... done Collecting toposort Downloading toposort-1.10-py3-none-any.whl (8.5 kB) Building wheels for collected packages: py2many Building wheel for py2many (setup.py) ... done Created wheel for py2many: filename=py2many-0.4-py3-none-any.whl size=135227 sha256=fdee7d3e80a45ee303084aa6db3aa5a7568c1a18eb4e6f071b1fbd502ce751ec Stored in directory: /home/ptisnovs/.cache/pip/wheels/e0/90/16/93e6e6033164b1c0391430e30ce0ba0ef247c24d3a93c25d36 Successfully built py2many Installing collected packages: toposort, py2many Successfully installed py2many-0.4 toposort-1.10
6. Podpůrné nástroje pro naformátování výsledných zdrojových kódů podle zvyklostí cílového programovacího jazyka
Samotný transpřekladač py2many sice dokáže generovat zdrojové kódy pro zvolený cílový jazyk, ovšem naformátování těchto kódů nemusí odpovídat zvyklostem, které jsou v cílovém ekosystému dodržovány. Týká se to především ekosystémů okolo programovacích jazyků Rust a Go, v nichž se poměrně striktně dodržuje určitá štábní kultura. Aby výsledné zdrojové kódy do těchto ekosystémů dobře zapadaly, pokouší se nástroj py2many volat pomocné nástroje pro jejich výsledné naformátování. Konkrétně se v případě jazyka Rust jedná o nástroj rustfmt (lze nainstalovat samostatně) a v případě jazyka Go pak o nástroj go fmt (ten je součástí instalace samotného Go a není ho nutné instalovat samostatně).
Jen pro úplnost si ukažme instalaci nástroje rustfmt na operačním systému s podporou balíčků RPM. Je to snadné:
$ sudo dnf install rustfmt
Průběh instalace:
Last metadata expiration check: 3:36:59 ago on Fri 14 Jun 2024 05:53:53 AM CEST. Dependencies resolved. =============================================================================================================================================================== Package Architecture Version Repository Size =============================================================================================================================================================== Installing: rustfmt x86_64 1.78.0-1.fc38 updates 1.9 M Installing dependencies: cargo x86_64 1.78.0-1.fc38 updates 6.5 M Transaction Summary =============================================================================================================================================================== Install 2 Packages Total download size: 8.4 M Installed size: 26 M Is this ok [y/N]: y Downloading Packages: (1/2): rustfmt-1.78.0-1.fc38.x86_64.rpm 1.1 MB/s | 1.9 MB 00:01 (2/2): cargo-1.78.0-1.fc38.x86_64.rpm 3.7 MB/s | 6.5 MB 00:01 --------------------------------------------------------------------------------------------------------------------------------------------------------------- Total 3.8 MB/s | 8.4 MB 00:02 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : cargo-1.78.0-1.fc38.x86_64 1/2 Installing : rustfmt-1.78.0-1.fc38.x86_64 2/2 Running scriptlet: rustfmt-1.78.0-1.fc38.x86_64 2/2 Verifying : cargo-1.78.0-1.fc38.x86_64 1/2 Verifying : rustfmt-1.78.0-1.fc38.x86_64 2/2 Installed: cargo-1.78.0-1.fc38.x86_64 rustfmt-1.78.0-1.fc38.x86_64
7. Transpřeklad programu typu „Hello, world!“
Začneme zdánlivě triviálním příkladem, a to konkrétně skriptem, který po svém spuštění vypíše obligátní pozdravení celému světu. V Pythonu je takový skript skutečně jednoduchý:
print("Hello, world!")
Už u takto jednoduchého programu však narazíme na to, že zdaleka ne všechny programovací jazyky (zejména ne ty klasické kompilované) podporují jednotlivé příkazy, které nejsou vloženy do funkce. Proto také výsledky transpřekladu dopadnou nevalně, protože mnohé výsledné zdrojové kódy nebudou přeložitelné nebo budou nespustitelné (s výjimkou skriptu transpřeloženého do jazyka Julia):
hello.cpp
#include <iostream> // NOLINT(build/include_order) std::cout << std::string{"Hello, world!"}; std::cout << std::endl;
hello.go
package main import ( "fmt") fmt.Printf("%v\n","Hello, world!");
hello.jl
println(join(["Hello, world!"], " "));
hello.rs
//! ```cargo //! [package] //! edition = "2018" //! [dependencies] //! //! ``` #![allow(clippy::collapsible_else_if)] #![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17 #![allow(clippy::map_identity)] #![allow(clippy::needless_return)] #![allow(clippy::print_literal)] #![allow(clippy::ptr_arg)] #![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266 #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::useless_vec)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_parens)] println!("{}","Hello, world!");
8. Program obsahující pouze definici jediné funkce
Ve druhém demonstrační příkladu si otestujeme transpřeklad skriptu s definicí funkce bez parametrů i bez návratových hodnot. Tato funkce bude jen definována, ovšem nebude volána. Nejprve je vypsán původní zdrojový kód v Pythonu a poté zdrojové kódy získané po transpilaci do dalších programovacích jazyků:
hello_func2.py
def hello(): print("Hello, world!")
hello_func2.cpp
#include <iostream> // NOLINT(build/include_order) inline void hello() { std::cout << std::string{"Hello, world!"}; std::cout << std::endl; }
hello_func2.go
package main import ( "fmt" ) func Hello() { fmt.Printf("%v\n", "Hello, world!") }
hello_func2.jl
function hello() println(join(["Hello, world!"], " ")); end
hello_func2.rs
//! ```cargo //! [package] //! edition = "2018" //! [dependencies] //! //! ``` #![allow(clippy::collapsible_else_if)] #![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17 #![allow(clippy::map_identity)] #![allow(clippy::needless_return)] #![allow(clippy::print_literal)] #![allow(clippy::ptr_arg)] #![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266 #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::useless_vec)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_parens)] pub fn hello() { println!("{}", "Hello, world!"); }
Výsledky jsou v tomto případě korektní pro všechny jazyky, i když ne vždy odpovídají tomu, co by napsal člověk.
9. Definice funkce s voláním této funkce
Ve třetím demonstračním příkladu rozšíříme předchozí skript o přímé volání právě definované funkce. Opět zde narazíme na problémy v těch programovacích jazycích, v nichž není možné zapisovat příkazy mimo funkce či metody, což se v tomto případě týká všech jazyků kromě originálního Pythonu a Julie:
hello_func.py
def hello(): print("Hello, world!") hello()
hello_func.cpp
#include <iostream> // NOLINT(build/include_order) inline void hello() { std::cout << std::string{"Hello, world!"}; std::cout << std::endl; } hello();
hello_func.go
package main import ( "fmt") func Hello() { fmt.Printf("%v\n","Hello, world!");} Hello();
hello_func.jl
function hello() println(join(["Hello, world!"], " ")); end hello();
hello_func.rs
//! ```cargo //! [package] //! edition = "2018" //! [dependencies] //! //! ``` #![allow(clippy::collapsible_else_if)] #![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17 #![allow(clippy::map_identity)] #![allow(clippy::needless_return)] #![allow(clippy::print_literal)] #![allow(clippy::ptr_arg)] #![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266 #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::useless_vec)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_parens)] pub fn hello() { println!("{}","Hello, world!"); } hello();
10. Výpočet faktoriálu realizovaný funkcí bez typových informací
Následuje příklad se složitější funkcí, konkrétně s rekurzivním výpočtem faktoriálu. Funkce je v programovacím jazyku Python zapsána bez použití typových informací (type hints). Výsledky transpilace nejsou zcela špatné (někdy je lze dokonce i přeložit a spustit :-), ovšem je evidentní, že chybějící typové informace nástroj py2many poměrně hodně zmátly:
factorial.py
def factorial(n): """Rekurzivní výpočet faktoriálu.""" if n < 0: return None if n == 0: return 1 result = n * factorial(n - 1) return result
factorial.cpp
template <typename T0> int factorial(T0 n) { if (n < 0) { return NULL; } if (n == 0) { return 1; } int result = n * (factorial(n - 1)); return result; }
factorial.go
package main func Factorial[T0 any](n T0 any) int { if(n < 0) { return nil } if(n == 0) { return 1 } var result int = (n*Factorial((n - 1))) return result}
factorial.jl
function factorial{T0}(n::T0)::Int64 if n < 0 return nothing end if n == 0 return 1 end result = n*factorial(convert(, n - 1)) return result end
factorial.rs
//! ```cargo //! [package] //! edition = "2018" //! [dependencies] //! //! ``` #![allow(clippy::collapsible_else_if)] #![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17 #![allow(clippy::map_identity)] #![allow(clippy::needless_return)] #![allow(clippy::print_literal)] #![allow(clippy::ptr_arg)] #![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266 #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::useless_vec)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_parens)] pub fn factorial<T0>(n: T0) -> i32 { if (n as i32) < 0 { return None; } if (n as i32) == 0 { return 1; } let result: i32 = ((n as i32) * factorial(((n as i32) - 1))); return result; }
11. Ackermannova funkce definovaná bez použití datových typů
I další demonstrační příklad, který si v dnešním článku ukážeme, obsahuje definice funkcí. Konkrétně se jedná o známou Ackermannovu funkci, kterou v Pythonu pochopitelně můžeme definovat bez toho, abychom uvedli typové informace o jejích parametrech i o návratové hodnotě. V navazující kapitole si pak ukážeme tutéž funkci, ovšem s uvedením typových informací.
ackermann_untyped.py
# Výpočet Ackermannovy funkce, založeno na konstrukci if def A(m, n): """Ackermannova funkce.""" if m == 0: return n + 1 if n == 0: return A(m - 1, 1) return A(m - 1, A(m, n - 1)) def check_a(): """Korektnosti výpočtu Ackermannovy funkce.""" for m in range(4): for n in range(5): print(m, n, A(m, n))
ackermann_untyped.cpp
#include <cppitertools/range.hpp> // NOLINT(build/include_order) #include <iostream> // NOLINT(build/include_order) template <typename T0, typename T1> int A(T0 m, T1 n) { if (m == 0) { return n + 1; } if (n == 0) { return A(m - 1, 1); } return A(m - 1, A(m, n - 1)); } inline void check_a() { for (auto m : iter::range(4)) { for (auto n : iter::range(5)) { std::cout << m; std::cout << " "; std::cout << n; std::cout << " "; std::cout << A(m, n); std::cout << std::endl; } } }
ackermann_untyped.go
package main import ( iter "github.com/hgfischer/go-iter" "fmt") func A[T0 any, T1 any](m T0 any, n T1 any) int { if(m == 0) { return (n + 1) } if(n == 0) { return A((m - 1), 1) } return A((m - 1), A(m, (n - 1)))} func CheckA() { for _, m := range iter.NewIntSeq(iter.Start(0), iter.Stop(4)).All() { for _, n := range iter.NewIntSeq(iter.Start(0), iter.Stop(5)).All() { fmt.Printf("%v %v %v\n",m, n, A(m, n)); } }}
ackermann_untyped.jl
function A{T0, T1}(m::T0, n::T1)::Int64 if m == 0 return n + 1 end if n == 0 return A(convert(, m - 1), convert(, 1)) end return A(convert(, m - 1), convert(, A(m, convert(, n - 1)))) end function check_a() for m in 0:4 - 1 for n in 0:5 - 1 println(join([m, n, A(m, n)], " ")); end end end
ackermann_untyped.rs
//! ```cargo //! [package] //! edition = "2018" //! [dependencies] //! //! ``` #![allow(clippy::collapsible_else_if)] #![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17 #![allow(clippy::map_identity)] #![allow(clippy::needless_return)] #![allow(clippy::print_literal)] #![allow(clippy::ptr_arg)] #![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266 #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::useless_vec)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_parens)] pub fn A<T0, T1>(m: T0, n: T1) -> i32 { if (m as i32) == 0 { return ((n as i32) + 1); } if (n as i32) == 0 { return A(((m as i32) - 1), 1); } return A(((m as i32) - 1), A(m, ((n as i32) - 1))); } pub fn check_a() { for m in (0..4) { for n in (0..5) { println!("{} {} {}", m, n, A(m, n)); } } }
12. Ackermannova funkce definovaná s využitím typových informací
Pokusme se tedy tranpřekladači pomoci a upravit Ackermannovu funkci takovým způsobem, aby byly již v čase (trans)překladu známé jak typy parametrů této funkce, tak i typ návratové hodnoty:
ackermann_typed.py
# Výpočet Ackermannovy funkce, založeno na konstrukci if def A(m: int, n: int) -> int: """Ackermannova funkce.""" if m == 0: return n + 1 if n == 0: return A(m - 1, 1) return A(m - 1, A(m, n - 1)) def check_a() -> None: """Korektnosti výpočtu Ackermannovy funkce.""" for m in range(4): for n in range(5): print(m, n, A(m, n))
ackermann_typed.cpp
#include <cppitertools/range.hpp> // NOLINT(build/include_order) #include <iostream> // NOLINT(build/include_order) inline int A(int m, int n) { if (m == 0) { return n + 1; } if (n == 0) { return A(m - 1, 1); } return A(m - 1, A(m, n - 1)); } inline auto check_a() { for (auto m : iter::range(4)) { for (auto n : iter::range(5)) { std::cout << m; std::cout << " "; std::cout << n; std::cout << " "; std::cout << A(m, n); std::cout << std::endl; } } }
ackermann_typed.go
package main import ( "fmt" iter "github.com/hgfischer/go-iter" ) func A(m int, n int) int { if m == 0 { return (n + 1) } if n == 0 { return A((m - 1), 1) } return A((m - 1), A(m, (n-1))) } func CheckA() { for _, m := range iter.NewIntSeq(iter.Start(0), iter.Stop(4)).All() { for _, n := range iter.NewIntSeq(iter.Start(0), iter.Stop(5)).All() { fmt.Printf("%v %v %v\n", m, n, A(m, n)) } } }
ackermann_typed.jl
function A(m::Int64, n::Int64)::Int64 if m == 0 return n + 1 end if n == 0 return A(m - 1, 1) end return A(m - 1, A(m, n - 1)) end function check_a():: for m in 0:4 - 1 for n in 0:5 - 1 println(join([m, n, A(m, n)], " ")); end end end
ackermann_typed.rs
//! ```cargo //! [package] //! edition = "2018" //! [dependencies] //! //! ``` #![allow(clippy::collapsible_else_if)] #![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17 #![allow(clippy::map_identity)] #![allow(clippy::needless_return)] #![allow(clippy::print_literal)] #![allow(clippy::ptr_arg)] #![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266 #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::useless_vec)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_parens)] pub fn A(m: i32, n: i32) -> i32 { if m == 0 { return (n + 1); } if n == 0 { return A((m - 1), 1); } return A((m - 1), A(m, (n - 1))); } pub fn check_a() { for m in (0..4) { for n in (0..5) { println!("{} {} {}", m, n, A(m, n)); } } }
13. Funkce s implementací algoritmu bublinkového řazení
Další zdrojový soubor naprogramovaný v Pythonu, který si dnes ukážeme, obsahuje funkci nazvanou bubble_sort s realizací algoritmu bublinkového řazení. V této funkci se tedy používají operace se seznamem, s nímž se vlastně pracuje takovým způsobem, jakoby se jednalo o pole s neměnnou délkou (počtem prvků). Samotný algoritmus je realizován dvojicí vnořených smyček a podmínkou. Povšimněte si taktéž „dvojitého přiřazení“ do prvků pole s indexy j a j + 1 (tím je realizována operace prohození prvků bez použití třetí pomocné proměnné):
bubble.py
def bubble_sort(size): a = [random.randrange(0, 10000) for i in range(size)] for i in range(size - 1, 0, -1): for j in range(0, i): if a[j] > a[j + 1]: a[j], a[j + 1] = a[j + 1], a[j] print(a)
bubble.cpp
FAILED
bubble.go
package main import ( "fmt" iter "github.com/hgfischer/go-iter") func BubbleSort[T0 any](size T0 any) { a := iter.NewIntSeq(iter.Start(0), iter.Stop(size)).All().iter().map(|i| randrange(random, 0, 10000)).collect::<Vec<_>>() for _, i := range iter.NewIntSeq(iter.Start((size - 1)), iter.Stop(0), iter.Step(-1)).All() { for _, j := range iter.NewIntSeq(iter.Start(0), iter.Stop(i)).All() { if(a[j] > a[(j + 1)]) { { var __tmp1, __tmp2 = a[(j + 1)], a[j] a[j] = __tmp1 a[(j + 1)] = __tmp2 } } } } fmt.Printf("%v\n",a);}
bubble.jl
function bubble_sort{T0}(size::T0) a = [randrange(random, 0, 10000) for i in 0:size - 1] for i in 0 - 1:-1:size - 1 for j in 0:i - 1 if a[j] > a[j + 1] a[j], a[j + 1] = (a[j + 1], a[j]) end end end println(join([a], " ")); end
bubble.rs
//! ```cargo //! [package] //! edition = "2018" //! [dependencies] //! //! ``` #![allow(clippy::collapsible_else_if)] #![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17 #![allow(clippy::map_identity)] #![allow(clippy::needless_return)] #![allow(clippy::print_literal)] #![allow(clippy::ptr_arg)] #![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266 #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::useless_vec)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_parens)] pub fn bubble_sort<T0>(size: T0) { let mut a = (0..size) .map(|i| random.randrange(0, 10000)) .collect::<Vec<_>>(); for i in (((size as i32) - 1)..0).step_by(-1) { for j in (0..i) { if a[j] > a[((j as i32) + 1)] { ({ let (__tmp1, __tmp2) = (a[((j as i32) + 1)], a[j]); a[j] = __tmp1; a[((j as i32) + 1)] = __tmp2; }); } } } println!("{}", a); }
14. Datové typy v programovacím jazyce Python i v jazycích, do nichž je transpilován
V reálných programech vytvořených v jazyku Python se prakticky vždy setkáme s využitím nějakých typů sekvencí, popř. kontejnerů, kam spadají datové typy seznam (list), n-tice (tuple), množiny (set) a slovníky (dictionary). Ostatně s vytvořením a inicializací seznamu jsme se již setkali v předchozí kapitole při konstrukci seznamu s náhodnými hodnotami. Otázkou ovšem zůstává, jakým způsobem jsou operace s těmito datovými typy transpilovány, protože zde opět narazíme především na sémantické rozdíly, mnohdy zcela odlišné typové systémy apod. Dnes si ukážeme dva základní příklady z této oblasti a v navazujícím článku na toto téma pochopitelně ještě navážeme, protože je pro reálné použití py2many velmi důležité.
15. Základní operace se seznamy: varianta s datovými typy i bez typů
V dalších dvou kapitolách budou ukázány výsledky transpilace dvou skriptů, které obsahují funkci pro konstrukci a naplnění seznamu sekvencí celočíselných hodnot. První z těchto skriptů neobsahuje typové informace:
list_untyped.py
def fill_in_list(size): l = [] for i in range(size): l.append(i+1) return l
Druhý skript obsahuje totožný kód, nyní však s typovými informacemi:
list_typed.py
def fill_in_list(size: int) -> list[int]: l = [] for i in range(size): l.append(i+1) return l
16. Výsledek transpilace kódu, v němž nebyly použity typové informace
Výsledky transpilace skriptu, v němž se konstruuje seznam ve skriptu bez typových informací, nedopadne ani v jednom případě dobře:
list_untyped.cpp
#include <cppitertools/range.hpp> // NOLINT(build/include_order) #include <vector> // NOLINT(build/include_order) template <typename T0> List fill_in_list(T0 size) { std::vector< decltype(std::declval<typename decltype(range(size))::value_type>() + 1)> l = {}; for (auto i : iter::range(size)) { l.push_back(i + 1); } return l; }
list_untyped.go
package main import ( iter "github.com/hgfischer/go-iter") func FillInList[T0 any](size T0 any) List { var l List = []None{} for _, i := range iter.NewIntSeq(iter.Start(0), iter.Stop(size)).All() { l = append(l, (i + 1)); } return l}
list_untyped.jl
function fill_in_list{T0}(size::T0)::List l = [] for i in 0:size - 1 push!(l, i + 1); end return l end
list_untyped.rs
//! ```cargo //! [package] //! edition = "2018" //! [dependencies] //! //! ``` #![allow(clippy::collapsible_else_if)] #![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17 #![allow(clippy::map_identity)] #![allow(clippy::needless_return)] #![allow(clippy::print_literal)] #![allow(clippy::ptr_arg)] #![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266 #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::useless_vec)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_parens)] use std::collections; pub fn fill_in_list<T0>(size: T0) -> List { let mut l: List = vec![]; for i in (0..size) { l.push(((i as i32) + 1)); } return l; }
17. Výsledek transpilace kódu, v němž byly použity typové informace
list_typed.cpp
#include <cppitertools/range.hpp> // NOLINT(build/include_order) #include <vector> // NOLINT(build/include_order) inline auto fill_in_list(int size) { std::vector< decltype(std::declval<typename decltype(range(size))::value_type>() + 1)> l = {}; for (auto i : iter::range(size)) { l.push_back(i + 1); } return l; }
list_typed.go
package main import ( iter "github.com/hgfischer/go-iter" ) func FillInList(size int) None { var l List = []None{} for _, i := range iter.NewIntSeq(iter.Start(0), iter.Stop(size)).All() { l = append(l, (i + 1)) } return None(l) }
list_typed.jl
function fill_in_list(size::Int64):: l = [] for i in 0:size - 1 push!(l, i + 1); end return l end
list_typed.rs
//! ```cargo //! [package] //! edition = "2018" //! [dependencies] //! //! ``` #![allow(clippy::collapsible_else_if)] #![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17 #![allow(clippy::map_identity)] #![allow(clippy::needless_return)] #![allow(clippy::print_literal)] #![allow(clippy::ptr_arg)] #![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266 #![allow(clippy::unnecessary_cast)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::useless_vec)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(unused_imports)] #![allow(unused_mut)] #![allow(unused_parens)] use std::collections; pub fn fill_in_list(size: i32) { let mut l: List = vec![]; for i in (0..size) { l.push(((i as i32) + 1)); } return l as _; }
18. Tabulka se srovnáním úspěchu transpřekladu jednotlivých příkladů
Výsledky projektu py2many jsou prozatím dosti špatné, jak je to ostatně patrné z následující tabulky. Hodnoty × a ✓ mají zřejmý význam, hodnota 1/2 pak představuje řešení, které sice není přímo přeložitelné a spustitelné, ale není zcela špatné – malým ručním zásahem je možné transpilované zdrojové kódy opravit:
Program | Testuje se | C++ | Go | Julia | Rust |
---|---|---|---|---|---|
hello.py | jednotlivý příkaz mimo funkci | × | × | ✓ | × |
hello_func2.py | definice samostatné funkce | ✓ | ✓ | ✓ | ✓ |
hello_func.py | definice funkce s jejím voláním | × | × | ✓ | × |
factorial.py | definice funkce bez typových informací | × | × | × | × |
ackermann_untyped.py | Ackermannova funkce definovaná bez datových typů | ✓ | × | 1/2 | ? |
ackermann_typed.py | Ackermannova funkce definovaná s datovými typy | ✓ | ✓ | 1/2 | ✓ |
bubble.py | realizace algoritmu bublinkového řazení | × | × | 1/2 | 1/2 |
list_untyped.py | konstrukce a naplnění seznamu; bez typů | 1/2 | × | 1/2 | 1/2 |
list_typed.py | konstrukce a naplnění seznamu; s typy | 1/2 | 1/2 | 1/2 | 1/2 |
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python a pro nástroj py2many byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs.
20. Odkazy na Internetu
- py2many na GitHubu
https://github.com/py2many/py2many - py2many na PyPi
https://pypi.org/project/py2many/ - Awesome Transpilers
https://github.com/milahu/awesome-transpilers - Pseudocode (Wikipedia)
https://en.wikipedia.org/wiki/Pseudocode - Pseudokód (Wikipedia)
https://cs.wikipedia.org/wiki/Pseudok%C3%B3d - Cython (home page)
http://cython.org/ - Cython (wiki)
https://github.com/cython/cython/wiki - Cython (Wikipedia)
https://en.wikipedia.org/wiki/Cython - Cython (GitHub)
https://github.com/cython/cython - Seriál Programovací jazyk Julia
https://www.root.cz/serialy/programovaci-jazyk-julia/ - Seriál programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Ackermann function
https://en.wikipedia.org/wiki/Ackermann_function - EmbeddingCython
https://github.com/cython/cython/wiki/EmbeddingCython - The Basics of Cython
http://docs.cython.org/en/latest/src/tutorial/cython_tutorial.html - Compiling to WebAssembly: It’s Happening!
https://hacks.mozilla.org/2015/12/compiling-to-webassembly-its-happening/ - WebAssembly
https://webassembly.org/ - A quick guide about Python implementations
https://blog.rmotr.com/a-quick-guide-about-python-implementations-aa224109f321