Listen

Listen sind in Haskell ein wichtiger Bestandteil der Sprache. Zum einen können quasi beliebig viele Werte gespeichert werden (bis der Speicher des Computers voll ist), allerdings ermöglicht die List Comprehension eine Art mathematische Definition von Listen. Des Weiteren sind auch unendliche Listen möglich, die nur bis zu einem bestimmten Punkt berechnet werden (siehe Lazy Evaluation).

Syntax

Eine Liste enthält nur Werte eines Datentyps. Dh. wollen wir eine Liste mit Integers eine Funktion übergeben, sähe das so aus:

func :: [Integer] -> Int

Allerdings kann es sein, dass mehrere Werte pro Listenelement vorhanden sein sollen (bspw. wenn man eine Messwerttabelle hat), dann verwendet man als Listenelement einen Tupeltyp, bspw:

func :: [(Int, Double)] -> Int

Interne Repräsentation

Wie eine Liste intern repräsentiert wird, hängt stark vom Compiler / Interpreter ab. Wenn der Compiler der Überzeugung ist, die Liste mit einem Array effizienter speichern zu können als mit einer verketteten Liste, dann wird er die Liste als Array intern anlegen, ansonsten als Liste. Darauf hat man als Haskell-Programmierer keinen Einfluss - muss sich damit nicht auskennen oder auseinandersetzen.

List Comprehension

List Comprehension dient dazu Listen aufzubauen anhand einer mathematischen Vorschrift.

Syntax

List Comprehension hat folgende Syntax:

[x | x <- <Grundwertemenge> , <Filter>]
[<varname> | <Generator>, <Filter>]

Der Variablenname ist austauschbar. Es können beliebig viele Filter stehen. Auch möglich sind Liste folgendermaßen aufzubauen (Liste mit Integerzahlen von 1 bis 10):

[1..10]

An einem Beispiel besprechen wir die Lesweise:

[x | x <- [1..10] , mod (x*x) 2 == 0]

Die Liste besteht aus allen x, x in [1..10], für die gilt: x^2 \mod 2 = 0. Dh. aus den Zahlen von 1 bis 10 erfüllen folgende Zahlen das Kriterium:

[2,4,6,8,10]

Als Generator kann auch eine Aufzählung stehen, also bspw:

[x | x <- [1,2,3,4,5,6,7,8,9,10] , mod (x*x) 2 == 0]

Es können auch Listen als Generator benutzt werden, wie bspw. [1,3,5,…,100]:

[x | x <- [1,3..100] , mod (x*x) 2 == 0]

Nehmen wir an, dass xs im folgenden Beispiel eine Liste mit Ganzzahlwerten ist, so ist auch folgendes zulässig:

[x | x <- xs , mod (x*x) 2 == 0]]

Unendliche Listen

In Haskell ist es dank Lazy Evaluation möglich mit unendlichen Listen zu rechnen. Natürlich geht das nicht unbegrenzt lange, irgendwann ist der Speicher verbraucht, allerdings ist es möglich diese Listen nur bis zu einem bestimmten Folgenglied zu verwenden, dann wird die Haskell-Laufzeitumgebung die Werte nur bis zu diesem Wert berechnen. So ist es möglich die Erzeugung von Listen von der Verwendung zu trennen (also die Erzeugung in eine eigene Funktion zu packen und die Verarbeitung in eine andere).

Verwendung von Listen in Funktionen

Wollen wir eine Liste an eine Funktion übergeben, so müssen wir Haskell das mitteilen, indem wir den Datentyp in eckige Klammern setzen. Dann können wir in der entweder auf die gesamte Liste zugreifen oder das erste Elemente vom Rest abtrennen:

func :: [Int] -> Int
func (x:xs) = 1

Aber hier fehlt noch ein Fall. Wenn wir diese Funktion mit einer leeren Liste ([]) aufrufen, dann beendet der Interpreter unser Programm. Deshalb gehört der Fall noch mit abgefangen:

func :: [Int] -> Int
func [] = 1
func (x:xs) = 1

Eine Funktion, die bspw. alle Integer-Elemente der Liste zusammenaddiert, kann rekursiv so aufgebaut werden:

addList :: [Int] -> Int
addList [] = 0
addList (x:xs) = x + addList xs

Dh. wir definieren, dass bei einer leeren Liste die Summe 0 ist (also auch, wenn unser Programm so weit fortgeschritten ist, dass die Funktion mit einer leeren Liste aufgerufen wird), ansonsten trennen wir das erste Element vom Rest ab und addieren das Element mit der Summe der restlichen Liste. Der Rest der Liste wird dabei beim letzten Element zur leeren Liste, dh. wir benötigen den Fall der leeren Liste zwingend.

Wir können die List Comprehension auch innerhalb von Funktionen benutzen. Will ich bspw. in einer Funktion die Funktion addList mit der Liste von oben rufen, könnte das so aussehen:

func :: Int
func = addList [x | x <- [1..10] , mod (x*x) 2 == 0]

Listen zusammenfügen

Listen werden Schrittweise rekursiv (siehe Kapitel Rekursion) verarbeitet. Dabei kann es notwendig sein, die Ergebnisse in einer Liste zu speichern und zurückzugeben. Listen können mit dem ++ Operator zusammengesetzt. Dabei wird die erste Liste um die zweite erweitert - es findet keine Sortierung der Werte statt!

Nehmen wir eine Funktion, die alle Werte einer Liste um den Wert 1 erhöht:

add1 :: [Int] -> [Int] 
add1 [] = []
add1 (x:xs) = [(x+1)] ++ add1 xs

Dabei wird die Liste mit einem Element (x+1) mit der Liste zusammengefügt, die add1 mit dem Parameter xs (Restliste) zurückliefert.

Wollen Sie nur auf Teile der Liste zugreifen, gibt es eingebaute Funktionen dafür:

  • (++) → Zusammensetzen von 2 Listen
  • head → erstes Element der Liste
  • last → letztes Element der Liste
  • init → alles, außer das letzte Element
  • tail → alles, außer das erste Element
  • !!n → Zugriff auf das n-te Element der Liste