OCaml – Wikipedia

OCaml
Paradigma Multi-Paradigma: funktional, imperativ, modular,[1]objektorientierten
Familie ML
Entworfen von Xavier Leroy, Jérôme Vouillon, Damien Doligez, Didier Rémy und Ascánder Suárez
Entwickler INRIA
Erstmals erschienen 1996;; vor 25 Jahren (1996)
Stabile Version
4.11.1[2]

/ 31. August 2020;; vor 4 Monaten (31. August 2020)

Schreibdisziplin Abgeleitet, statisch, stark, strukturell
Implementierungssprache OCaml, C.
Plattform IA-32, x86-64, Power, SPARC, ARM 32-64
Betriebssystem Plattformübergreifend: Unix, MacOS, Windows
Lizenz LGPLv2.1
Dateinamenerweiterungen .ml, .mli
Webseite ocaml.org
Beeinflusst von
C, Caml, Modula-3, Pascal, Standard ML
Beeinflusst
ATS, Coq, Ulme, F #, F *, Haxe, Opa, Rost, Scala

OCaml ( Oh-KAM-əlfrüher Ziel Caml) ist eine universelle Programmiersprache mit mehreren Paradigmen, die den Caml-Dialekt von ML um objektorientierte Funktionen erweitert. OCaml wurde 1996 von Xavier Leroy, Jérôme Vouillon, Damien Doligez, Didier Rémy, Ascánder Suárez und anderen gegründet.

Die OCaml-Toolchain enthält einen interaktiven Interpreter der obersten Ebene, einen Bytecode-Compiler, einen optimierenden nativen Code-Compiler, einen reversiblen Debugger und einen Paketmanager (OPAM). OCaml wurde ursprünglich im Rahmen der automatisierten Theoremprüfung entwickelt und ist in der Software für statische Analysen und formale Methoden übergroß vertreten. Über diese Bereiche hinaus hat es unter anderem in der Systemprogrammierung, Webentwicklung und im Financial Engineering ernsthafte Verwendung gefunden.

Das Akronym CAML stand ursprünglich für Kategoriale abstrakte Maschinensprache, aber OCaml lässt diese abstrakte Maschine aus.[3] OCaml ist ein kostenloses Open-Source-Softwareprojekt, das vom französischen Institut für Informatik- und Automatisierungsforschung (INRIA) verwaltet und hauptsächlich verwaltet wird. In den frühen 2000er Jahren wurden Elemente von OCaml von vielen Sprachen übernommen, insbesondere von F # und Scala.

Philosophie[edit]

Von ML abgeleitete Sprachen sind am besten für ihre statischen Typsysteme und Typ-Inferenz-Compiler bekannt. OCaml vereint funktionale, imperative und objektorientierte Programmierung unter einem ML-ähnlichen Typsystem. Daher müssen Programmierer mit dem rein funktionalen Sprachparadigma für die Verwendung von OCaml nicht sehr vertraut sein.

Durch die Anforderung, dass der Programmierer innerhalb der Einschränkungen seines statischen Typsystems arbeiten muss, beseitigt OCaml viele der typbezogenen Laufzeitprobleme, die mit dynamisch typisierten Sprachen verbunden sind. Außerdem reduziert der Typ-Inferenz-Compiler von OCaml den Bedarf an manuellen Typanmerkungen, die in den meisten statisch typisierten Sprachen erforderlich sind, erheblich. Beispielsweise müssen der Datentyp von Variablen und die Signatur von Funktionen normalerweise nicht explizit deklariert werden, wie dies in Sprachen wie Java und C # der Fall ist, da sie von den Operatoren und anderen Funktionen abgeleitet werden können, die auf die Variablen und andere Werte angewendet werden im Code. Die effektive Verwendung des OCaml-Typsystems kann von Seiten eines Programmierers einige Raffinesse erfordern, aber diese Disziplin wird mit zuverlässiger, leistungsstarker Software belohnt.

OCaml unterscheidet sich vielleicht am meisten von anderen Sprachen mit akademischem Ursprung durch die Betonung der Leistung. Das statische Typsystem verhindert Fehlanpassungen des Laufzeit-Typs und vermeidet so Laufzeit-Typ- und Sicherheitsüberprüfungen, die die Leistung dynamisch typisierter Sprachen belasten, und garantiert gleichzeitig die Laufzeitsicherheit, außer wenn die Überprüfung der Array-Grenzen deaktiviert ist oder wenn einige typsichere Funktionen wie die Serialisierung verwendet werden . Diese sind selten genug, dass es in der Praxis durchaus möglich ist, sie zu vermeiden.

Abgesehen vom Aufwand für die Typprüfung ist es aufgrund von Problemen wie dem Funarg-Problem im Allgemeinen schwierig, funktionale Programmiersprachen zu effizientem Maschinensprachencode zu kompilieren. Neben Standardoptimierungen für Schleifen, Register und Anweisungen verwendet der Optimierungscompiler von OCaml statische Programmanalysemethoden, um das Werteboxen und die Zuweisung von Abschlüssen zu optimieren und die Leistung des resultierenden Codes zu maximieren, selbst wenn funktionale Programmierkonstrukte umfassend verwendet werden.

Xavier Leroy hat erklärt, dass “OCaml mindestens 50% der Leistung eines anständigen C-Compilers liefert”,[4] obwohl ein direkter Vergleich unmöglich ist. Einige Funktionen in der OCaml-Standardbibliothek werden mit schnelleren Algorithmen implementiert als äquivalente Funktionen in den Standardbibliotheken anderer Sprachen. Beispielsweise ist die Implementierung der Mengenvereinigung in der OCaml-Standardbibliothek theoretisch asymptotisch schneller als die entsprechende Funktion in den Standardbibliotheken imperativer Sprachen (z. B. C ++, Java), da die OCaml-Implementierung die Unveränderlichkeit von Mengen ausnutzt, um Teile der Eingabe wiederzuverwenden setzt in der Ausgabe (siehe persistente Datenstruktur).

Eigenschaften[edit]

OCaml bietet ein statisches Typsystem, Typinferenz, parametrischen Polymorphismus, Schwanzrekursion, Mustervergleich, erstklassige lexikalische Verschlüsse, Funktoren (parametrische Module), Ausnahmebehandlung und inkrementelle automatische Speicherbereinigung für Generationen.

OCaml ist bemerkenswert für die Erweiterung der Typinferenz im ML-Stil auf ein Objektsystem in einer Allzwecksprache. Dies ermöglicht eine strukturelle Untertypisierung, bei der Objekttypen kompatibel sind, wenn ihre Methodensignaturen kompatibel sind, unabhängig von ihrer deklarierten Vererbung (ein ungewöhnliches Merkmal in statisch typisierten Sprachen).

Eine Fremdfunktionsschnittstelle zum Verknüpfen mit C-Grundelementen wird bereitgestellt, einschließlich Sprachunterstützung für effiziente numerische Arrays in Formaten, die sowohl mit C als auch mit Fortran kompatibel sind. OCaml unterstützt auch das Erstellen von Bibliotheken mit OCaml-Funktionen, die mit a verknüpft werden können Main Programm in C, so dass eine OCaml-Bibliothek an C-Programmierer verteilt werden kann, die keine Kenntnisse oder Installation von OCaml haben.

Die OCaml-Distribution enthält:

Der native Code-Compiler ist für viele Plattformen verfügbar, einschließlich Unix, Microsoft Windows und Apple MacOS. Die Portabilität wird durch die Unterstützung der nativen Codegenerierung für wichtige Architekturen erreicht: IA-32, X86-64 (AMD64), Power, SPARC, ARM und ARM64.[5]

OCaml-Bytecode- und native Code-Programme können in einem Multithread-Stil mit präemptiver Kontextumschaltung geschrieben werden. Da der Garbage Collector des INRIA OCaml-Systems (der derzeit einzige verfügbare vollständige Implementierung der Sprache) nicht für die Parallelität ausgelegt ist, wird die symmetrische Mehrfachverarbeitung nicht unterstützt.[6] OCaml-Threads im selben Prozess werden nur durch Time-Sharing ausgeführt. Es gibt jedoch mehrere Bibliotheken für verteiltes Rechnen wie z Funktion und Okamnet / Plasma.

Entwicklungsumgebung[edit]

Seit 2011 wurden viele neue Tools und Bibliotheken in die OCaml-Entwicklungsumgebung aufgenommen:

  • Entwicklungswerkzeuge
    • opam ist ein Paketmanager für OCaml, entwickelt von OCamlPro.
    • Merlin Bietet IDE-ähnliche Funktionen für mehrere Editoren, einschließlich Type Throwback, Go-to-Definition und Auto-Completion.
    • Düne ist ein zusammensetzbares Build-System für OCaml.
    • OCamlformat ist ein Auto-Formatierer für OCaml.
  • Websites:
  • Alternative Compiler für OCaml:
    • js_of_ocaml, entwickelt vom Ocsigen-Team, ist ein optimierender Compiler von OCaml zu JavaScript.
    • BuckleScript, das auch auf JavaScript abzielt, mit dem Schwerpunkt auf der Erstellung lesbarer, idiomatischer JavaScript-Ausgaben.
    • ocamlcc ist ein Compiler von OCaml bis C, der den nativen Code-Compiler für nicht unterstützte Plattformen ergänzt.
    • OCamlJava, entwickelt von INRIA, ist ein Compiler von OCaml zur Java Virtual Machine (JVM).
    • OCaPic, entwickelt von Lip6, ist ein OCaml-Compiler für PIC-Mikrocontroller.

Codebeispiele[edit]

Ausschnitte aus OCaml-Code lassen sich am einfachsten untersuchen, indem Sie sie in das eingeben Höchststufe. Dies ist eine interaktive OCaml-Sitzung, die die abgeleiteten Typen der resultierenden oder definierten Ausdrücke druckt. Die OCaml-oberste Ebene wird durch einfaches Ausführen des OCaml-Programms gestartet:

$ ocaml
     Objective Caml version 3.09.0
#

Der Code kann dann an der Eingabeaufforderung “#” eingegeben werden. Zum Beispiel, um 1 + 2 * 3 zu berechnen:

# 1 + 2 * 3;;
- : int = 7

OCaml leitet den Typ des Ausdrucks als “int” (eine Ganzzahl mit Maschinengenauigkeit) ab und gibt das Ergebnis “7” aus.

Hallo Welt[edit]

Das folgende Programm “hello.ml”:

print_endline "Hello World!"

kann in eine ausführbare Bytecode-Datei kompiliert werden:

$ ocamlc hello.ml -o hello

oder in eine optimierte ausführbare Datei mit nativem Code kompiliert:

$ ocamlopt hello.ml -o hello

und ausgeführt:

Das erste Argument für ocamlc, “hello.ml”, gibt die zu kompilierende Quelldatei an, und das Flag “-o hello” gibt die Ausgabedatei an.[7]

Summieren einer Liste von ganzen Zahlen[edit]

Listen sind einer der grundlegenden Datentypen in OCaml. Das folgende Codebeispiel definiert eine rekursive Funktion Summe das akzeptiert ein Argument, ganze Zahlen, die eine Liste von ganzen Zahlen sein soll. Beachten Sie das Schlüsselwort rec was bedeutet, dass die Funktion rekursiv ist. Die Funktion durchläuft rekursiv die angegebene Liste von Ganzzahlen und liefert eine Summe der Elemente. Das Spiel Die Anweisung hat Ähnlichkeiten mit dem Schalterelement von C, ist jedoch weitaus allgemeiner.

let rec sum integers =                   (* Keyword rec means 'recursive'. *)
  match integers with
  | [] -> 0                              (* Yield 0 if integers is the empty 
                                            list []. *)
  | first :: rest -> first + sum rest;;  (* Recursive call if integers is a non-
                                            empty list; first is the first 
                                            element of the list, and rest is a 
                                            list of the rest of the elements, 
                                            possibly []. *)
  # sum [1;2;3;4;5];;
  - : int = 15

Eine andere Möglichkeit ist die Verwendung der Standard-Falzfunktion, die mit Listen funktioniert.

let sum integers =
  List.fold_left (fun accumulator x -> accumulator + x) 0 integers;;
  # sum [1;2;3;4;5];;
  - : int = 15

Da die anonyme Funktion einfach die Anwendung des Operators + ist, kann dies verkürzt werden auf:

let sum integers =
  List.fold_left (+) 0 integers

Darüber hinaus kann man das Listenargument weglassen, indem man eine Teilanwendung verwendet:

let sum =
  List.fold_left (+) 0

Schnelle Sorte[edit]

OCaml bietet sich an, um rekursive Algorithmen präzise auszudrücken. Das folgende Codebeispiel implementiert einen Algorithmus ähnlich dem Quicksort, der eine Liste in aufsteigender Reihenfolge sortiert.

 let rec qsort = function
   | [] -> []
   | pivot :: rest ->
     let is_less x = x < pivot in
     let left, right = List.partition is_less rest in
     qsort left @ [pivot] @ qsort right

Geburtstagsproblem[edit]

Das folgende Programm berechnet die kleinste Anzahl von Personen in einem Raum, für die die Wahrscheinlichkeit vollständig eindeutiger Geburtstage weniger als 50% beträgt (das Geburtstagsproblem, bei dem für 1 Person die Wahrscheinlichkeit 365/365 (oder 100%) beträgt, für 2 Personen 364/365, für 3 ist es 364/365 × 363/365 usw.) (Antwort = 23).

let year_size = 365.

let rec birthday_paradox prob people =
  let prob = (year_size -. float people) /. year_size *. prob  in
  if prob < 0.5 then
    Printf.printf "answer = %dn" (people+1)
  else
    birthday_paradox prob (people+1)
;;

birthday_paradox 1.0 1

Kirchenzahlen[edit]

Der folgende Code definiert eine kirchliche Kodierung natürlicher Zahlen mit Nachfolger (succ) und Addition (add). Eine Kirchennummer n ist eine Funktion höherer Ordnung, die eine Funktion akzeptiert f und ein Wert x und gilt f zu x genau n mal. Um eine Church-Zahl von einem Funktionswert in eine Zeichenfolge umzuwandeln, übergeben wir ihr eine Funktion, die der Zeichenfolge vorangestellt ist "S" zu seiner Eingabe und der konstanten Zeichenfolge "0".

let zero f x = x
let succ n f x = f (n f x)
let one = succ zero
let two = succ (succ zero)
let add n1 n2 f x = n1 f (n2 f x)
let to_string n = n (fun k -> "S" ^ k) "0"
let _ = to_string (add (succ two) two)

Faktorielle Funktion mit beliebiger Genauigkeit (Bibliotheken)[edit]

Eine Vielzahl von Bibliotheken ist direkt von OCaml aus zugänglich. Zum Beispiel hat OCaml eine eingebaute Bibliothek für Arithmetik mit beliebiger Genauigkeit. Da die Fakultätsfunktion sehr schnell wächst, werden maschinengenaue Zahlen (normalerweise 32- oder 64-Bit) schnell übergelaufen. Fakultät ist somit ein geeigneter Kandidat für Arithmetik mit beliebiger Genauigkeit.

In OCaml bietet das Num-Modul (das jetzt vom ZArith-Modul abgelöst wird) eine Arithmetik mit beliebiger Genauigkeit und kann in eine laufende oberste Ebene geladen werden, indem:

# #use "topfind";;
# #require "num";;
# open Num;;

Die Fakultätsfunktion kann dann unter Verwendung der numerischen Operatoren mit beliebiger Genauigkeit geschrieben werden = /, * / und – / ::

# let rec fact n =
    if n =/ Int 0 then Int 1 else n */ fact(n -/ Int 1);;
val fact : Num.num -> Num.num = <fun>

Diese Funktion kann viel größere Fakultäten berechnen, z. B. 120!:

# string_of_num (fact (Int 120));;
- : string =
"6689502913449127057588118054090372586752746333138029810295671352301633
55724496298936687416527198498130815763789321409055253440858940812185989
8481114389650005964960521256960000000000000000000000000000"

Dreieck (Grafik)[edit]

Das folgende Programm rendert mit OpenGL ein rotierendes Dreieck in 2D:

let () =
  ignore (Glut.init Sys.argv);
  Glut.initDisplayMode ~double_buffer:true ();
  ignore (Glut.createWindow ~title:"OpenGL Demo");
  let angle t = 10. *. t *. t in
  let render () =
    GlClear.clear [ `color ];
    GlMat.load_identity ();
    GlMat.rotate ~angle: (angle (Sys.time ())) ~z:1. ();
    GlDraw.begins `triangles;
    List.iter GlDraw.vertex2 [-1., -1.; 0., 1.; 1., -1.];
    GlDraw.ends ();
    Glut.swapBuffers () in
  GlMat.mode `modelview;
  Glut.displayFunc ~cb:render;
  Glut.idleFunc ~cb:(Some Glut.postRedisplay);
  Glut.mainLoop ()

Die LablGL-Bindungen an OpenGL sind erforderlich. Das Programm kann dann zu Bytecode kompiliert werden mit:

  $ ocamlc -I +lablGL lablglut.cma lablgl.cma simple.ml -o simple

oder zum Nativecode mit:

  $ ocamlopt -I +lablGL lablglut.cmxa lablgl.cmxa simple.ml -o simple

oder einfacher mit dem Befehl ocamlfind build

  $ ocamlfind opt simple.ml -package lablgl.glut -linkpkg -o simple

und Renn:

  $ ./simple

In OCaml können weitaus ausgefeiltere, leistungsstärkere 2D- und 3D-Grafikprogramme entwickelt werden. Dank der Verwendung von OpenGL und OCaml können die resultierenden Programme plattformübergreifend sein und auf vielen wichtigen Plattformen ohne Änderungen kompiliert werden.

Fibonacci-Folge[edit]

Der folgende Code berechnet die Fibonacci-Folge einer Zahl n eingegeben. Es verwendet Schwanzrekursion und Mustervergleich.

let fib n =
  let rec fib_aux m a b =
    match m with
    | 0 -> a
    | _ -> fib_aux (m - 1) b (a + b)
  in fib_aux n 0 1

Funktionen höherer Ordnung[edit]

Funktionen können als Ergebnis Funktionen als Eingabe- und Rückgabefunktionen übernehmen. Zum Beispiel bewerben zweimal zu einer Funktion f ergibt eine zutreffende Funktion f zweimal zu seinem Argument.

let twice (f : 'a -> 'a) = fun (x : 'a) -> f (f x);;
let inc (x : int) : int = x + 1;;
let add2 = twice inc;;
let inc_str (x : string) : string = x ^ " " ^ x;;
let add_str = twice(inc_str);;
  # add2 98;;
  - : int = 100
  # add_str "Test";;
  - : string = "Test Test Test Test"

Die Funktion zweimal verwendet eine Typvariable ‘ein um anzuzeigen, dass es auf jede Funktion angewendet werden kann f Zuordnung von einem Typ ‘ein zu sich selbst und nicht nur zu int-> int Funktionen. Speziell, zweimal kann sogar auf sich selbst angewendet werden.

  # let fourtimes f = (twice twice) f;;
  val fourtimes : ('a -> 'a) -> 'a -> 'a = <fun>
  # let add4 = fourtimes inc;;
  val add4 : int -> int = <fun>
  # add4 98;;
  - : int = 102

Abgeleitete Sprachen[edit]

MetaOCaml[edit]

MetaOCaml[8] ist eine mehrstufige Programmiererweiterung von OCaml, die das inkrementelle Kompilieren von neuem Maschinencode zur Laufzeit ermöglicht. Unter bestimmten Umständen sind bei mehrstufiger Programmierung erhebliche Beschleunigungen möglich, da zur Laufzeit detailliertere Informationen zu den zu verarbeitenden Daten verfügbar sind als zur regulären Kompilierungszeit, sodass der inkrementelle Compiler viele Fälle von Bedingungsprüfungen usw. optimieren kann.

Als Beispiel: Wenn zur Kompilierungszeit bekannt ist, dass eine Potenzfunktion funktioniert x -> x^n wird oft benötigt, aber der Wert von n ist nur zur Laufzeit bekannt, eine zweistufige Power-Funktion kann in MetaOCaml verwendet werden:

 let rec power n x =
   if n = 0
   then .<1>.
   else
     if even n
     then sqr (power (n/2) x)
     else .x *. .~(power (n - 1) x)>.

Sobald n ist zur Laufzeit bekannt, kann eine spezialisierte und sehr schnelle Power-Funktion erstellt werden:

 .<fun x -> .~(power 5 .<x>.)>.

Das Ergebnis ist:

 fun x_1 -> (x_1 *
     let y_3 = 
         let y_2 = (x_1 * 1)
         in (y_2 * y_2)
     in (y_3 * y_3))

Die neue Funktion wird automatisch kompiliert.

Andere abgeleitete Sprachen[edit]

  • AtomCaml bietet ein Synchronisationsprimitiv für die atomare (Transaktions-) Ausführung von Code.
  • Emily (2006) ist eine Teilmenge von OCaml 3.08, die einen Entwurfsregelprüfer verwendet, um die Sicherheitsprinzipien des Objektfähigkeitsmodells durchzusetzen.
  • F # ist eine .NET Framework-Sprache, die auf OCaml basiert.
  • Fresh OCaml erleichtert die Bearbeitung von Namen und Ordnern.
  • GCaml fügt OCaml einen Extensionspolymorphismus hinzu und ermöglicht so eine Überladung und typsicheres Marshalling.
  • JoCaml integriert Konstruktionen für die Entwicklung gleichzeitiger und verteilter Programme.
  • OCamlDuce erweitert OCaml um Funktionen wie XML-Ausdrücke und Typen mit regulären Ausdrücken.
  • OCamlP3l ist ein paralleles Programmiersystem, das auf OCaml und der Sprache P3L basiert.
  • Reason ist zwar keine eigenständige Sprache, aber eine alternative OCaml-Syntax und Toolchain für OCaml, die bei Facebook erstellt wurde.

In OCaml geschriebene Software[edit]

  • 0install, ein plattformübergreifender Paketmanager.
  • Coccinelle, ein Dienstprogramm zum Transformieren des Quellcodes von C-Programmen.
  • Coq, ein formales Proof-Management-System.
  • FFTW, eine Bibliothek zur Berechnung diskreter Fourier-Transformationen. Mehrere C-Routinen wurden von einem OCaml-Programm mit dem Namen generiert genfft.
  • Die Webversion von Facebook Messenger.[9]
  • Flow, ein bei Facebook erstellter statischer Analysator, der statische Typen auf JavaScript ableitet und überprüft.[10]
  • Owl Scientific Computing, ein spezielles System für wissenschaftliches und technisches Computing.
  • Frama-C, ein Framework zur Analyse von C-Programmen.
  • GeneWeb, kostenlose und Open-Source-Genealogie-Software für mehrere Plattformen.
  • Der auf Facebook erstellte Hack-Programmiersprachen-Compiler erweitert PHP um statische Typen.
  • Der Programmiersprachen-Compiler von Haxe.
  • HOL Light, ein formeller Beweisassistent.
  • Infer, ein statischer Analysator, der auf Facebook für Java, C, C ++ und Objective-C erstellt wurde und zur Erkennung von Fehlern in iOS- und Android-Apps verwendet wird.[11]
  • Lexifi Apropos, ein System zur Modellierung komplexer Derivate.
  • MirageOS, ein Unikernel-Programmierframework, das in reinem OCaml geschrieben wurde.
  • MLdonkey, eine Peer-to-Peer-Filesharing-Anwendung, die auf dem EDonkey-Netzwerk basiert.
  • Ocsigen, ein OCaml-Webframework.
  • Opa, eine kostenlose Open-Source-Programmiersprache für die Webentwicklung.
  • pyre-check, eine bei Facebook erstellte Typprüfung für Python.[12]
  • Tezos, eine sich selbst ändernde intelligente Vertragsplattform, die XTZ als Mutterwährung verwendet.
  • Einklang, ein Dateisynchronisierungsprogramm zum Synchronisieren von Dateien zwischen zwei Verzeichnissen.
  • Der Referenzinterpreter für WebAssembly, ein Bytecode auf niedriger Ebene, der für die Ausführung in Webbrowsern vorgesehen ist.[13]
  • Xen Cloud Platform (XCP), eine schlüsselfertige Virtualisierungslösung für den Xen-Hypervisor.

Mehrere Dutzend Unternehmen setzen OCaml bis zu einem gewissen Grad ein.[14] Bemerkenswerte Beispiele sind:

  • Bloomberg LP, die erstellt BuckleScript, ein OCaml-Compiler-Backend für JavaScript.[15]
  • Citrix Systems, das OCaml in XenServer verwendet (im Jahr 2018 in Citrix Hypervisor umbenannt).
  • Facebook, das Flow, Hack, Infer, Pfff und Reason in OCaml entwickelt hat.
  • Jane Street Capital, eine firmeneigene Handelsfirma, die OCaml in ihren Anfängen als bevorzugte Sprache gewählt hat.[16]
  • MEDIT, Frankreich, verwendet OCaml für die Bioformatik.[17]

Verweise[edit]

Externe Links[edit]