Typen in Haskell

8

Ich bin irgendwie neu in Haskell und ich habe Schwierigkeiten zu verstehen, wie abgeleitete Typen und solche Arbeiten.

%Vor%

Was genau bedeutet das?

%Vor%

Was sind die Unterschiede zwischen diesen?

Und der abgeleitete Typ von

foldr Karte ist [a] - & gt; [a - & gt; a] - & gt; [a]

aber warum ist das so?

Danke!

    
Linda Cohen 11.05.2010, 18:41
quelle

4 Antworten

10

Wenn Sie das Beispiel von

nehmen %Vor%

Dies bedeutet, dass map zwei Argumente benötigt

  1. Eine Funktion von Typ a nach Typ b
  2. Eine Liste vom Typ a

Und es kommt zurück

  1. Eine Liste von b

Sie können das Muster bereits sehen, aber es wird klarer, wenn wir 'a' und 'b'

ersetzen
  • a = Zeichenfolge
  • b = Int

Also wird diese Typdefinition

sein %Vor%

Nun ist es also eine Funktion, die einen String zurücknimmt und Int und eine Liste von Strings zurückgibt und eine Liste von Ints zurückgibt.

Sagen Sie unsere Funktion, die einen String übernimmt und zurückgibt, und Int ist read . read verwendet den String, den Sie ihm geben, um ihn in etwas anderes zu konvertieren. Da wir es hier in einen Int-Kontext stellen, wird die Zeichenkette in int

konvertiert %Vor%

Dies wird zu

führen %Vor%

weil map die Funktion read und Maps für jedes Element der Liste übernimmt (anwendet). Sie müssen nicht read sagen, es ist ein Argument wie read "1" oder read n , weil map das für Sie erledigt.

Und das ist wirklich alles, was dazu gehört. Der Typ einer Funktion sagt nur, welche Typen sie benötigt und welchen Typ sie zurückgibt. Natürlich gibt es auch Curry, aber dazu wirst du später entweder absichtlich kommen oder nicht! Es bedeutet im Grunde, dass eine Funktion nicht viele Argumente akzeptiert, sondern nur eins. Nehmen wir an, Sie nehmen die Funktion + . Wenn Sie 1+2 auswerten, gibt es eine Funktion zurück, die eine andere Zahl übernimmt, die zu 1 hinzugefügt wird, und da es hier eine andere Zahl gibt, 2 , wird diese verwendet. Sie hätten es als (1+) auswerten und weitergeben können, eventuell später die Nummer hinzugefügt. Es ist klarer, wenn Sie nicht die Infix-Syntax von + haben. Es hätte (+) 1 2 geschrieben werden können, also wird diese Anweisung zuerst (+) 1 , was vom Typ ist (vereinfacht! Ich werde hier nicht die Klasse Num-Klasse einführen) Int -> Int . Also (+) 1 (oder (1+) ) ist nur eine weitere Funktion, auf die Sie einen Wert anwenden können. An diesem Punkt kann das Ergebnis zu 3 rechnen.

So sieht das in der Praxis aus: (Ignoriere den (Num a) => -Teil hier, wenn er dich verwirrt, es ist ein Konzept, von dem du später mehr erfährst. Ersetze einfach die a -Typen hier durch Int , wenn du willst, und Ignoriere den (Num a) => Teil komplett.)

%Vor%

Und zu Ihrer zweiten Frage: Sie definieren abgeleitete Typen NICHT ALLE. Sie werden "abgeleitet" genannt, weil der Compiler / Interpreter die Typen selbst "leitet" (liest: berechnet), ohne sie explizit zu benennen.

Über die foldX Unterschiede: Sie alle machen genau dasselbe: Reduzieren Sie eine Liste auf einen einzelnen Wert. Der Unterschied zwischen den Funktionen ist lediglich die interne Definition. foldl faltet die Liste von links und foldr macht sie rechts.

Um eine Liste zusammenzufassen, können Sie alle so verwenden ...

%Vor%

Sie sehen, mit Ausnahme von foldl1, liefern Sie eine Funktion zum Falten, einen Startwert (Akkumulator) und eine Liste zum Falten. fold1 hat einen eigenen Akku, du gibst ihn nicht selbst.

In der Praxis sollten Sie besser foldr verwenden, da es im Gegensatz zu fold für BIG-Listen geeignet ist, ohne aufgrund eines Stack-Überlaufs abzustürzen (tee, hee). Und Ausnahme von dieser Regel ist foldl' (beachte das "'"!) In Data.Map - es ist eine strikte Faltung, die auch für große Listen geeignet ist.

Wenn Sie Funktionen ihre Typen selbst geben möchten (wenn Sie sie geschrieben haben), betrachten Sie dieses Beispiel:

%Vor%

Oder auf haskellische Weise

%Vor%

In der ersten Zeile der beiden Beispiele ( double :: Int -> Int ) suchen Sie. So können Sie den Compiler oder Interpreter zwingen, die Funktion zu identifizieren - Sie können sie dann weglassen, und der Compiler / Interpreter wird sie herausfinden (und manchmal sogar besser, als man zuerst denken würde)

    
LukeN 11.05.2010, 19:06
quelle
3

Funktionen in Haskell verwenden eine Notation namens Curry. Eine Definition wie

%Vor%

bedeutet, dass plus eine Funktion ist, die zwei Int s akzeptiert und einen Wert vom Typ ganz rechts zurückgibt (der wiederum Int ist). Genauer gesagt bedeutet currying, dass Sie mit teilweise angewandten Funktionen arbeiten, die Ihnen erlauben, Code wie

zu schreiben %Vor%

Kleinbuchstaben in Typensignaturen weisen auf eine sogenannte -Typ-Variable hin, die als Platzhalter für jeden möglichen Typ angesehen werden kann.

Die Definition

%Vor%

bedeutet also Für alle Typen a und b , map nimmt eine Funktion von a bis b und eine Liste von a , von denen es eine Liste von erzeugt b s .

Nehmen wir ein Beispiel

%Vor%

Wir sehen, die angegebene Liste ist eine Liste von Int , also a = Int . Darüber hinaus sollte map show auf jedes Element anwenden, was ein String zurückgibt. Da show auf den Typ a -> b der Definition passen muss, können wir folgern, dass b = String .

Unser Ausdruck gibt ein [b] zurück, also ist das Ergebnis ein [String] .

Um solche Signaturen noch deutlicher zu machen, können wir sie mit einer ausführlicheren Syntax schreiben

%Vor%

Dies gibt nun explizit an, dass map mit allen Typen a und b arbeiten soll.

Für die Falten werfen Sie einen Blick auf ihre Definitionen . Angesichts des obigen Wissens sollten die Typ-Signaturen selbsterklärend sein, wenn klar ist, was die Funktionen tun sollen.

Beispiel: factorial n = foldl (*) 1 [1..n]

Die Signatur von foldr map wird.

%Vor%

Sie können diese auch erhalten, indem Sie einfach :t foldr map in den Haskell-Interpreter (GHCi) eingeben.

    
Dario 11.05.2010 19:08
quelle
1

Die Kleinbuchstaben in den Typdeklarationen sind Typ Variablen . Das heißt, wenn ein a vorhanden ist, muss dies derselbe Typ sein.

Klammerausdrücke (zB (a -> b) ) Teilausdrücke neigen dazu, zu bedeuten, dass die Funktion eine Funktion als einen ihrer Parameter akzeptiert.

Rechteckige Klammern zeigen eine Liste von etwas an.

So akzeptiert map diese Argumente:

  • (a -> b) Eine Funktion, die ein Argument
  • akzeptiert
  • [a] eine Liste, in der die erste Argumentfunktion auf
  • wirken kann

und erzeugt [b] eine Liste als Ergebnis.

Wir können (.) auf die gleiche Weise dekodieren:

  • (a -> b) ist eine Funktion, die ein einzelnes Argument
  • akzeptiert
  • (c -> a) ist eine Funktion, die den gleichen Typ wie das Argument der ersten Funktion
  • erzeugt
  • c ein Argument des gleichen Typs, das die zweite Funktion verwendet

Haskell kann jedoch eine nette Sache machen, und das heißt, wenn Sie ein Argument weglassen, gibt es eine Funktion zurück, die die verbleibende Signatur hat. Daher wird (.) häufig verwendet, um Funktionen zu ketten.

Nehmen wir an, wir haben 2 Funktionen ord :: Char -> Int , die ein Zeichen annehmen und dessen Integer-Repräsentation und add32 :: Int -> Int zurückgeben, was seinem Argument 32 hinzufügt.

Wenn wir upcaseVal = add32 . ord schreiben, hat die Funktion upcaseVal jetzt die Signatur upcaseVal :: Char -> Int , weil die Funktion (.) sie mit c -> b belassen hätte und wir sie in der Funktionsdefinition ersetzen würden.

Also müsste foldr map so sein:

  1. Das erste Argument von foldr ist eine Funktion, die 2 Argumente akzeptiert, die map ist.
  2. Da die Ausgabe der Funktionen vom selben Typ sein muss wie das zweite Argument ( (a -> b -> b ), sehen wir das in map a = b

Somit ist der letzte Typ:

%Vor%

Aber ich denke, das muss Sie nicht so sehr stören, da der Compiler die meiste Zeit damit beschäftigt ist.

    
Jakub Hampl 11.05.2010 19:06
quelle
1

In Haskell ist eine Kleinschreibung eine Typvariable. Während Char nur Char , a oder b bedeutet, könnte Char oder Bool oder Integer oder ein anderer Typ bedeuten. Der Schlüssel ist, dass alle a s vom selben Typ sind. Und alle b s sind vom selben Typ.

Zum Beispiel verwendet map als erstes Argument eine Funktion, die eine Variable vom Typ a und eine andere vom Typ b zurückgibt. Dann gibt map eine neue Funktion zurück, die eine Liste vom Typ a übernimmt. Diese neue Funktion gibt eine Liste vom Typ b zurück.

Angenommen, Sie hätten eine Funktion increment , die einen Wert zu einer Ganzzahl hinzufügt. map increment [1,2,3] würde letztendlich eine Liste [2,3,4] zurückgeben.

Die frühere Definition von map mit den ersetzten Typvariablen würde wie folgt aussehen:

map :: increment -> [1,2,3] -> [2,3,4]

und increment , BTW, würden wie

aussehen

increment :: Integer -> Integer

Der Grund, warum es lustig geschrieben ist, ist, dass Sie Funktionen teilweise anwenden können. Hier werde ich eine neue Funktion basierend auf der teilweisen Anwendung der Karte machen:

%Vor%

Dann könnten Sie

verwenden %Vor%

würde das gleiche Ergebnis erzeugen

    
Fletcher Moore 11.05.2010 19:04
quelle