Który język programowania wybrać na początek - język natywny

12 minut(y)

Wiele osób pyta się, który język programowania wybrać na początek jako pierwszy język do nauki. Wiele jednak zależy od tego do czego chcemy użyć tego języka programowania. Dlatego wybrałem zwycięzców w czterech kategoriach:

  1. dynamicznie typowany język skryptowy ogólnego przeznaczenia
  2. statycznie typowany język korporacyjny używany do pisania długowiecznych aplikacji klasy enterprise
  3. język fullstackowy, który można używać do pisania frontendu i backendu
  4. szybki język natywny działający bez maszyny wirtualnej i interpretera

W tym artykule skupię się na zwycięzcy czwartej kategorii, czyli języku natywnym.

Ważna uwaga! Artykuł nie jest sponsorowany przez Google, mimo że każdy ze zwycięskich języków jest używany przez Google. Nie jest też sponsorowany przez JetBrains. Po prostu uważam, że JetBrains tworzy najlepsze IDE. Jest to tylko mój mały subiektywny ranking języków programowania na rok 2019.

Język natywny - definicja

Roboczo przyjmiemy następujące definicje:

  1. Język natywny jest to język, który pozwala pisać aplikacje natywne.
  2. Aplikacja natywna jest to aplikacja, która jest skompilowana do kodu natywnego.
  3. Kod natywny jest to najbliższa sprzętowo implementacja kodu jaka jest możliwa. Czyli:
    • Dla Androida będzie to kod bajtowy Javy
    • Dla przeglądarki będzie to JavaScript
    • Dla Maszyny Pascala był to PCode
    • Gdyby Micro$oft skończył prace nad systemem operacyjnym Midori byłby to kod bajtowy CIL
    • W pozostałych wypadkach będzie to najprawodpodobniej kod maszynowy

Zalety języków natywnych

Języki natywne posiadają wiele zalet. Dla mnie najważniejszymi są:

  • możliwość spakowania programu/aplikacji do jednego pliku wykonywalnego razem ze wszystkimi bibliotekami trzecimi i plikami konfiguracyjnymi
  • mała zajętość zasobów, głównie pamięci RAM
  • szybkość działania

Jedna paczka ze wszystkim jako plik wykonywalny

Jeśli piszemy aplikację desktopową prawdopodobnie chcielibyśmy dostarczyć klientowi jeden plik wykonywalny. Tak żeby nie musiał instalować żadnych maszyn wirtualnych, interpreterów i dodatkowych bibliotek współdzielonych. Tak właśnie jest w przypadku języków kompilowanych do kodu natywnego.

Jeśli piszemy w języku skryptowym czasem można oszukać. Niektóre języki skryptowe pozwalają spakować skrypty i interpreter w jeden wykonywalny plik. Przykładem może tu być Electron, który posłużył do napisania edytorów Atom oraz VSC.

Gorzej jest w przypadku języków korporacyjnych kompilowanych do kodu bajtowego. Ich maszyny wirtualne, jak JVM, są często zbyt duże, by móc je dołączać do każdego programu z osobna. Prawdziwy problem zaczyna się, gdy różne programy wymagają różnych wersji maszyny wirtualych np. JRE 6 i JRE 11.

Niska zajętość RAMu

Electron jest wspaniałym narzędziem, które pozwoliło programistom frontendu pisać aplikacje desktopowe. Ale jest to okupione dużym kosztem. Zwykle koszt ten przelicza się na duże zużycie pamięci RAM. Koszt ten wynika z tego, że każda aplikacja oparta na Electronie musi uruchomić zawarty w sobie interpreter JavaScriptu V8 oraz silnik do renderowania HTMLu i CSSów.

W przypadku języków natywnych możemy użyć bibliotek graficznych wbudowanych w system operacyjny. Lub przenośnych bibliotek graficznych jak GTK (Gnome) oraz Qt (KDE). Nie musimy uruchamiać także zewnętrznych interpreterów, maszyn wirtualnych i kompilatorów JIT.

Szybkość działania

Szybkość poszczególnych języków programowania jest tematem drażliwym. Często testy porównawcze (ang. benchmarks) szybkości działania poszczególnych języków programowania są pisane w taki sposób, żeby potwierdziły tezę piszącego. Potem można spotkać raporty z których wynika, że JavaScript jest szybszy od C++.

Drugim problemem jest to, że wydajność języków zmienia się w czasie. Np. początkowo kod bajtowy Javy był tylko interpretowany. Obecnie jest kompilowany podczas uruchomienia (ang. just-in-time compilation, JIT) Przydałby się niezależny zestaw testów nie sponsorowany przez żaden z języków. Szczęśliwie taki zestaw istnieje i nazywa się Benchmarks Game.

Na podstawie tego zestawu testów możemy podzielić języki programowania na kilka grup pod względem szybkości działania:

  1. Top - trzy prawie idealnie tak samo szybkie języki programowania:
    • C gcc
    • C++ g++
    • Rust - kompilowany do kodu natywnego język z systemem typów inspirowanych językiem Haskell
  2. Około dwa razy wolniejsze niż C:
    • Ada 2012 Gnat - kompilowany do kodu natywnego
    • Fortran Intel - kompilowany do kodu natywnego
    • Swift - kompilowany do kodu natywnego
    • C# .NET Core - kompilowany do kodu bajtowego CIL
    • Java - kompilowany do kodu bajtowego maszyny wirtualnej Javy
    • FreePascal - kompilowany do kodu natywnego
  3. Około trzy razy wolniejsze niż C:
    • Julia - kompilowany do kodu natywnego
    • Chapel
    • F#.NET Core - kompilowany do kodu bajtowego CIL
    • OCaml - obiektowa werjsa Meta Language kompilowany do kodu natywnego
    • LispSBCL - implementacja Common Lisp
    • Go - kompilowany do kodu natywnego
  4. Trzy do dziesięciu razy wolniejsze niż C:
  5. Ponad dziesięć razy wolniejsze niż C:

Moje największe zaskoczenia, gdy analizowałem wykresy:

  1. Do niedawna nikomu nieznany Rust znalazł się w pierwszej trójce najszybszych jezyków programowania.
  2. Kompilowany do kodu bajtowego F# wyprzedził kompilowany do kodu natywnego OCaml.
  3. Dynamicznie typowany Common Lisp wyprzedził statycznie typowany Go.
  4. TypeScript wyprzedził język Haskell - nie mam pojęcia jak oni to zrobili. Widocznie leniwość i monady są drogie.
  5. JavaScript (i TypeScript) niesamowicie wyprzedzają pozostałe języki skryptowe. Nawet język Erlang, w którym, z powodu niezmienności, powinny być możliwe bardzo agresywne optymalizacje.

Oczywiście z powodu istnienia kompilatora LLVM kolejność, jak i sama lista, może w najbliższej przyszłości się zmienić.

Język natywny - wady

Przez wiele lat jako najbardziej popularny natywny język programowania królował C++. Dlatego wady języków natywnych są to głównie wady języka C++. A lista rzeczy które można zarzucić C++ jest długa:

  • trudna składnia i bycie zbyt blisko sprzętu
  • długi czas kompilacji
  • duża ilość narzędzi potrzebnych do poprawnej kompilacji (kompilator/transpilator, konsolidator/linker, preprocesor/makroprocesor)
  • przestarzałe narzędzia
  • archaiczne podejście do zarządzania zależnościami, czyli jego brak
  • często biblioteki wymagane do kompilacji instaluje się wprost do systemu operacyjnego, dlatego istnieje żart że linux nie jest systemem operacyjnym tylko środowiskiem do programowania w C/C++

Na pewno nie jest to prosty język programowania. A niestety on jest często polecany jako pierwszy język do nauki programowania.

Język natywny - zwycięzcy …

Język C++ z pozycji lidera próbowały wygryźć inne języki jak D oraz Vala (język transpilowany do C i oparty na bibliotece GObject ze składnią wzorowaną na językach Java i C#). Jednak nie stała za tymi językami programowania żadna duża korporacja. Inaczej jest w przypadku języka Go, który jest zwycięzcą w kategorii. Główne cechy języka:

  • Język roku 2009 i 2016 według Tiobe
  • Pierwszy język stworzony przez Google, który przebił się do mainstreamu
  • Niestety GoLand od JetBains jest płatny, ale na szczęście istnieją inne dobre IDE dla Go jak Atom lub VSC
  • Mimo że to język względnie nowy powstała w nim już ogromna ilość oprogramowania jak np. Docker i Kubernetes.
  • Dzięki temu, że posiada ~~upośledzoną~~ prostą składnię prawdopodobnie będzie to też nowy język korporacyjny.

Dodatkowo problemy znane z C++ są rozwiązane w Go za pomocą wszystkomającego kompilatora, który także formatuje kod i zarządza zależnościami. Co prawda nie jest superszybki, co widać po testach, ale za to szybko się kompiluje.

… i trudne alternatywy

Co jednak jeśli nie chcemy pisać w języku z ~~upośledzoną~~ prostą składnią, który nie zawiera nawet generyków? Lub chcemy pisać szybko działające oprogramowanie? W takim przypadku istnieje kilka alternatyw, ale żadna z nich nie jest łatwa:

  • Rust - posiada zaawansowany system typów, domyślną niezmienność (ang. immutable), prawie klasy typów (ang. type class) i wiele innych cech deklaratywno funkcyjnych języków programowania. Jednocześnie pozwalając na imperatywne optymalizacje tam gdzie ważna jest wydajność.
  • Pony - posiada typowany system aktorów inspirowany językiem Erlang, i podobnie jak Rust, rozbudowany system własności zmiennych.
  • Crystal - język bazujący składniowo na języku Ruby, a więc kolejny spadkobierca języka Smalltalk

Oprócz tego część języków nienatywnych stara się zostać językami natywnymi. Przykładami mogą tu być Scala Native i Kotlin Native. Jest to dziś łatwiejsze dzięki istnieniu kompilatora LLVM.

Podsumowanie

W tej chwili trudno doradzać komuś naukę języków Go lub Rust jako pierwszego języka programowania. Ponieważ aktualnie ilość ofert pracy dla Go jest mniejsza niż dla języka Scala. Np. na Górnym Śląsku jest jedna firma zatrudniająca do Go. Z drugiej strony ilość i jakość narzędzi, które powstają w Go jest niesamowita. I to może odwrócić trend w najbliższych latach.

Zostaw komentarz