EMZETT.
Login

Kurz: Statt null gibt es Maybe a: entweder Just wert oder Nothing. Der Compiler zwingt dich, beide Fälle zu behandeln.

Teil des Kurses Haskell

Kapitel 6 von 9 im Kurs Haskell. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Maybe: Werte, die fehlen können

Statt null gibt es Maybe a: entweder Just wert oder Nothing. Der Compiler zwingt dich, beide Fälle zu behandeln.

import Data.Maybe (fromMaybe, mapMaybe, catMaybes, isJust, fromJust, listToMaybe, maybe)
import Text.Read (readMaybe)
import qualified Data.Map as M
 
sicherDiv :: Int -> Int -> Maybe Int
sicherDiv _ 0 = Nothing
sicherDiv a b = Just (a `div` b)
 
main :: IO ()
main = do
  print (sicherDiv 10 2, sicherDiv 1 0)
  print (fromMaybe 0 (sicherDiv 1 0))
  print (maybe "nichts" show (sicherDiv 9 3))
  print (readMaybe "42" :: Maybe Int, readMaybe "x" :: Maybe Int, readMaybe "3.5" :: Maybe Double)
  print (mapMaybe (\s -> readMaybe s :: Maybe Int) ["1", "x", "3"])
  print (catMaybes [Just 1, Nothing, Just 3])
  print (lookup "b" [("a", 1), ("b", 2)], M.lookup 5 (M.fromList [(1, "x")]))
  print (listToMaybe [9, 8], listToMaybe ([] :: [Int]))
  case sicherDiv 20 4 of
    Nothing -> putStrLn "Fehler"
    Just n -> putStrLn ("Ergebnis " ++ show n)
  print (fmap (+ 1) (Just 5), fmap (+ 1) Nothing)
  print ((+) <$> Just 3 <*> Just 4, (+) <$> Just 3 <*> Nothing)
  print (Just 5 >>= \x -> sicherDiv 100 x >>= \y -> Just (y + 1))
  print (isJust (Just 1), (<|>) Nothing (Just 2))
  where (<|>) a b = maybe b Just a

Ausgabe:

(Just 5,Nothing)
0
"3"
(Just 42,Nothing,Just 3.5)
[1,3]
[1,3]
(Just 2,Nothing)
(Just 9,Nothing)
Ergebnis 5
(Just 6,Nothing)
(Just 7,Nothing)
Just 21
(True,Just 2)

Either: Erfolg oder Fehler mit Grund

Either e a: Left fehler oder Right wert. Konvention: Right ist “richtig”.

import Data.Char (isDigit)
 
data Fehler = Leer | KeineZahl String | ZuGross Int deriving (Show, Eq)
 
parseAlter :: String -> Either Fehler Int
parseAlter "" = Left Leer
parseAlter s
  | not (all isDigit s) = Left (KeineZahl s)
  | n > 150 = Left (ZuGross n)
  | otherwise = Right n
  where n = read s
 
pruefe :: Int -> Either Fehler String
pruefe a = Right (if a >= 18 then "volljährig" else "minderjährig")
 
main :: IO ()
main = do
  print (map parseAlter ["17", "", "abc", "200"])
  print (parseAlter "30" >>= pruefe)
  print (parseAlter "x" >>= pruefe)
  print (fmap (* 2) (parseAlter "21"))
  print (either show (\a -> "Alter " ++ show a) (parseAlter "12"))
  print (traverse parseAlter ["1", "2", "3"])
  print (traverse parseAlter ["1", "x", "3"])
  let ergebnis = do                       -- do-Notation für Either
        a <- parseAlter "40"
        b <- parseAlter "2"
        Right (a + b)
  print ergebnis

Ausgabe:

[Right 17,Left Leer,Left (KeineZahl "abc"),Left (ZuGross 200)]
Right "vollj\228hrig"
Left (KeineZahl "x")
Right 42
"Alter 12"
Right [1,2,3]
Left (KeineZahl "x")
Right 42

Das Ergebnis Left bricht die Kette ab (wie eine Exception), ohne Ausnahmen zu werfen: Fehler sind Werte im Typ.

Functor, Applicative, Monad

Alle bisherigen Typen (Maybe, Either, Listen, IO) gehören zu denselben Typklassen:

KlasseFunktionIdee
Functorfmap f x bzw. f <$> xFunktion in den Container anwenden
Applicativepure x, f <*> xFunktionen im Container anwenden, kombinieren
Monadx >>= fAbläufe verketten, bei denen jeder Schritt vom vorigen abhängt
main :: IO ()
main = do
  print (fmap (* 2) [1, 2, 3], (+ 1) <$> Just 1, fmap length (Right "abc" :: Either Int String))
  print (pure 5 :: [Int], pure 5 :: Maybe Int)
  print ((+) <$> [1, 2] <*> [10, 20])               -- alle Kombinationen
  print ((,) <$> Just 'a' <*> Just True)
  print ([1, 2, 3] >>= \x -> [x, x * 10])
  print (do { x <- [1, 2]; y <- "ab"; return (x, y) })
  print (sequence [Just 1, Just 2], sequence [Just 1, Nothing])
  print (mapM (\x -> if x > 0 then Just x else Nothing) [1, 2, 3])
  print (zipWith3 (\a b c -> a + b + c) [1, 2] [10, 20] [100, 200])
  print (fmap (+ 1) (3, 4), fst <$> Just (1, 2))
  print (concat <$> mapM (\x -> Just [x, x]) [1, 2])

Ausgabe:

([2,4,6],Just 2,Right 3)
([5],Just 5)
[11,21,12,22]
Just ('a',True)
[1,10,2,20,3,30]
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
(Just [1,2],Nothing)
Just [1,2,3]
[111,222]
((3,5),Just 1)
Just [1,1,2,2]

Merke

  • Maybe a (Just/Nothing) statt null, Either e a (Left/Right) für Fehler mit Grund
  • fromMaybe, maybe, either, readMaybe, mapMaybe
  • fmap/<$>, <*>, >>=: Funktionen über Container und Abläufe
  • do-Notation verkettet Maybe/Either/Listen/IO elegant
  • traverse/mapM/sequence drehen “Liste von Maybe” zu “Maybe von Liste”

Übungsaufgabe

Schreibe sicherWurzel :: Double -> Maybe Double und verkette zwei Aufrufe mit >>=.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Eigene Typen und Typklassen

Weiter: IO, do-Notation und Zustand

Alle Kapitel: Haskell im Überblick