DEV Community

Betelgeuse
Betelgeuse

Posted on • Updated on

Typové parametry v moderních jazycích

Všechny moderní překládané jazyky podporují generické programování. Následuje stručný přehled jejich používání v C++, Rustu, Go a Swiftu.

Proč typové parametry?

Typové parametry umožňují úspornější kód. Například u kolekcí můžeme mít class ListOfInts a vedle toho class ListOfStrings a případně další třídy pro jiné typy, ale implementace seznamu bude stejná, bez ohledu na typ prvků. Proto se používají typové parametry. Stačí pak mít jeden typový operátor class List<T>, kde T je typový parametr. Místo new ListOfInts pak bude new List<Int> apod.

Dalším příkladem jsou funkce. Typový operátor Func<T,U> reprezentuje unární funkci s argumentem typu T a návratovou hodnotou typu U.

Negenerické typy se nazývají konkrétní, například Int, List<Int> nebo Func<Int,String> jsou konkrétní.

C++

V C++ jsou typové parametry implementované pomocí šablon. Ty nejsou v jazyce ničím novým, ale postupně se zlepšovala inference typových parametrů, takže v C++17 (a čerstvém C++20) se už téměř nemusejí explicitně uvádět. Deklarace typového operátoru:

template<typename T> class A { ... };

V C++ se aplikace typových operátorů na typy řeší v době překladu, pro každou proto překladač vygeneruje negenerický kód. To přináší vyšší výkon při běhu (na rozdíl od Javy nebo C#, kde je genericita zachována i na úrovni VM, což vede k určité režii při běhu).

Za zmínku stojí, že C++ podporuje i typové operátory tzv. vyššího druhu, tj. jejich parametry můžou být nejen konkrétní typy, ale i jiné typové operátory. To jinak umí jen funkcionální jazyky (v Rustu a Swiftu se o jejich přidání uvažuje).

Rust

Rust řeší typové parametry stejně jako C++ v době překladu, na rozdíl od C++ je ale možné zúžit množinu typů dosaditelných za parametr. Například

struct Sorter<T:Comparable> { ... }

deklaruje typový operátor, jehož typový parametr musí implementovat Comparable, což je rozhraní zajišťující existenci metody pro porovnání.

Go

Go má typový systém velmi podobný Rustu. Je v něm možné stejně jako v Rustu zúžit přípustné typy pro typové parametry. Pokud lze použít libovolný typ, deklarace vypadá takto:

type List[T any] struct { ... }

Místo any lze uvést libovolné rozhraní, tedy například

type Sorter[T comparable] { ... }

Go navíc umožňuje určit výčtem, které typy jsou přípustné. Do tohoto výčtu lze zahrnout i typové parametry, například

type Explosive[T any] interface {
Explode()
type *T
}

Toto je rozhraní, které je samo typovým operátorem a kromě existence metody Explode zajišťuje, že typ implementující Explosive je ukazatelem na T. To je, spolu s poměrně propracovanou inferencí typových parametrů, značně mocný nástroj umožňující stručný a flexibilní kód (například funkci se signaturou func SomeFunc[T any, PT Explosive[T]](…) {…}).

Swift

Swift zachází s typovými parametry stejně jako Rust:

class Sorter<T:Comparable> { ... }

Je tedy možné specifikovat rozhraní (kterému se zde říká protokol a odpovídá přímo traitům Rustu), které (nebo která) musí typové parametry implementovat.

Žádný ze zmíněných jazyků nemá plnou typovou varianci. Rust a Go proto, že jejich typové systémy neznají třídní dědičnost. Ve Swiftu je částečně implementovaná (pro funkční typy), ale na rozdíl od Javy nebo Objective-C nelze volně specifikovat varianci typových parametrů v deklaracích.

Discussion (0)