Um Eigenschaften und Unterscheidungsmerkmale von Programmiersprachen geht es in der einhundertzweiundachzigsten Episode des IT-Berufe-Podcasts.
Inhalt
Was ist eine Programmiersprache?
- Programmiersprache: „Eine Programmiersprache ist eine formale Sprache zur Formulierung von Datenstrukturen und Algorithmen, d.h. von Rechenvorschriften, die von einem Computer ausgeführt werden können.“ [Herv. d. Verf.]
- Bausteine von Algorithmen: Sequenz, Verzweigung (z.B.
if
,switch
, aber auch Pattern Matching), Wiederholung (GOTO
, Schleifen, Rekursion) - Turing-complete: „[…] die Eigenschaft einer Programmiersprache oder eines anderen logischen Systems, sämtliche Funktionen berechnen zu können, die eine universelle Turingmaschine berechnen kann.“
Demnach sind keine Programmiersprachen: HTML/XML (Auszeichnungssprache), CSS (Stylesheet-Sprache), SQL (Datenbankabfragesprache).
Sprache vs. Plattform vs. Ökosystem
Programmiersprachen bringen meistens „eingebaute“ („native“) Funktionen mit, die direkt in der Syntax der Sprache formuliert werden können:
- Ein-/Ausgabe-Befehle, um Daten verarbeiten zu können
- Deklaration von Variablen zum Speichern von Informationen
- mathematische Funktionen wie Addition, Multiplikation usw.
- Steueranweisungen für Verzweigung und Wiederholung
- Möglichkeiten zur Programmunterteilung (z.B. Funktionen, Subprogramme)
- Einbinden von (externen) Bibliotheken zur Wiederverwendung
Viele Programmiersprachen bringen außerdem noch eine umfangreiche Bibliothek an vorgefertigten Implementierungen (z.B. in Form von Klassen in objektorientierten Sprachen) mit. Diese Bibliothek ist bei der Einarbeitung in eine neue Sprache meist schwieriger/langwieriger zu lernen als die Syntax. Oftmals teilen sich mehrere Programmiersprachen die Bibliotheken einer gemeinsamen Plattform, z.B. der JVM bei Java und Kotlin bzw. .NET bei C# und Visual Basic.
Darüber hinaus existiert meist auch noch ein ganzes Ökosystem rund um die Sprache/Plattform:
- Build-Tools, z.B. Maven, Gradle
- Dependency-Management, z.B. NPM, RubyGems
- Test-Frameworks, z.B. JUnit
- weitere Frameworks und Libraries, z.B. Spring, Jakarta EE, Rails, Blazor
Klassifizierung/Einsatzzweck(e)
Im Alltag sind die Identifikation und Auswahl einer für das jeweilige „Realweltproblem“ passenden Sprache wichtig. Viele Programmiersprachen haben Schwerpunkte bei ihrem Einsatz, weil sie für bestimmte Einsatzzwecke optimiert wurden oder dafür viele vorgefertigte Lösungen mitbringen.
- Einsatzzweck: Webanwendung (z.B. PHP), App (z.B. Swift), Desktop-Anwendung (z.B. C#), Server-Anwendung (z.B. Java)
- Frontend (browserseitig) vs. Backend
- Scriptsprachen: geringer Programmieraufwand für schnell sichtbare Ergebnisse, oft Interpretersprachen mit dynamischer Typisierung und laxer Syntaxprüfung (z.B. Semikolons optional), Beispiele: PowerShell, PHP
- Web-Programmiersprachen: bringen meist umfangreiche Bibliotheken und Frameworks für Webanwendungen mit, oftmals auch Scriptsprachen, Beispiele: PHP, Ruby, Python
Programmierparadigma
Ein Programmierparadigma gibt die grundsätzliche Art und Weise vor, wie mit einer Programmiersprache entwickelt wird. Es definiert grundlegende Herangehensweisen und Prinzipien bei der Softwareentwicklung, aber auch ganz konkrete syntaktische Vorgaben. So legt es z.B. fest, mit welchen Konstrukten das Programm hauptsächlich arbeitet (z.B. Objekte in der Objektorientierung bzw. Funktionen in der funktionalen Programmierung als sogenannte „First Class Citizens“), wie Programme modularisiert werden sollten und auf welche Art und Weise Algorithmen vorzugsweise formuliert werden sollten („idiomatische Programmierung“).
Viele Programmiersprachen sind heutzutage sogenannte Multiparadigmensprachen, bieten also Konzepte aus mehreren Paradigmen an, z.B. Objektorientierung und funktionale Programmierung. Meist haben sie aber ein definierendes Paradigma, z.B. Objektorientierung bei Java.
Imperativ vs. Deklarativ
Grundsätzlich kann man die imperative und deklarative Programmierung unterscheiden. Während bei der imperativen Programmierung (von lat. „imperare“ – befehlen) exakt vorgegeben wird, in welcher Reihenfolge der Computer welche Befehle wie ausführen muss, gibt man bei der deklarativen Programmierung (von lat. „declarare“ – erklären) lediglich vor, welches Ergebnis am Ende erreicht sein soll, und lässt den Computer den Weg dorthin selbst finden.
Beispiel:
// imperativ for (int i = 0; i < list.getSize(); i++) { System.out.println(list.get(i)); } // deklarativ list.forEach(System.out::println);
Konkrete Programmierparadigmen
- unstrukturiert: Einsatz von
GOTO
führte dazu, dass konkrete Programmabläufe nicht mehr nachvollzogen werden konnten - strukturiert: Verzicht auf
GOTO
und Einsatz von Kontrollstrukturen wieif
undwhile
- prozedural: Programme werden in kleine, wiederverwendbare Einheiten („Prozeduren“) aufgespalten
- funktional: (mathematische) Funktionen bilden den Kern dieser Vorgehensweise, Higher Order Functions, Immutability und Rekursion als wichtige Merkmale
- objektorientiert: Objekte kapseln Eigenschaften und Funktionen zu einer Einheit, Vererbung und Polymorphie als wichtige Merkmale
- logisch: Programmierung auf Basis der mathematischen Aussagenlogik
Compiler vs. Interpreter
- Compiler: Übersetzt Quellcode in Maschinen- oder Bytecode, bevor das Programm ausgeführt wird.
- JIT-Compiler: Just-In-Time-Compiler übersetzen z.B. Teile des Bytecodes zur Laufzeit in Maschinencode, um die Performance zu erhöhen.
- Interpreter: Interpretiert den Quellcode Zeile für Zeile und übersetzt ihn während der Ausführung in Maschinencode.
Typisierung
- statisch vs. dynamisch
- statisch: Datentypen stehen schon zur Compile-Zeit fest.
- dynamisch: Datentypen werden erst zur Laufzeit geprüft.
- stark vs. schwach: eher ein Spektrum („stärker/schwächer typisiert“) als eine harte Einteilung
- stark: keine Typumwandlung möglich oder nur explizit („Cast“,
(int)3.5
) - schwach: implizite Typumwandlungen durch die Sprache, z.B.
if (1) { ... }
- stark: keine Typumwandlung möglich oder nur explizit („Cast“,
Beispiele für alle Kombinationen
- statisch/stark: Java
> cat .Main.java class Main { public static void main(String[] args) { double d = 1.5; int i = d; } } > javac .Main.java .Main.java:6: error: incompatible types: possible lossy conversion from double to int int i = d; ^ 1 error
- statisch/schwach: C
> cat test.c #includeint main() { int i = 1; if (i) { printf("Hallon"); } return 0; } > gcc test.c -o test > ./test Hallo
- dynamisch/stark: Ruby
> cat .test.rb i = 1 s = "a" puts i + s > ruby .test.rb ./test.rb:3:in `+': String can't be coerced into Integer (TypeError) from ./test.rb:3:in `'
- dynamisch/schwach: PHP
> cat test.php $i = "asdf"; if ($i) { echo "Hallon"; } > php test.php Hallo
Syntax
Syntaktisch gibt es eigentlich nur die Unterscheidung zwischen Sprachen, die ähnlich zu C sind (insb. Klammern, Schlüsselwörter, Datentypen) oder eben nicht.
Beispiel Java (C-ähnlich):
void pruefePerson(int alter) { if (alter >= 18) { System.out.println("volljährig"); } }
Beispiel Ruby:
def pruefePerson(alter) puts "volljährig" if alter >= 18 end
Grafisch vs. textuell
Die weitaus meisten Programmiersprachen sind textuelle Sprachen, aber es gibt auch grafische Programmiersprachen, bei denen die Algorithmen „zusammengeklickt“ werden können. Ein Beispiel ist Scratch.
Abstraktionsniveau/Sprachhöhe
- 1GL: Maschinensprache, Nullen und Einsen
- 2GL: Assembler, etwas abstrakter, aber immer noch kryptisch, an bestimmte Prozessoren gebunden
- 3GL: moderne Hochsprachen wie C, Java usw.
- 4GL: Sprachen mit Fokus auf einen bestimmten Anwendungsbereich, Ziel: wenig Code für häufig benötigte Funktionen, Beispiele: Natural, ABAP
General Purpose vs. Domain Specific
- General Purpose Language (GPL): Kann eingesetzt werden, um beliebige Probleme zu lösen, verwendet aber eine allgemeine Syntax. Beispiele: Java, C#, PHP etc.
- Domain Specific Language (DSL): Kann nur Probleme eines genau abgegrenzten Bereichs lösen, verwendet dafür aber eine perfekt passende Syntax. Es gibt interne (fachliche APIs der eigenen Komponenten) und externe (komplett separate Programmiersprachen mit Compiler usw.).
Weitere Unterscheidungsmöglichkeiten
- Portabilität/Laufzeitumgebung: hardwarenah (C, C++) vs. virtuelle Maschine (Java, C#)
- Managed vs. unmanaged: Manuelle Speicherverwaltung (C) vs. Garbage Collector (Java, C#)
- Performance/Speicherverbrauch: Durch die Kombination mehrerer der obigen Eigenschaften können sich deutliche Unterschiede bei der Performance einzelner Sprachen ergeben. So ist ein Programm in C, das speziell für die konkrete Laufzeitumgebung kompiliert wurde, sicherlich schneller als ein Java-Programm, das auf einer virtuellen Maschine interpretiert und ausgeführt wird. Aber das ist immer noch schneller als ein JavaScript-Programm, das zunächst noch interpretiert werden muss.
Beispiele für Programmiersprachen
Diese Liste ist nicht vollständig!
- Web
- PHP: sehr verbreitete Web-Programmiersprache mit viel Unterstützung für übliche Anforderungen (z.B. Zugriff auf Query-String usw.)
- Ruby: Basis von Ruby on Rails und geschaffen, um Entwickler:innen glücklich zu machen
- Python: gerade im KI-Umfeld stark verbreitet
- JavaScript: bislang die einzige (!) Programmiersprache für das Frontend im Browser
- Typescript: statisch typisierte Alternative zu JavaScript
- Enterprise
- Java: großes Ökosystem, Langlebigkeit, Abwärtskompatibilität, sehr performant
- C#: stark verbreitet für Windows-Anwendungen
- COBOL: alte, aber immer noch in vielen großen Unternehmen eingesetzte 4GL-Sprache für klassische Business-Anwendungen
- ABAP: Programmiersprache von SAP
- VBA: Makrosprache für Microsoft Office
- App
- Kotlin: Standardsprache für Android-Anwendungen, läuft wie Java auf der JVM
- Swift: Standardsprache für iOS-Anwendungen, „Nachfolger“ von Objective-C
- Hardware
- Assembler: immer noch bei hochperformanten Anwendungen im Einsatz (z.B. Spiele)
- C: Basis vieler eingebetteter Systeme und Betriebssysteme
- C++: objektorientierter Aufsatz auf C
- Funktional
- Haskell: die funktionale Programmiersprache, in der realen Welt nicht allzu verbreitet
- F#: funktionale Sprache für .NET
- Lisp: Urvater der modernen funktionalen Programmiersprachen
- Elixir: basiert auf Erlang und ist stark bei nebenläufiger Programmierung
- Logisch
- Prolog: Programmierung mit Prädikatenlogik, Backtracking usw.
Literatur
Links
- Permalink zu dieser Podcast-Episode
- RSS-Feed des Podcasts
- Turing-Vollständigkeit (Wikipedia)
- Programmiersprache (Wikipedia)
- Einführung in Build-Werkzeuge
- Unit-Tests – Häufige Fragen im Fachgespräch
- Java EE 7 (Lernzielkontrolle)
- HTML
- Buchclub: Handbuch für Fachinformatiker (Teil 9: XML)
- Buchclub: Handbuch für Fachinformatiker (Teil 7: HTML und CSS)
- SQL – Häufige Fragen im Fachgespräch
- Variablen und Operatoren (Lernzielkontrolle)
- Algorithmen und Methoden (Lernzielkontrolle)
- Objektorientierung Teil 1 (Lernzielkontrolle)
- Markus Amshove über Domänenspezifische Sprachen
Der Beitrag Eigenschaften und Unterscheidung von Programmiersprachen – IT-Berufe-Podcast #182 erschien zuerst auf IT-Berufe-Podcast.