Opake Typen sind eine Kapselungstechnik in Elm.
Manche nennen das auch Informationsverbergung oder das Verbergen von Implementierungsdetails.
Sie werden außerdem häufig eingesetzt, um Validierung zu erzwingen, und zwar mit dem Muster Parse, dont validate.
Zum Beispiel könntest du einen opaken Typ EmailAddress haben, der nur gültige E-Mail-Adressen enthalten kann (im Gegensatz zu einem String, der alles enthalten kann).
Benutzerdefinierte Typen haben eine oder mehrere Varianten, und der Compiler erzeugt automatisch eine Funktion, um jede Variante zu erstellen, und zwar anhand des Namens der Variante. Das ist die einzige Möglichkeit, benutzerdefinierte Typen zu erstellen. Der Compiler erlaubt außerdem Destrukturierung für diese Varianten.
type Maybe a
= Nothing --> Variant implies a 'Nothing' function, with type 'Maybe a', and destructuring of 'Nothing'
| Just a --> Variant implies a 'Just' function, with type 'a -> Maybe a', and destructuring of 'Just _'
Standardmäßig ist all das intern für die Datei bzw. das Modul, und mit dem Schlüsselwort exposing wählen wir aus, was exportiert wird.
Um einen opaken Typ zu erstellen, entscheidest du dich einfach dafür, die Varianten nicht zu exportieren.
Wenn du das tust, musst du fast immer eine andere Möglichkeit bereitstellen, die Varianten zu erstellen und auszulesen, indem du eigene Funktionen exponierst. Diese eigenen Funktionen erlauben es dir, beim Erstellen und Auslesen der Varianten eine gewisse Logik anzuwenden. Das verbirgt die internen Implementierungsdetails des Moduls und bedeutet, dass du die Implementierungsdetails beliebig ändern kannst, solange die exponierten Funktionen gleich bleiben.
Der wohl häufigste Einsatz von opaken Typen ist das Erzwingen von Validierung mit dem Muster Parse, dont validate.
Bei diesem Muster exponierst du eine Funktion, die eine Eingabe mit weniger Struktur bzw. Typsicherheit entgegennimmt, als du möchtest, zum Beispiel einen String, und diese dann in einen stärker strukturierten Typ wie EmailAddress parst. Dabei gibst du entweder den Typ zurück oder einen Fehler (meist Maybe oder Result).
Im folgenden Code zum Beispiel können externe Module einen EmailAddress-Typ nur mit der Funktion parseEmailAddress erstellen. Wenn du also mit dem Typ EmailAddress arbeitest, kannst du sicher sein, dass er gültig ist.
module EmailAddress exposing ( EmailAddress, parseEmailAddress, getEmailAddress )
-- The `EmailAddress` custom type has one variant, also called `EmailAddress`
-- When a custom type has one variant, it is idiomatic to use the same name for both.
type EmailAddress =
EmailAddress String
-- create an EmailAddress variant
parseEmailAddress: String -> Maybe EmailAddress
parseEmailAddress candidateEmailAddress =
if isValidEmailAddress candidateEmailAddress then
Just (EmailAddress candidateEmailAddress)
else
Nothing
-- get an EmailAddress variant
getEmailAddress: EmailAddress -> String
getEmailAddress ( EmailAddress emailAdress ) =
emailAdress
isValidEmailAddress: String -> bool
-- ...
In dieser Übung schreibst du eine generische (und magische!) SecureTreasureChest, um einen Schatz zu verwahren.
Aufmerksamen Lernenden wird aufgefallen sein, dass die vorherige TreasureChest gar nicht so sicher war!
Wenn du die Funktion getTreasure verwendet hast, musstest du das Passwort angeben. Trotzdem konntest du den Typ TreasureChest einfach destrukturieren, um an den Schatz zu kommen, ohne das Passwort zu kennen.
Schlimmer noch: Auf dieselbe Weise konntest du sogar das Passwort auslesen!
Das beheben wir mit einem opaken Typ.
Definiere einen eigenen Typ Password mit einer einzigen Variante.
Die Variante soll einen zugehörigen String-Wert für das Passwort haben.
Du sollst den Typ Password freigeben, aber nicht die Variante.
Dadurch wird Password ein opaker Typ, und du kannst die Destrukturierung nicht mehr verwenden, um an das Passwort zu kommen.
Ob ein Typ opak ist oder nicht, lässt sich nicht testen. In Produktionscode würdest du elm-review (ein Werkzeug zur statischen Analyse) verwenden, um das zu erzwingen.
Definiere eine Funktion createPassword mit einer Typdefinition und gib sie frei. Sie nimmt einen String und gibt ein Maybe Password zurück.
Owasp empfiehlt, dass Passwörter mindestens 8 Zeichen lang sein sollten. Also nutzen wir das Muster Parse, don't validate, um diese Invariante durchzusetzen.
Wenn das Passwort kürzer als 8 Zeichen ist, soll createPassword Nothing zurückgeben, andernfalls soll es Just Password zurückgeben.
Du sollst den Typ SecureTreasureChest freigeben, aber nicht die Variante.
Dadurch wird SecureTreasureChest ein opaker Typ, und du kannst die Destrukturierung nicht mehr verwenden, um an das Passwort oder den Schatz zu kommen.
Diese Funktion soll zwei Parameter nehmen und eine Typdefinition haben.
Password (für das Passwort)Die Funktion soll eine SecureTreasureChest zurückgeben.
Gib diese Funktion frei, damit andere Module Instanzen von SecureTreasureChest erstellen können.
Diese Funktion soll zwei Parameter nehmen und eine Typdefinition haben.
String (für das Passwort)SecureTreasureChest
Diese Funktion soll den übergebenen Passwortversuch mit dem Password in der SecureTreasureChest abgleichen.
Die Funktion soll ein Maybe zurückgeben.
Wenn die Passwörter übereinstimmen, gib Just mit dem generischen Wert aus der SecureTreasureChest zurück (dem Schatz!).
Wenn die Passwörter nicht übereinstimmen, gib Nothing zurück.
Gib diese Funktion frei, damit andere Module den Schatz aus einer SecureTreasureChest holen können, solange sie das richtige Passwort angeben.
Melde dich bei Exercism an, um Elm mit 28 Konzepte110 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.