Added Aufgabe 4
This commit is contained in:
parent
0c89ace6c9
commit
aab564e716
29
.ghci
Normal file
29
.ghci
Normal file
@ -0,0 +1,29 @@
|
||||
:def vis \_ -> return $ "GHC.Vis.vis"
|
||||
:def mvis \_ -> return $ "GHC.Vis.mvis"
|
||||
:def svis \_ -> return $ "GHC.Vis.svis"
|
||||
:def view \x -> return $ "GHC.Vis.view (" ++ x ++ ") " ++ show x
|
||||
:def eval \x -> return $ "GHC.Vis.eval \"" ++ x ++ "\""
|
||||
:def switch \_ -> return $ "GHC.Vis.switch"
|
||||
:def update \_ -> return $ "GHC.Vis.update"
|
||||
:def clear \_ -> return $ "GHC.Vis.clear"
|
||||
:def restore \_ -> return $ "GHC.Vis.restore"
|
||||
:def timeBack \_ -> return $ "GHC.Vis.history (+1)"
|
||||
:def timeForward \_ -> return $ "GHC.Vis.history (\\x -> x - 1)"
|
||||
:def export \x -> return $ "GHC.Vis.export " ++ show x
|
||||
:def setDepth \x -> return $ "GHC.Vis.setDepth (" ++ x ++ ")"
|
||||
|
||||
-- Evaluate one step and update values in ghc-vis
|
||||
:def su \x -> return $ ":step " ++ x ++ "\n:update"
|
||||
|
||||
-- Continue to next breakpoint and update values in ghc-vis
|
||||
:def cu \_ -> return $ ":continue\n:update"
|
||||
|
||||
-- Keep evaluating and updating on any key pressed, quit with q
|
||||
:def asu \x -> getChar >>= \c -> case c of { 'q' -> return ""; _ -> return $ ":step " ++ x ++ "\n:update\n:asu" }
|
||||
|
||||
-- :tsu t x
|
||||
-- Evaluate one step of expression x every t seconds until any key is pressed
|
||||
:def tsu \x -> (System.Timeout.timeout (round $ 1000000 * (read . head $ words x :: Double)) getChar) >>= \c -> case c of { Just _ -> return ""; _ -> return $ ":step " ++ (unwords . tail $ words x) ++ "\n:update\n:tsu " ++ (head $ words x) }
|
||||
|
||||
:set -fobject-code
|
||||
|
@ -1,6 +0,0 @@
|
||||
module Main where
|
||||
|
||||
import Aufgabe1
|
||||
|
||||
main :: IO ()
|
||||
main = putStrLn result
|
@ -1,6 +0,0 @@
|
||||
module Main where
|
||||
|
||||
import Aufgabe2
|
||||
|
||||
main :: IO ()
|
||||
main = putStrLn result
|
@ -1,6 +0,0 @@
|
||||
module Main where
|
||||
|
||||
import Aufgabe3
|
||||
|
||||
main :: IO ()
|
||||
main = putStrLn result
|
2
install_dependencies.sh
Normal file
2
install_dependencies.sh
Normal file
@ -0,0 +1,2 @@
|
||||
tar xf /media/remote/ygottschalk/.stack.tar.gz -C $HOME/
|
||||
|
@ -1,27 +0,0 @@
|
||||
module AreaCode (AreaCode,separateAreaCode) where
|
||||
|
||||
import qualified Data.Attoparsec.Text as A
|
||||
import Data.Attoparsec.Combinator
|
||||
import qualified Data.Text as T
|
||||
|
||||
type Number = String
|
||||
type Name = String
|
||||
type AreaCode = String
|
||||
|
||||
{-Area Code Parser-}
|
||||
|
||||
separateAreaCode :: Number -> (AreaCode,Number)
|
||||
separateAreaCode nr =
|
||||
case A.parse parseAreaCode (T.pack nr) of
|
||||
A.Done s ac -> (ac, T.unpack s)
|
||||
_ -> ("",nr)
|
||||
|
||||
parseAreaCode :: A.Parser String
|
||||
parseAreaCode = do
|
||||
many' (A.char ' ')
|
||||
A.char '('
|
||||
ac <- many1 $ A.satisfy (A.inClass "0123456789")
|
||||
A.char ')'
|
||||
many' (A.char ' ')
|
||||
return ac
|
||||
|
@ -1,82 +0,0 @@
|
||||
-- Aufgabe 1
|
||||
-- =========
|
||||
|
||||
module Aufgabe1 where
|
||||
|
||||
-- Functional phone book – Implementieren Sie ein Telefonbuch als Funktion.
|
||||
|
||||
type Number = String
|
||||
type Name = String
|
||||
type Entry = [Number]
|
||||
newtype PhoneBook = PB (Name -> Entry)
|
||||
|
||||
|
||||
-- Implementieren Sie die Funktion `usePB`, welche aus einem `PhoneBook`
|
||||
-- mit einem `Name` den zugehörigen `Entry` findet`
|
||||
|
||||
|
||||
usePB :: PhoneBook -> Name -> Entry
|
||||
usePB = undefined
|
||||
|
||||
|
||||
|
||||
-- Implementieren Sie eine `Monoid` Instanz für `PhoneBook`, um zu garantieren,
|
||||
-- dass `PhoneBook` ein leeres Element hat und eine Verkettungsfunktion, die
|
||||
-- zwei `PhoneBook`s in eines zusammenführt.
|
||||
|
||||
|
||||
instance Monoid PhoneBook where
|
||||
mempty = undefined
|
||||
mappend = undefined
|
||||
|
||||
|
||||
|
||||
-- Implementieren Sie eine Funktion `addEntry`, welche einem `PhoneBook` eine
|
||||
-- `Name` zu `Number` Verknüpfung hinzufügt, also für einen gegebenen Namen und
|
||||
-- eine Nummer einen Eintrag im Telefonbuch erstellt.
|
||||
|
||||
|
||||
addEntry :: Name -> Number -> PhoneBook -> PhoneBook
|
||||
addEntry = undefined
|
||||
|
||||
|
||||
|
||||
-- Implementieren Sie eine Funktion `delEntry`, die alle Nummern aus dem `PhoneBook`
|
||||
-- entfernt, die mit dem gegebenen `Name` assoziiert sind.
|
||||
-- Hinweis: "Entfernt" heißt streng genommen nur, dass die Nummern nicht mehr aus
|
||||
-- dem resultierenden Telefonbuch herausgesucht werden können
|
||||
|
||||
|
||||
delEntry :: Name -> PhoneBook -> PhoneBook
|
||||
delEntry = undefined
|
||||
|
||||
|
||||
-- Implementieren Sie eine Funktion `findInMult`, welche alle Einträge aus einer
|
||||
-- Liste von `PhoneBook`s sucht
|
||||
|
||||
|
||||
findInMult :: [PhoneBook] -> Name -> Entry
|
||||
findInMult = undefined
|
||||
|
||||
|
||||
result = "Wie war noch mal die Nummer von diesem Alonzo Church? Vielleicht kann der mir weiterhelfen.. \n"
|
||||
++ (show $ findInMult [pb2,pb3] "Alonzo Church") ++ "\n"
|
||||
where pb1 = addEntry "Alonzo Church" "(0123) 73645362" mempty
|
||||
pb2 = delEntry "Alonzo Church" pb1
|
||||
pb3 = addEntry "Haskell Brooks Curry" "(0167) 987761262" (mappend pb1 pb2)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,72 +0,0 @@
|
||||
Aufgabe 1
|
||||
=========
|
||||
|
||||
> module Aufgabe1 where
|
||||
|
||||
Functional phone book – Implementieren Sie ein Telefonbuch als Funktion.
|
||||
|
||||
> type Number = String
|
||||
> type Name = String
|
||||
> type Entry = [Number]
|
||||
> newtype PhoneBook = PB (Name -> Entry)
|
||||
|
||||
|
||||
Implementieren Sie die Funktion `usePB`, welche aus einem `PhoneBook`
|
||||
mit einem `Name` den zugehörigen `Entry` findet`
|
||||
|
||||
|
||||
> usePB :: PhoneBook -> Name -> Entry
|
||||
> usePB = undefined
|
||||
|
||||
|
||||
|
||||
Implementieren Sie eine `Monoid` Instanz für `PhoneBook`, um zu garantieren,
|
||||
dass `PhoneBook` ein leeres Element hat und eine Verkettungsfunktion, die
|
||||
zwei `PhoneBook`s in eines zusammenführt.
|
||||
|
||||
|
||||
> instance Monoid PhoneBook where
|
||||
> mempty = undefined
|
||||
> mappend = undefined
|
||||
|
||||
|
||||
|
||||
Implementieren Sie eine Funktion `addEntry`, welche einem `PhoneBook` eine
|
||||
`Name` zu `Number` Verknüpfung hinzufügt, also für einen gegebenen Namen und
|
||||
eine Nummer einen Eintrag im Telefonbuch erstellt.
|
||||
|
||||
|
||||
> addEntry :: Name -> Number -> PhoneBook -> PhoneBook
|
||||
> addEntry = undefined
|
||||
|
||||
|
||||
|
||||
Implementieren Sie eine Funktion `delEntry`, die alle Nummern aus dem `PhoneBook`
|
||||
entfernt, die mit dem gegebenen `Name` assoziiert sind.
|
||||
Hinweis: "Entfernt" heißt streng genommen nur, dass die Nummern nicht mehr aus
|
||||
dem resultierenden Telefonbuch herausgesucht werden können
|
||||
|
||||
|
||||
> delEntry :: Name -> PhoneBook -> PhoneBook
|
||||
> delEntry = undefined
|
||||
|
||||
|
||||
|
||||
Implementieren Sie eine Funktion `findInMult`, welche alle Einträge aus einer
|
||||
Liste von `PhoneBook`s sucht
|
||||
|
||||
|
||||
> findInMult :: [PhoneBook] -> Name -> Entry
|
||||
> findInMult = undefined
|
||||
|
||||
|
||||
|
||||
> result = "Wie war noch mal die Nummer von diesem Alonzo Church? Vielleicht kann der mir weiterhelfen.. \n"
|
||||
> ++ (show $ findInMult [pb2,pb3] "Alonzo Church") ++ "\n"
|
||||
> where pb1 = addEntry "Alonzo Church" "(0123) 73645362" mempty
|
||||
> pb2 = delEntry "Alonzo Church" pb1
|
||||
> pb3 = addEntry "Haskell Brooks Curry" "(0167) 987761262" (mappend pb1 pb2)
|
||||
|
||||
|
||||
|
||||
|
@ -1,79 +0,0 @@
|
||||
Aufgabe 1
|
||||
=========
|
||||
|
||||
```haskell
|
||||
module Aufgabe1 where
|
||||
```
|
||||
|
||||
Functional phone book – Implementieren Sie ein Telefonbuch als Funktion.
|
||||
|
||||
```haskell
|
||||
type Number = String
|
||||
type Name = String
|
||||
type Entry = [Number]
|
||||
newtype PhoneBook = PB (Name -> Entry)
|
||||
```
|
||||
|
||||
Implementieren Sie die Funktion `usePB`, welche aus einem `PhoneBook`
|
||||
mit einem `Name` den zugehörigen `Entry` findet`
|
||||
|
||||
```haskell
|
||||
usePB :: PhoneBook -> Name -> Entry
|
||||
usePB = undefined
|
||||
```
|
||||
|
||||
|
||||
Implementieren Sie eine `Monoid` Instanz für `PhoneBook`, um zu garantieren,
|
||||
dass `PhoneBook` ein leeres Element hat und eine Verkettungsfunktion, die
|
||||
zwei `PhoneBook`s in eines zusammenführt.
|
||||
|
||||
```haskell
|
||||
instance Monoid PhoneBook where
|
||||
mempty = undefined
|
||||
mappend pb1 pb2 = undefined
|
||||
```
|
||||
|
||||
|
||||
Implementieren Sie eine Funktion `addEntry`, welche einem `PhoneBook` eine
|
||||
`Name` zu `Number` Verknüpfung hinzufügt, also für einen gegebenen Namen und
|
||||
eine Nummer einen Eintrag im Telefonbuch erstellt.
|
||||
|
||||
```haskell
|
||||
addEntry :: Name -> Number -> PhoneBook -> PhoneBook
|
||||
addEntry n nr pb = undefined
|
||||
```
|
||||
|
||||
|
||||
Implementieren Sie eine Funktion `delEntry`, die alle Nummern aus dem `PhoneBook`
|
||||
entfernt, die mit dem gegebenen `Name` assoziiert sind.
|
||||
Hinweis: "Entfernt" heißt streng genommen nur, dass die Nummern nicht mehr aus
|
||||
dem resultierenden Telefonbuch herausgesucht werden können
|
||||
|
||||
```haskell
|
||||
delEntry :: Name -> PhoneBook -> PhoneBook
|
||||
delEntry n pb = undefined
|
||||
```
|
||||
|
||||
|
||||
Implementieren Sie eine Funktion `findInMult`, welche alle Einträge aus einer
|
||||
Liste von `PhoneBook`s sucht
|
||||
|
||||
```haskell
|
||||
findInMult :: [PhoneBook] -> Name -> Entry
|
||||
findInMult = undefined
|
||||
```
|
||||
|
||||
```haskell
|
||||
result = "Wie war noch mal die Nummer von diesem Alonzo Church? Vielleicht kann der mir weiterhelfen.. \n"
|
||||
++ (show $ findInMult [pb2,pb3] "Alonzo Church") ++ "\n"
|
||||
where pb1 = addEntry "Alonzo Church" "(0123) 73645362" mempty
|
||||
pb2 = delEntry "Alonzo Church" pb1
|
||||
pb3 = addEntry "Haskell Brooks Curry" "(0167) 987761262" (mappend pb1 pb2)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
-- Aufgabe 2
|
||||
-- =========
|
||||
|
||||
module Aufgabe2 where
|
||||
|
||||
-- Besser und allgemeiner
|
||||
-- ----------------------
|
||||
|
||||
-- Vereinfachen und verallgemeinern sie folgenden Ausdrücke so weit wie
|
||||
-- möglich. Geben Sie die dadurch entstehenden Typsignaturen und
|
||||
-- Funktionsdefinitionen an. Bedenken Sie, dass wenn sie auf eine Typklasse
|
||||
-- abstrahieren, Sie die gesamten Gesetze der Typklasse benutzen können.
|
||||
|
||||
-- Kann die Funktion nachher mehr als vorher?
|
||||
|
||||
-- *Bonus*: Hat sich an der Laufzeit etwas verändert?
|
||||
|
||||
|
||||
mystery1 :: [[a]] -> [[a]]
|
||||
mystery1 = map (++[])
|
||||
|
||||
mystery2 :: (Int -> Bool)
|
||||
-> Maybe (Either String Int)
|
||||
-> Maybe (Either String Bool)
|
||||
mystery2 f (Just (Right a)) = Just . Right . f $ a
|
||||
mystery2 _ (Just (Left b)) = Just (Left b)
|
||||
mystery2 _ Nothing = Nothing
|
||||
|
||||
mystery3 :: (Eq a) => a -> a -> a -> Bool
|
||||
mystery3 x y z
|
||||
| y == z = True
|
||||
| z == y && y == x = True
|
||||
| x /= z = False
|
||||
| y /= x = False
|
||||
| z /= y || y /= x = True
|
||||
| otherwise = False
|
||||
|
||||
result = "foo?"
|
||||
|
||||
|
||||
|
@ -1,45 +0,0 @@
|
||||
Aufgabe 2
|
||||
=========
|
||||
|
||||
> module Aufgabe2 where
|
||||
|
||||
Besser und allgemeiner
|
||||
----------------------
|
||||
|
||||
Vereinfachen und verallgemeinern sie folgenden Ausdrücke so weit wie
|
||||
möglich. Geben Sie die dadurch entstehenden Typsignaturen und
|
||||
Funktionsdefinitionen an. Bedenken Sie, dass wenn sie auf eine Typklasse
|
||||
abstrahieren, Sie die gesamten Gesetze der Typklasse benutzen können.
|
||||
|
||||
Kann die Funktion nachher mehr als vorher?
|
||||
|
||||
*Bonus*: Hat sich an der Laufzeit etwas verändert?
|
||||
|
||||
|
||||
> mystery1 :: [[a]] -> [[a]]
|
||||
> mystery1 = map (++[])
|
||||
|
||||
|
||||
> mystery2 :: (Int -> Bool)
|
||||
> -> Maybe (Either String Int)
|
||||
> -> Maybe (Either String Bool)
|
||||
> mystery2 f (Just (Right a)) = Just . Right . f $ a
|
||||
> mystery2 _ (Just (Left b)) = Just (Left b)
|
||||
> mystery2 _ Nothing = Nothing
|
||||
|
||||
|
||||
> mystery3 :: (Eq a) => a -> a -> a -> Bool
|
||||
> mystery3 x y z
|
||||
> | y == z = True
|
||||
> | z == y && y == x = True
|
||||
> | x /= z = False
|
||||
> | y /= x = False
|
||||
> | z /= y || y /= x = True
|
||||
> | otherwise = False
|
||||
|
||||
|
||||
|
||||
> result = "foo?"
|
||||
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
Aufgabe 2
|
||||
=========
|
||||
|
||||
```haskell
|
||||
module Aufgabe2 where
|
||||
```
|
||||
|
||||
Besser und allgemeiner
|
||||
----------------------
|
||||
|
||||
Vereinfachen und verallgemeinern sie folgenden Ausdrücke so weit wie
|
||||
möglich. Geben Sie die dadurch entstehenden Typsignaturen und
|
||||
Funktionsdefinitionen an. Bedenken Sie, dass wenn sie auf eine Typklasse
|
||||
abstrahieren, Sie die gesamten Gesetze der Typklasse benutzen können.
|
||||
|
||||
Kann die Funktion nachher mehr als vorher?
|
||||
|
||||
*Bonus*: Hat sich an der Laufzeit etwas verändert?
|
||||
|
||||
```haskell
|
||||
mystery1 :: [[a]] -> [[a]]
|
||||
mystery1 = map (++[])
|
||||
|
||||
|
||||
mystery2 :: (Int -> Bool)
|
||||
-> Maybe (Either String Int)
|
||||
-> Maybe (Either String Bool)
|
||||
mystery2 f (Just (Right a)) = Just . Right . f $ a
|
||||
mystery2 _ (Just (Left b)) = Just (Left b)
|
||||
mystery2 _ Nothing = Nothing
|
||||
|
||||
|
||||
mystery3 :: (Eq a) => a -> a -> a -> Bool
|
||||
mystery3 x y z
|
||||
| y == z = True
|
||||
| z == y && y == x = True
|
||||
| x /= z = False
|
||||
| y /= x = False
|
||||
| z /= y || y /= x = True
|
||||
| otherwise = False
|
||||
|
||||
|
||||
|
||||
result = "foo?"
|
||||
```
|
||||
|
||||
|
147
src/Aufgabe3.hs
147
src/Aufgabe3.hs
@ -1,147 +0,0 @@
|
||||
-- Aufgabe 3
|
||||
-- =========
|
||||
|
||||
module Aufgabe3 where
|
||||
|
||||
import FunPB
|
||||
import DataPB
|
||||
import AreaCode
|
||||
import Data.Char
|
||||
|
||||
-- Functor – ein Container?
|
||||
-- --------------------------
|
||||
|
||||
-- Zur Erinnerung:
|
||||
|
||||
|
||||
-- class Functor f where
|
||||
-- fmap :: (a -> b) -> f a -> f b
|
||||
|
||||
-- Aus der Vorlesung 2 kennen Sie bereits die `Functor`-Instanzen für `[]`, `Identity`
|
||||
-- und einen binären Baum. Außerdem haben Sie gelernt, dass Sie auch für Ihre eigenen
|
||||
-- Typen `Functor`-Instanzen definieren können. Eine Intuition dafür, ob sich für einen
|
||||
-- Typ eine `Functor`-Instanz schreiben lässt, erhalten Sie, indem Sie sich fragen, ob
|
||||
-- der Typ "eine Art Container" ist, auf deren Inhalte sich Funktionen (a -> b) anwenden
|
||||
-- lassen. Was bedeutet dies in Haskell-Syntax? Hierfür ist es dienlich, sich den
|
||||
-- `type constructor` des betreffenden Datentyps anzuschauen. Handelt es sich um einen
|
||||
-- polymorphen Datentyp stehen die Chancen gut, dass es ein `Functor` ist.
|
||||
|
||||
-- data Bool = False | True
|
||||
|
||||
-- data Maybe a = Nothing | Just a
|
||||
|
||||
-- data (,) a b = (a,b)
|
||||
|
||||
-- `Bool` ist offenbar kein `Functor`, denn der Typkonstruktor `Bool` hat keine Parameter –
|
||||
-- hier ist nur Platz für True und False. `Maybe a` dagegen hat einen Parameter, einen freien
|
||||
-- Slot für alles mögliche. Der Tupeltyp `(,) a b` hat sogar zwei Parameter – er kann Dinge von
|
||||
-- zwei verschiedenen Typen enthalten. Hier stellt sich die Frage, für welchen Container die
|
||||
-- Functorinstanz definiert ist. Ähnlich wie Funktionen, lassen sich auch Typkonstruktoren
|
||||
-- partiell anwenden. Für die Instanziierung werden dem Typkonstruktor daher alle bis auf ein
|
||||
-- Parameter übergeben. Dieser letzte, freie Parameter legt dann den Inhalt des "Container"
|
||||
-- fest. Beispiel:
|
||||
|
||||
-- instance Functor ((,) a) where --Die Functor-Instanz ist für (,) a definiert
|
||||
-- fmap f (x,y) = (x, f y) --Also wird über Tupelslot 2 "gemapt"
|
||||
|
||||
-- Implementieren Sie `Functor`-Instanzen für die folgenden Datentypen:
|
||||
|
||||
|
||||
data Vielleicht a = Nichts | Etwas a
|
||||
deriving (Show,Eq)
|
||||
|
||||
instance Functor Vielleicht where
|
||||
fmap = undefined
|
||||
|
||||
|
||||
data Entweder a b = Jenes a | Dieses b
|
||||
deriving (Show,Eq)
|
||||
|
||||
instance Functor (Entweder a) where
|
||||
fmap = undefined
|
||||
|
||||
|
||||
data Konstant a b = Konstant a
|
||||
deriving (Show,Eq)
|
||||
|
||||
instance Functor (Konstant a) where
|
||||
fmap = undefined
|
||||
|
||||
|
||||
-- Achtung: Die "Container"-Metapher hat ihre Grenzen. Betrachten Sie hierzu noch einmal den
|
||||
-- Datentyp Pred a von Zettel 1: `data Pred a = Pred (\a -> Bool)`.
|
||||
|
||||
data Pred a = Pred (a -> Bool)
|
||||
runPred :: Pred a -> (a -> Bool)
|
||||
runPred (Pred p) = p
|
||||
|
||||
-- `Pred a` macht den Anschein als handele es sich auch hier um einen Container mit Inhalt a.
|
||||
-- Trotzdem lässt sich `Functor` hierfür nicht instanziieren. Der Unterschied liegt darin,
|
||||
-- dass der Typaramter `a` als Input und nicht als Ergebnis in der vom Konstruktor `Pred`
|
||||
-- "eingepackten" Berechnung auftaucht. Allerdings lässt sich hier auf eine sehr ähnliche
|
||||
-- Eigenschaft abstrahieren, die wir für's Erste `InputFunctor` nennen wollen.
|
||||
|
||||
class InputFunctor f where
|
||||
inputmap :: (a -> b) -> f b -> f a
|
||||
|
||||
-- Schreiben Sie eine `InputFunctor`-Instanz für `Pred a`.
|
||||
|
||||
instance InputFunctor Pred where
|
||||
inputmap = undefined
|
||||
|
||||
-- Hiermit lässt sich nun bequem die folgende Funktion definieren, welche aus einem
|
||||
-- `Pred Int` ein `Pred String` macht, das prüft, ob ein Eingabestring wenigstens die Länge 5 hat.
|
||||
|
||||
atLeast5 :: Pred Int
|
||||
atLeast5 = Pred $ (\x -> x>=5)
|
||||
|
||||
atLeast5Char :: Pred String
|
||||
atLeast5Char = inputmap length atLeast5
|
||||
|
||||
-- Functorial phone book
|
||||
-- ---------------------
|
||||
|
||||
-- Jetzt noch einmal zurück zu PhoneBook aus Aufgabe 1.
|
||||
|
||||
type Number = String
|
||||
type Name = String
|
||||
type Entry = [Number]
|
||||
|
||||
newtype PhoneBook = PB (Name -> Entry)
|
||||
|
||||
-- `PhoneBook` hat keinen Parameter, aber die allgemeinere Version `FunPB` hat sogar zwei:
|
||||
-- data FunPB a b = FunPB (a -> (a,[b]))
|
||||
|
||||
-- Beachten Sie, dass sich auch der Rückgabetyp ein wenig unterscheidet. Die Idee ist,
|
||||
-- dass FunPB zusätzlich zu den assoziierten `Number`s (angenommen `b` ist `Number`) auch
|
||||
-- den gesuchten `Name` (angenommen `a` ist `Name`) zurückgibt.
|
||||
|
||||
-- Implementieren Sie eine `Functor`-Instanz für `FunPB`.
|
||||
-- Hinweis: Sie können benutzen, dass für `[]`, `((,) a)` und sogar für den "function arrow"
|
||||
-- `((->) a)` bereits `Functor`-Instanzen in der [`GHC.Base`](https://hackage.haskell.org/package/base "GHC.Base") existieren.
|
||||
|
||||
instance Functor (FunPB a) where
|
||||
fmap = undefined
|
||||
|
||||
-- Die Functor-Instanz erlaubt uns nun die Funktionen
|
||||
-- `separateAreaCode :: Number -> (AreaCode,Number)` und `snd` zu verwenden, um ein
|
||||
-- TelefonBuch mit separiertem bzw. ganz ohne AreaCode zu erhalten.
|
||||
|
||||
areaCodeFunPB :: FunPB Name String -> FunPB Name (AreaCode,Number)
|
||||
areaCodeFunPB = fmap separateAreaCode
|
||||
|
||||
withoutAreaCodeFunPB :: FunPB Name Number -> FunPB Name Number
|
||||
withoutAreaCodeFunPB = fmap (snd.separateAreaCode)
|
||||
|
||||
|
||||
result = "Suche \"Paula\" in (FunPB Name Number): \n"
|
||||
++ (show $ runFunPB (dataToFunPB simpleData) "Paula") ++ "\n"
|
||||
++ "Suche \"Paula\" in (FunPB Name (AreaCode,Number)): \n"
|
||||
++ (show $ runFunPB (areaCodeFunPB $ dataToFunPB simpleData) "Paula") ++ "\n"
|
||||
++ "Suche \"Paula\" in (FunPB Name Number) ohne Vorwahl: \n"
|
||||
++ (show $ runFunPB (withoutAreaCodeFunPB $ dataToFunPB simpleData) "Paula")
|
||||
|
||||
|
||||
|
||||
|
||||
|
168
src/Aufgabe3.lhs
168
src/Aufgabe3.lhs
@ -1,168 +0,0 @@
|
||||
Aufgabe 3
|
||||
=========
|
||||
|
||||
> module Aufgabe3 where
|
||||
|
||||
> import FunPB
|
||||
> import DataPB
|
||||
> import AreaCode
|
||||
> import Data.Char
|
||||
|
||||
`Functor` – ein Container?
|
||||
--------------------------
|
||||
|
||||
Zur Erinnerung:
|
||||
|
||||
class Functor f where
|
||||
fmap :: (a -> b) -> f a -> f b
|
||||
|
||||
Aus der Vorlesung 2 kennen Sie bereits die `Functor`-Instanzen für `[]`, `Identity`
|
||||
und einen binären Baum. Außerdem haben Sie gelernt, dass Sie auch für Ihre eigenen
|
||||
Typen `Functor`-Instanzen definieren können. Eine Intuition dafür, ob sich für einen
|
||||
Typ eine `Functor`-Instanz schreiben lässt, erhalten Sie, indem Sie sich fragen, ob
|
||||
der Typ "eine Art Container" ist, auf deren Inhalte sich Funktionen (a -> b) anwenden
|
||||
lassen. Was bedeutet dies in Haskell-Syntax? Hierfür ist es dienlich, sich den
|
||||
`type constructor` des betreffenden Datentyps anzuschauen. Handelt es sich um einen
|
||||
polymorphen Datentyp stehen die Chancen gut, dass es ein `Functor` ist.
|
||||
|
||||
|
||||
data Bool = False | True
|
||||
|
||||
data Maybe a = Nothing | Just a
|
||||
|
||||
data (,) a b = (a,b)
|
||||
|
||||
|
||||
`Bool` ist offenbar kein `Functor`, denn der Typkonstruktor `Bool` hat keine Parameter –
|
||||
hier ist nur Platz für True und False. `Maybe a` dagegen hat einen Parameter, einen freien
|
||||
Slot für alles mögliche. Der Tupeltyp `(,) a b` hat sogar zwei Parameter – er kann Dinge von
|
||||
zwei verschiedenen Typen enthalten. Hier stellt sich die Frage, für welchen Container die
|
||||
Functorinstanz definiert ist. Ähnlich wie Funktionen, lassen sich auch Typkonstruktoren
|
||||
partiell anwenden. Für die Instanziierung werden dem Typkonstruktor daher alle bis auf ein
|
||||
Parameter übergeben. Dieser letzte, freie Parameter legt dann den Inhalt des "Container"
|
||||
fest. Beispiel:
|
||||
|
||||
|
||||
instance Functor ((,) a) where --Die Functor-Instanz ist für (,) a definiert
|
||||
fmap f (x,y) = (x, f y) --Also wird über Tupelslot 2 "gemapt"
|
||||
|
||||
|
||||
Implementieren Sie `Functor`-Instanzen für die folgenden Datentypen:
|
||||
|
||||
|
||||
> data Vielleicht a = Nichts | Etwas a
|
||||
> deriving (Show,Eq)
|
||||
|
||||
> instance Functor Vielleicht where
|
||||
> fmap = undefined
|
||||
|
||||
|
||||
> data Entweder a b = Jenes a | Dieses b
|
||||
> deriving (Show,Eq)
|
||||
|
||||
> instance Functor (Entweder a) where
|
||||
> fmap = undefined
|
||||
|
||||
|
||||
> data Konstant a b = Konstant a
|
||||
> deriving (Show,Eq)
|
||||
|
||||
> instance Functor (Konstant a) where
|
||||
> fmap = undefined
|
||||
|
||||
|
||||
|
||||
Achtung: Die "Container"-Metapher hat ihre Grenzen. Betrachten Sie hierzu noch einmal den
|
||||
Datentyp Pred a von Zettel 1: `data Pred a = Pred (\a -> Bool)`.
|
||||
|
||||
|
||||
> data Pred a = Pred (a -> Bool)
|
||||
> runPred :: Pred a -> (a -> Bool)
|
||||
> runPred (Pred p) = p
|
||||
|
||||
|
||||
`Pred a` macht den Anschein als handele es sich auch hier um einen Container mit Inhalt a.
|
||||
Trotzdem lässt sich `Functor` hierfür nicht instanziieren. Der Unterschied liegt darin,
|
||||
dass der Typaramter `a` als Input und nicht als Ergebnis in der vom Konstruktor `Pred`
|
||||
"eingepackten" Berechnung auftaucht. Allerdings lässt sich hier auf eine sehr ähnliche
|
||||
Eigenschaft abstrahieren, die wir für's Erste `InputFunctor` nennen wollen.
|
||||
```
|
||||
|
||||
> class InputFunctor f where
|
||||
> inputmap :: (a -> b) -> f b -> f a
|
||||
|
||||
|
||||
Schreiben Sie eine `InputFunctor`-Instanz für `Pred a`.
|
||||
|
||||
|
||||
> instance InputFunctor Pred where
|
||||
> inputmap = undefined
|
||||
|
||||
|
||||
Hiermit lässt sich nun bequem die folgende Funktion definieren, welche aus einem
|
||||
`Pred Int` ein `Pred String` macht, das prüft, ob ein Eingabestring wenigstens die Länge 5 hat.
|
||||
|
||||
|
||||
> atLeast5 :: Pred Int
|
||||
> atLeast5 = Pred $ (\x -> x>=5)
|
||||
|
||||
> atLeast5Char :: Pred String
|
||||
> atLeast5Char = inputmap length atLeast5
|
||||
|
||||
|
||||
Functorial phone book
|
||||
---------------------
|
||||
|
||||
Jetzt noch einmal zurück zu PhoneBook aus Aufgabe 1.
|
||||
|
||||
|
||||
> type Number = String
|
||||
> type Name = String
|
||||
> type Entry = [Number]
|
||||
|
||||
> newtype PhoneBook = PB (Name -> Entry)
|
||||
|
||||
|
||||
`PhoneBook` hat keinen Parameter, aber die allgemeinere Version `FunPB` hat sogar zwei:
|
||||
|
||||
|
||||
data FunPB a b = FunPB (a -> (a,[b]))
|
||||
|
||||
|
||||
Beachten Sie, dass sich auch der Rückgabetyp ein wenig unterscheidet. Die Idee ist,
|
||||
dass FunPB zusätzlich zu den assoziierten `Number`s (angenommen `b` ist `Number`) auch
|
||||
den gesuchten `Name` (angenommen `a` ist `Name`) zurückgibt.
|
||||
|
||||
Implementieren Sie eine `Functor`-Instanz für `FunPB`.
|
||||
Hinweis: Sie können benutzen, dass für `[]`, `((,) a)` und sogar für den "function arrow"
|
||||
`((->) a)` bereits `Functor`-Instanzen in der [`GHC.Base`](https://hackage.haskell.org/package/base "GHC.Base") existieren.
|
||||
|
||||
|
||||
> instance Functor (FunPB a) where
|
||||
> fmap = undefined
|
||||
|
||||
|
||||
Die Functor-Instanz erlaubt uns nun die Funktionen
|
||||
`separateAreaCode :: Number -> (AreaCode,Number)` und `snd` zu verwenden, um ein
|
||||
TelefonBuch mit separiertem bzw. ganz ohne AreaCode zu erhalten.
|
||||
|
||||
|
||||
> areaCodeFunPB :: FunPB Name String -> FunPB Name (AreaCode,Number)
|
||||
> areaCodeFunPB = fmap separateAreaCode
|
||||
|
||||
> withoutAreaCodeFunPB :: FunPB Name Number -> FunPB Name Number
|
||||
> withoutAreaCodeFunPB = fmap (snd.separateAreaCode)
|
||||
|
||||
|
||||
|
||||
|
||||
> result = "Suche \"Paula\" in (FunPB Name Number): \n"
|
||||
> ++ (show $ runFunPB (dataToFunPB simpleData) "Paula") ++ "\n"
|
||||
> ++ "Suche \"Paula\" in (FunPB Name (AreaCode,Number)): \n"
|
||||
> ++ (show $ runFunPB (areaCodeFunPB $ dataToFunPB simpleData) "Paula") ++ "\n"
|
||||
> ++ "Suche \"Paula\" in (FunPB Name Number) ohne Vorwahl: \n"
|
||||
> ++ (show $ runFunPB (withoutAreaCodeFunPB $ dataToFunPB simpleData) "Paula")
|
||||
|
||||
|
||||
|
||||
|
171
src/Aufgabe3.md
171
src/Aufgabe3.md
@ -1,171 +0,0 @@
|
||||
Aufgabe 3
|
||||
=========
|
||||
|
||||
```haskell
|
||||
module Aufgabe3 where
|
||||
|
||||
import FunPB
|
||||
import DataPB
|
||||
import AreaCode
|
||||
import Data.Char
|
||||
```
|
||||
|
||||
`Functor` – ein Container?
|
||||
--------------------------
|
||||
|
||||
Zur Erinnerung:
|
||||
|
||||
```haskell
|
||||
class Functor f where
|
||||
fmap :: (a -> b) -> f a -> f b
|
||||
```
|
||||
|
||||
Aus der Vorlesung 2 kennen Sie bereits die `Functor`-Instanzen für `[]`, `Identity`
|
||||
und einen binären Baum. Außerdem haben Sie gelernt, dass Sie auch für Ihre eigenen
|
||||
Typen `Functor`-Instanzen definieren können. Eine Intuition dafür, ob sich für einen
|
||||
Typ eine `Functor`-Instanz schreiben lässt, erhalten Sie, indem Sie sich fragen, ob
|
||||
der Typ "eine Art Container" ist, auf deren Inhalte sich Funktionen (a -> b) anwenden
|
||||
lassen. Was bedeutet dies in Haskell-Syntax? Hierfür ist es dienlich, sich den
|
||||
`type constructor` des betreffenden Datentyps anzuschauen. Handelt es sich um einen
|
||||
polymorphen Datentyp stehen die Chancen gut, dass es ein `Functor` ist.
|
||||
|
||||
```haskell
|
||||
data Bool = False | True
|
||||
|
||||
data Maybe a = Nothing | Just a
|
||||
|
||||
data (,) a b = (a,b)
|
||||
```
|
||||
|
||||
`Bool` ist offenbar kein `Functor`, denn der Typkonstruktor `Bool` hat keine Parameter –
|
||||
hier ist nur Platz für True und False. `Maybe a` dagegen hat einen Parameter, einen freien
|
||||
Slot für alles mögliche. Der Tupeltyp `(,) a b` hat sogar zwei Parameter – er kann Dinge von
|
||||
zwei verschiedenen Typen enthalten. Hier stellt sich die Frage, für welchen Container die
|
||||
Functorinstanz definiert ist. Ähnlich wie Funktionen, lassen sich auch Typkonstruktoren
|
||||
partiell anwenden. Für die Instanziierung werden dem Typkonstruktor daher alle bis auf ein
|
||||
Parameter übergeben. Dieser letzte, freie Parameter legt dann den Inhalt des "Container"
|
||||
fest. Beispiel:
|
||||
|
||||
```haskell
|
||||
instance Functor ((,) a) where --Die Functor-Instanz ist für (,) a definiert
|
||||
fmap f (x,y) = (x, f y) --Also wird über Tupelslot 2 "gemapt"
|
||||
```
|
||||
|
||||
Implementieren Sie `Functor`-Instanzen für die folgenden Datentypen:
|
||||
|
||||
```haskell
|
||||
data Vielleicht a = Nichts | Etwas a
|
||||
deriving (Show,Eq)
|
||||
|
||||
instance Functor Vielleicht where
|
||||
fmap = undefined
|
||||
|
||||
|
||||
data Entweder a b = Jenes a | Dieses b
|
||||
deriving (Show,Eq)
|
||||
|
||||
instance Functor (Entweder a) where
|
||||
fmap = undefined
|
||||
|
||||
|
||||
data Konstant a b = Konstant a
|
||||
deriving (Show,Eq)
|
||||
|
||||
instance Functor (Konstant a) where
|
||||
fmap = undefined
|
||||
```
|
||||
|
||||
Achtung: Die "Container"-Metapher hat ihre Grenzen. Betrachten Sie hierzu noch einmal den
|
||||
Datentyp Pred a von Zettel 1: `data Pred a = Pred (\a -> Bool)`.
|
||||
|
||||
```haskell
|
||||
data Pred a = Pred (a -> Bool)
|
||||
runPred :: Pred a -> (a -> Bool)
|
||||
runPred (Pred p) = p
|
||||
```
|
||||
|
||||
`Pred a` macht den Anschein als handele es sich auch hier um einen Container mit Inhalt a.
|
||||
Trotzdem lässt sich `Functor` hierfür nicht instanziieren. Der Unterschied liegt darin,
|
||||
dass der Typaramter `a` als Input und nicht als Ergebnis in der vom Konstruktor `Pred`
|
||||
"eingepackten" Berechnung auftaucht. Allerdings lässt sich hier auf eine sehr ähnliche
|
||||
Eigenschaft abstrahieren, die wir für's Erste `InputFunctor` nennen wollen.
|
||||
|
||||
```haskell
|
||||
class InputFunctor f where
|
||||
inputmap :: (a -> b) -> f b -> f a
|
||||
```
|
||||
|
||||
Schreiben Sie eine `InputFunctor`-Instanz für `Pred a`.
|
||||
|
||||
```haskell
|
||||
instance InputFunctor Pred where
|
||||
inputmap = undefined
|
||||
```
|
||||
|
||||
Hiermit lässt sich nun bequem die folgende Funktion definieren, welche aus einem
|
||||
`Pred Int` ein `Pred String` macht, das prüft, ob ein Eingabestring wenigstens die Länge 5 hat.
|
||||
|
||||
```haskell
|
||||
atLeast5 :: Pred Int
|
||||
atLeast5 = Pred $ (\x -> x>=5)
|
||||
|
||||
atLeast5Char :: Pred String
|
||||
atLeast5Char = inputmap length atLeast5
|
||||
```
|
||||
|
||||
Functorial phone book
|
||||
---------------------
|
||||
|
||||
Jetzt noch einmal zurück zu PhoneBook aus Aufgabe 1.
|
||||
|
||||
```haskell
|
||||
type Number = String
|
||||
type Name = String
|
||||
type Entry = [Number]
|
||||
|
||||
newtype PhoneBook = PB (Name -> Entry)
|
||||
```
|
||||
|
||||
`PhoneBook` hat keinen Parameter, aber die allgemeinere Version `FunPB` hat sogar zwei:
|
||||
|
||||
```haskell
|
||||
data FunPB a b = FunPB (a -> (a,[b]))
|
||||
```
|
||||
|
||||
Beachten Sie, dass sich auch der Rückgabetyp ein wenig unterscheidet. Die Idee ist,
|
||||
dass FunPB zusätzlich zu den assoziierten `Number`s (angenommen `b` ist `Number`) auch
|
||||
den gesuchten `Name` (angenommen `a` ist `Name`) zurückgibt.
|
||||
|
||||
Implementieren Sie eine `Functor`-Instanz für `FunPB`.
|
||||
Hinweis: Sie können benutzen, dass für `[]`, `((,) a)` und sogar für den "function arrow"
|
||||
`((->) a)` bereits `Functor`-Instanzen in der [`GHC.Base`](https://hackage.haskell.org/package/base "GHC.Base") existieren.
|
||||
|
||||
```haskell
|
||||
instance Functor (FunPB a) where
|
||||
fmap = undefined
|
||||
```
|
||||
|
||||
Die Functor-Instanz erlaubt uns nun die Funktionen
|
||||
`separateAreaCode :: Number -> (AreaCode,Number)` und `snd` zu verwenden, um ein
|
||||
TelefonBuch mit separiertem bzw. ganz ohne AreaCode zu erhalten.
|
||||
|
||||
```haskell
|
||||
areaCodeFunPB :: FunPB Name Number -> FunPB Name (AreaCode,Number)
|
||||
areaCodeFunPB = fmap separateAreaCode
|
||||
|
||||
withoutAreaCodeFunPB :: FunPB Name Number -> FunPB Name Number
|
||||
withoutAreaCodeFunPB = fmap (snd.separateAreaCode)
|
||||
```
|
||||
|
||||
|
||||
|
||||
```haskell
|
||||
result = "Suche \"Paula\" in (FunPB Name Number): \n"
|
||||
++ (show $ runFunPB (dataToFunPB simpleData) "Paula") ++ "\n"
|
||||
++ "Suche \"Paula\" in (FunPB Name (AreaCode,Number)): \n"
|
||||
++ (show $ runFunPB (areaCodeFunPB $ dataToFunPB simpleData) "Paula") ++ "\n"
|
||||
++ "Suche \"Paula\" in (FunPB Name Number) ohne Vorwahl: \n"
|
||||
++ (show $ runFunPB (withoutAreaCodeFunPB $ dataToFunPB simpleData) "Paula")
|
||||
|
||||
```
|
||||
|
@ -1,6 +1,85 @@
|
||||
-- Aufgabe 4
|
||||
-- =========
|
||||
|
||||
module Aufgabe4 where
|
||||
|
||||
-- Aufgabe 4 finden befindet sich auf dem Branch `ghc-vis`.
|
||||
-- Weitere Informationen in der README.md
|
||||
|
||||
result = "foo"
|
||||
-- Ein paar Infos
|
||||
-- --------------
|
||||
|
||||
-- In der Vorlesung haben Sie schon kennengelernt, dass Haskell 'Lazy' ist. In
|
||||
-- dieser Übung werden Sie dies graphisch erfahren können.
|
||||
|
||||
-- Hinweis:
|
||||
-- Da wir direkt auf den Evaluationstatus von einzelnen Ausdrücken zugreifen
|
||||
-- wollen, und sich der compilierte ByteCode von unterschiedlichen GHC Versionen
|
||||
-- sehr unterschiedlich ist, arbeiten wir für diese Übung mit GHC < 8.*
|
||||
|
||||
|
||||
-- Zur Visualisierung der Ausdrücke nutzen wir ghc-vis. Dies integriert sich
|
||||
-- dank eines Scripts direkt in den GHCi.
|
||||
|
||||
-- Befehle:
|
||||
|
||||
-- :vis Öffnet das ghc-vis Fenster (haben Sie dies geschlossen müssen Sie
|
||||
-- vermutlich den GHCi nocheinmal starten bevor Sie ghc-vis erneut öffnen
|
||||
-- können
|
||||
|
||||
-- :view x Zeigt den Ausdruck "x" grafisch an
|
||||
|
||||
-- :switch Schaltet den Anzeigemodus um
|
||||
|
||||
-- :clear Säubert die Anzeige
|
||||
|
||||
-- Aufgabenstellung
|
||||
-- ----------------
|
||||
|
||||
ones = [1,1..]
|
||||
|
||||
list = [1,3..]
|
||||
|
||||
list' = [1,5..]
|
||||
|
||||
-- Starten Sie nun ghc-vis mit dem Befehl ":vis" und lassen sich zunächst `ones`
|
||||
-- anzeigen ":view ones"
|
||||
-- Sie können einfach auf einen Eintrag klicken um diesen evaluieren zu lassen
|
||||
-- (dafür darf der Eintrag noch nicht vollständig evaluiert sein).
|
||||
|
||||
-- Dies können Sie nun auch mit den anderen Listen machen.
|
||||
|
||||
-- (1) Erstellen Sie eine PDF (ghc-vis > File > Export) in der die Listen bis
|
||||
-- mindestens zu ihrem 10ten Element ausgewertet sind. Fassen Sie zusätzlich in
|
||||
-- Stichpunkten die Unterschiede der Listen zusammen.
|
||||
|
||||
|
||||
|
||||
-- Lassen Sie sich nun auf die gleiche Art eine unendliche Liste aller Fibonacci
|
||||
-- Zahlen anzeigen.
|
||||
|
||||
f = 0:1:zipWith (+) f (tail f)
|
||||
|
||||
-- :clear
|
||||
-- :view f
|
||||
|
||||
-- (2) Interpretieren Sie das nun sichtbare in kurzen Stichpunkten. Erstellen Sie nun
|
||||
-- eine PDF in der die ersten 10 Fibonacci Zahlen UNAUSGEWERTET (also als Thunks)
|
||||
-- vorliegen. Nun sollte es für Sie möglich sein, mit einem Klick alle noch nicht
|
||||
-- berechneten Fibonacci Zahlen berechnen zu lassen. Speichern Sie auch diesen
|
||||
-- Graph als PDF.
|
||||
|
||||
|
||||
primes = sieve [2..] where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p > 0]
|
||||
|
||||
-- Lassen sie sich `primes` im ghc-vis anzeigen. Dies stellt eine unendliche Liste
|
||||
-- von Primzahlen dar, die durch das "Sieb des Eratosthenes" erzeugt werden.
|
||||
-- Das Prinzip dieses Siebes ist es, Primzahlen zu finden, indem jede Zahl mit
|
||||
-- 2 beginnend `[2..]` durch alle bereits gefundenden Primzahlen geteilt wird, um
|
||||
-- zu prüfen, ob eine unteilbare Zahl, und damit eine Primzahl, vorliegt.
|
||||
|
||||
-- (3) Erstellen Sie eine PDF, in der noch nicht gefilterte/überprüfte Zahlen
|
||||
-- (5-10) zu erkennen sind, sowie bereits als Primzahl erkannte Zahlen UND Zahlen, die
|
||||
-- bereits teilweise gefiltert sind.
|
||||
|
||||
|
||||
result = "No need to write code for this exercise!"
|
||||
|
||||
|
@ -1,6 +1,85 @@
|
||||
Aufgabe 4
|
||||
=========
|
||||
|
||||
> module Aufgabe4 where
|
||||
|
||||
Aufgabe 4 finden befindet sich auf dem Branch `ghc-vis`.
|
||||
Weitere Informationen in der README.md
|
||||
|
||||
> result = "foo"
|
||||
Ein paar Infos
|
||||
--------------
|
||||
|
||||
In der Vorlesung haben Sie schon kennengelernt, dass Haskell 'Lazy' ist. In
|
||||
dieser Übung werden Sie dies graphisch erfahren können.
|
||||
|
||||
Hinweis:
|
||||
Da wir direkt auf den Evaluationstatus von einzelnen Ausdrücken zugreifen
|
||||
wollen, und sich der compilierte ByteCode von unterschiedlichen GHC Versionen
|
||||
sehr unterschiedlich ist, arbeiten wir für diese Übung mit GHC < 8.*
|
||||
|
||||
|
||||
Zur Visualisierung der Ausdrücke nutzen wir ghc-vis. Dies integriert sich
|
||||
dank eines Scripts direkt in den GHCi.
|
||||
|
||||
Befehle:
|
||||
|
||||
:vis Öffnet das ghc-vis Fenster (haben Sie dies geschlossen müssen Sie
|
||||
vermutlich den GHCi nocheinmal starten bevor Sie ghc-vis erneut öffnen
|
||||
können
|
||||
|
||||
:view x Zeigt den Ausdruck "x" grafisch an
|
||||
|
||||
:switch Schaltet den Anzeigemodus um
|
||||
|
||||
:clear Säubert die Anzeige
|
||||
|
||||
Aufgabenstellung
|
||||
----------------
|
||||
|
||||
> ones = [1,1..]
|
||||
>
|
||||
> list = [1,3..]
|
||||
>
|
||||
> list' = [1,5..]
|
||||
|
||||
Starten Sie nun ghc-vis mit dem Befehl ":vis" und lassen sich zunächst `ones`
|
||||
anzeigen ":view ones"
|
||||
Sie können einfach auf einen Eintrag klicken um diesen evaluieren zu lassen
|
||||
(dafür darf der Eintrag noch nicht vollständig evaluiert sein).
|
||||
|
||||
Dies können Sie nun auch mit den anderen Listen machen.
|
||||
|
||||
(1) Erstellen Sie eine PDF (ghc-vis > File > Export) in der die Listen bis
|
||||
mindestens zu ihrem 10ten Element ausgewertet sind. Fassen Sie zusätzlich in
|
||||
Stichpunkten die Unterschiede der Listen zusammen.
|
||||
|
||||
|
||||
|
||||
Lassen Sie sich nun auf die gleiche Art eine unendliche Liste aller Fibonacci
|
||||
Zahlen anzeigen.
|
||||
|
||||
> f = 0:1:zipWith (+) f (tail f)
|
||||
|
||||
:clear
|
||||
:view f
|
||||
|
||||
(2) Interpretieren Sie das nun sichtbare in kurzen Stichpunkten. Erstellen Sie nun
|
||||
eine PDF in der die ersten 10 Fibonacci Zahlen UNAUSGEWERTET (also als Thunks)
|
||||
vorliegen. Nun sollte es für Sie möglich sein, mit einem Klick alle noch nicht
|
||||
berechneten Fibonacci Zahlen berechnen zu lassen. Speichern Sie auch diesen
|
||||
Graph als PDF.
|
||||
|
||||
|
||||
> primes = sieve [2..] where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p > 0]
|
||||
|
||||
Lassen sie sich `primes` im ghc-vis anzeigen. Dies stellt eine unendliche Liste
|
||||
von Primzahlen dar, die durch das "Sieb des Eratosthenes" erzeugt werden.
|
||||
Das Prinzip dieses Siebes ist es, Primzahlen zu finden, indem jede Zahl mit
|
||||
2 beginnend `[2..]` durch alle bereits gefundenden Primzahlen geteilt wird, um
|
||||
zu prüfen, ob eine unteilbare Zahl, und damit eine Primzahl, vorliegt.
|
||||
|
||||
(3) Erstellen Sie eine PDF, in der noch nicht gefilterte/überprüfte Zahlen
|
||||
(5-10) zu erkennen sind, sowie bereits als Primzahl erkannte Zahlen UND Zahlen, die
|
||||
bereits teilweise gefiltert sind.
|
||||
|
||||
|
||||
> result = "No need to write code for this exercise!"
|
||||
|
||||
|
@ -1,6 +1,85 @@
|
||||
Aufgabe 4
|
||||
=========
|
||||
|
||||
> module Aufgabe4 where
|
||||
|
||||
Aufgabe 4 finden befindet sich auf dem Branch `ghc-vis`.
|
||||
Weitere Informationen in der README.md
|
||||
|
||||
> result = "foo"
|
||||
Ein paar Infos
|
||||
--------------
|
||||
|
||||
In der Vorlesung haben Sie schon kennengelernt, dass Haskell 'Lazy' ist. In
|
||||
dieser Übung werden Sie dies graphisch erfahren können.
|
||||
|
||||
Hinweis:
|
||||
Da wir direkt auf den Evaluationstatus von einzelnen Ausdrücken zugreifen
|
||||
wollen, und sich der compilierte ByteCode von unterschiedlichen GHC Versionen
|
||||
sehr unterschiedlich ist, arbeiten wir für diese Übung mit GHC < 8.*
|
||||
|
||||
|
||||
Zur Visualisierung der Ausdrücke nutzen wir ghc-vis. Dies integriert sich
|
||||
dank eines Scripts direkt in den GHCi.
|
||||
|
||||
Befehle:
|
||||
|
||||
:vis Öffnet das ghc-vis Fenster (haben Sie dies geschlossen müssen Sie
|
||||
vermutlich den GHCi nocheinmal starten bevor Sie ghc-vis erneut öffnen
|
||||
können
|
||||
|
||||
:view x Zeigt den Ausdruck "x" grafisch an
|
||||
|
||||
:switch Schaltet den Anzeigemodus um
|
||||
|
||||
:clear Säubert die Anzeige
|
||||
|
||||
Aufgabenstellung
|
||||
----------------
|
||||
|
||||
> ones = [1,1..]
|
||||
>
|
||||
> list = [1,3..]
|
||||
>
|
||||
> list' = [1,5..]
|
||||
|
||||
Starten Sie nun ghc-vis mit dem Befehl ":vis" und lassen sich zunächst `ones`
|
||||
anzeigen ":view ones"
|
||||
Sie können einfach auf einen Eintrag klicken um diesen evaluieren zu lassen
|
||||
(dafür darf der Eintrag noch nicht vollständig evaluiert sein).
|
||||
|
||||
Dies können Sie nun auch mit den anderen Listen machen.
|
||||
|
||||
(1) Erstellen Sie eine PDF (ghc-vis > File > Export) in der die Listen bis
|
||||
mindestens zu ihrem 10ten Element ausgewertet sind. Fassen Sie zusätzlich in
|
||||
Stichpunkten die Unterschiede der Listen zusammen.
|
||||
|
||||
|
||||
|
||||
Lassen Sie sich nun auf die gleiche Art eine unendliche Liste aller Fibonacci
|
||||
Zahlen anzeigen.
|
||||
|
||||
> f = 0:1:zipWith (+) f (tail f)
|
||||
|
||||
:clear
|
||||
:view f
|
||||
|
||||
(2) Interpretieren Sie das nun sichtbare in kurzen Stichpunkten. Erstellen Sie nun
|
||||
eine PDF in der die ersten 10 Fibonacci Zahlen UNAUSGEWERTET (also als Thunks)
|
||||
vorliegen. Nun sollte es für Sie möglich sein, mit einem Klick alle noch nicht
|
||||
berechneten Fibonacci Zahlen berechnen zu lassen. Speichern Sie auch diesen
|
||||
Graph als PDF.
|
||||
|
||||
|
||||
> primes = sieve [2..] where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p > 0]
|
||||
|
||||
Lassen sie sich `primes` im ghc-vis anzeigen. Dies stellt eine unendliche Liste
|
||||
von Primzahlen dar, die durch das "Sieb des Eratosthenes" erzeugt werden.
|
||||
Das Prinzip dieses Siebes ist es, Primzahlen zu finden, indem jede Zahl mit
|
||||
2 beginnend `[2..]` durch alle bereits gefundenden Primzahlen geteilt wird, um
|
||||
zu prüfen, ob eine unteilbare Zahl, und damit eine Primzahl, vorliegt.
|
||||
|
||||
(3) Erstellen Sie eine PDF, in der noch nicht gefilterte/überprüfte Zahlen
|
||||
(5-10) zu erkennen sind, sowie bereits als Primzahl erkannte Zahlen UND Zahlen, die
|
||||
bereits teilweise gefiltert sind.
|
||||
|
||||
|
||||
> result = "No need to write code for this exercise!"
|
||||
|
||||
|
@ -1,9 +0,0 @@
|
||||
module DataPB where
|
||||
|
||||
import qualified Data.Attoparsec.Text as A
|
||||
import Data.Attoparsec.Combinator
|
||||
import qualified Data.Text as T
|
||||
|
||||
simpleData :: [(String,String)]
|
||||
simpleData = [("Phillip","(04165) 6048168"),("Michael","(0148) 56121127"),("Phillip","(03548) 4538973"),("Paula","(030) 664940"),("Paul","(088) 08933337"),("Sylvester","(035588) 582174"),("Harriet","(0751) 75825269"),("Ariane","(0302) 49046118"),("Michael","(06650) 6975458"),("Kylie","(046) 77199945"),("Lee","(032509) 210171"),("Hunter","(06929) 7068643"),("Marvin","(036623) 741589"),("Paul","(0229) 13909736"),("Linda","(0859) 15505790"),("Lydia","(039229) 379675"),("Michael","(0755) 98407141"),("Skyler","(035869) 897089"),("Ariane","(035) 22332444"),("Eden","(0080) 20592805"),("John","(09857) 4504229"),("Jenette","(0182) 34362051"),("Delilah","(035690) 139671"),("Michael","(034973) 388559"),("John","(0451) 08133630"),("Madonna","(03897) 0007997"),("Madeline","(0600) 19801141"),("Francesca","(087) 58002539"),("Latifah","(04010) 3533294"),("Lucas","(08963) 4253453"),("Acton","(034060) 453837"),("Shelby","(039459) 871551"),("Keiko","(047) 07336335"),("Moses","(017) 73610349"),("Samantha","(034114) 317044"),("Logan","(00017) 2404967"),("Marcia","(041) 04583123"),("Allistair","(0707) 04978234"),("Hedwig","(037227) 674138"),("Phillip","(01175) 6633514"),("Paul","(084) 47394790"),("Paula","(0243) 06440875"),("Michael","(065) 98178780"),("Paul","(00622) 0087233"),("Stacy","(004) 38785128"),("Stephen","(043) 84694380"),("Michael","(053) 55705629"),("Christine","(001) 44243108"),("Paul","(030513) 239204"),("Phillip","(07043) 5061835"),("Paula","(036638) 3341652")]
|
||||
|
21
src/FunPB.hs
21
src/FunPB.hs
@ -1,21 +0,0 @@
|
||||
module FunPB where
|
||||
|
||||
import Control.Monad
|
||||
|
||||
data FunPB a b = FunPB { runFunPB :: a -> (a,[b]) }
|
||||
|
||||
instance Monoid (FunPB a b) where
|
||||
mempty = FunPB $ \k -> (k,mempty)
|
||||
mappend pb1 pb2 = FunPB $ \n -> (,) n $ msum . (<$>) (uncurry (flip const).((flip runFunPB) n)) $ [pb1,pb2]
|
||||
|
||||
addAssoc :: Eq a => (a,b) -> FunPB a b -> FunPB a b
|
||||
addAssoc (n,nr) pb = FunPB $ \n' -> if n' == n then ((:) nr) <$> runFunPB pb n' else runFunPB pb n'
|
||||
|
||||
delAssoc :: Eq a => a -> FunPB a b -> FunPB a b
|
||||
delAssoc n pb = FunPB $ \n' -> if n == n' then runFunPB mempty n' else runFunPB pb n'
|
||||
|
||||
multiFind :: [FunPB a b] -> a -> (a,[b])
|
||||
multiFind = runFunPB.mconcat
|
||||
|
||||
dataToFunPB :: Eq a => [(a,b)] -> FunPB a b
|
||||
dataToFunPB = mconcat.fmap ((flip addAssoc) mempty)
|
10
stack.yaml
10
stack.yaml
@ -1,5 +1,11 @@
|
||||
flags: {}
|
||||
extra-package-dbs: []
|
||||
packages:
|
||||
- '.'
|
||||
extra-deps: []
|
||||
resolver: lts-8.9
|
||||
extra-deps:
|
||||
- ghc-vis-0.8
|
||||
- svgcairo-0.13.1.1
|
||||
- xdot-0.3.0.1
|
||||
- Cabal-1.24.2.0
|
||||
- gtk2hs-buildtools-0.13.2.2
|
||||
resolver: lts-6.31
|
||||
|
@ -1,66 +0,0 @@
|
||||
import Aufgabe1
|
||||
|
||||
import Test.Framework.Providers.HUnit (testCase)
|
||||
import Test.Framework.Runners.Console (defaultMain)
|
||||
import Test.HUnit
|
||||
|
||||
|
||||
{- TEST DATA -}
|
||||
|
||||
data1,data2,data3 :: [(String,String)]
|
||||
data1 = [("Phillip","(04165) 9876543"),("Phillip","(03548) 1234567"),("Paula","(035383) 567890")]
|
||||
data2 = [("Paula","(04165) 8765432"),("Michael","(03548) 2345678"),("Ariane","(035383) 678901")]
|
||||
data3 = [("Paula","(04165) 7654321"),("Hannelore","(03548) 3456789"),("Helmut","(035383) 789012")]
|
||||
|
||||
noData :: [(String,String)]
|
||||
noData = mempty
|
||||
|
||||
xPB :: PhoneBook
|
||||
xPB = PB $ \n -> [b|(a,b)<-data1, n==a]
|
||||
|
||||
yPB :: PhoneBook
|
||||
yPB = PB $ \n -> [b|(a,b)<-data2, n==a]
|
||||
|
||||
zPB :: PhoneBook
|
||||
zPB = PB $ \n -> [b|(a,b)<-data3, n==a]
|
||||
|
||||
examplePB = xPB
|
||||
|
||||
emptyPhoneBook :: PhoneBook
|
||||
emptyPhoneBook = PB $ \a -> [b|(a,b)<-noData]
|
||||
|
||||
{- TEST CASES -}
|
||||
|
||||
usePBTest = testCase "Benutze beispielhaftes PhoneBook"
|
||||
$ assertEqual "usePB examplePB \"Phillip\" sollte folgende zwei Nummern rausgeben" ["(04165) 9876543","(03548) 1234567"]
|
||||
$ usePB examplePB "Phillip"
|
||||
|
||||
mappendTest = testCase "Assoziativität von mappend"
|
||||
$ assertEqual "Assoziativität von mappend ist nicht erfüllt" (usePB (mappend (mappend xPB yPB) zPB) "Paula")
|
||||
$ usePB (mappend xPB $ mappend yPB zPB) "Paula"
|
||||
|
||||
memptyTest1 = testCase "Linksidentität von mempty"
|
||||
$ assertEqual "Linksidentität von mempty ist nicht erfüllt" (usePB xPB "Phillip")
|
||||
$ usePB (mappend xPB mempty) "Phillip"
|
||||
|
||||
memptyTest2 = testCase "Rechtsidentität von mempty"
|
||||
$ assertEqual "Rechtsidentität von mempty ist nicht erfüllt" (usePB xPB "Phillip")
|
||||
$ usePB (mappend mempty xPB) "Phillip"
|
||||
|
||||
addEntryTest = testCase "Füge Name-Nummer-Verknüpfung hinzu"
|
||||
$ assertEqual "Die hinzugefügte Nummer wird nicht gefunden" ["12345"]
|
||||
$ usePB (addEntry "NeuerName" "12345" emptyPhoneBook) "NeuerName"
|
||||
|
||||
delEntryTest = testCase "Lösche Name-Nummer-Verknüpfung"
|
||||
$ assertEqual "Die gelöschte Nummer wird weiterhin gefunden" []
|
||||
$ usePB (delEntry "Paula" xPB) "Paula"
|
||||
|
||||
findInMultTest = testCase "Suche in mehreren PhoneBooks"
|
||||
$ assertEqual "Es werden nicht alle drei Nummern von Paula gefunden" ["(035383) 567890","(04165) 8765432","(04165) 7654321"]
|
||||
$ findInMult [xPB,yPB,zPB] "Paula"
|
||||
|
||||
|
||||
tests = [usePBTest,mappendTest,memptyTest1,memptyTest2,addEntryTest,delEntryTest,findInMultTest]
|
||||
|
||||
main :: IO ()
|
||||
main = defaultMain tests
|
@ -1,7 +0,0 @@
|
||||
main :: IO ()
|
||||
main = putStrLn $ "Eine Testung von Aufgabe 2 mit HUnit ist nicht sinnvoll. \n"
|
||||
++ "Falls Sie feststecken und nicht weiterkommen: \n"
|
||||
++ "Welche Typklassen kennen Sie? \n"
|
||||
++ "Wie sehen deren Instanzen für die vorliegenden Datentypen aus? \n"
|
||||
++ "Welche Regeln/Gesetze implizieren diese Typklassen? \n"
|
||||
++ "Eventuell lassen sich Ausdrücke mehrfach verallgemeinern. \n"
|
@ -1,77 +0,0 @@
|
||||
import Aufgabe3
|
||||
import DataPB
|
||||
import FunPB
|
||||
import AreaCode
|
||||
|
||||
import Test.Framework.Providers.HUnit (testCase)
|
||||
import Test.Framework.Runners.Console (defaultMain)
|
||||
import Test.HUnit
|
||||
|
||||
|
||||
{- TEST DATA -}
|
||||
|
||||
funPBTest = dataToFunPB simpleData
|
||||
|
||||
vielleichtValues = [(Etwas 1234),Nichts]
|
||||
|
||||
entwederValues :: [Entweder String [[[Integer]]]]
|
||||
entwederValues = [(Jenes "just a string"),(Dieses [[[12344321]]])]
|
||||
|
||||
konstantValue = Konstant True
|
||||
|
||||
f = const 1
|
||||
g = const '0'
|
||||
|
||||
|
||||
{- TEST CASES-}
|
||||
|
||||
atleast5CharFalse = testCase "Teste Randbedingung (==False) für atleast5Char"
|
||||
$ assertEqual "Der Ausdruck >>> runPred atLeast5Char $ \"1234\" \nsollte zu False auswerten" False
|
||||
$ runPred atLeast5Char $ "1234"
|
||||
|
||||
atleast5CharTrue = testCase "Teste Randbedingung (==True) für atleast5Char"
|
||||
$ assertEqual "Der Ausdruck >>> runPred atLeast5Char $ \"12345\" \nsollte zu True auswerten" True
|
||||
$ runPred atLeast5Char $ "12345"
|
||||
|
||||
|
||||
|
||||
fmapVielleichtTestIdentity = testCase "Functor Vielleicht: Strukturerhaltung"
|
||||
$ assertEqual "Es sollte gelten: fmap id == id . " (id <$> vielleichtValues)
|
||||
$ ((fmap id) <$> vielleichtValues)
|
||||
|
||||
fmapVielleichtTestComposability = testCase "Functor Vielleicht: Komponierbarkeit"
|
||||
$ assertEqual "Es sollte gelten: fmap f . fmap g == fmap (f . g) . " ((fmap f . fmap g) <$> vielleichtValues)
|
||||
$ ((fmap (f . g)) <$> vielleichtValues)
|
||||
|
||||
|
||||
fmapEntwederTestIdentity = testCase "Functor (Entweder a): Strukturerhaltung"
|
||||
$ assertEqual "Es sollte gelten: fmap id == id . " (id <$> entwederValues)
|
||||
$ ((fmap id) <$> entwederValues)
|
||||
|
||||
fmapEntwederTestComposability = testCase "Functor (Entweder a): Komponierbarkeit"
|
||||
$ assertEqual "Es sollte gelten: fmap f . fmap g == fmap (f . g) . " ((fmap f . fmap g) <$> entwederValues)
|
||||
$ ((fmap (f . g)) <$> entwederValues)
|
||||
|
||||
|
||||
fmapKonstantTestIdentity = testCase "Functor (Konstant a): Strukturerhaltung"
|
||||
$ assertEqual "Es sollte gelten: fmap id == id . " (id konstantValue)
|
||||
$ ((fmap id) konstantValue)
|
||||
|
||||
fmapKonstantTestComposability = testCase "Functor (Konstant a): Komponierbarkeit"
|
||||
$ assertEqual "Es sollte gelten: fmap f . fmap g == fmap (f . g) . " ((fmap f . fmap g) konstantValue)
|
||||
$ ((fmap (f . g)) konstantValue)
|
||||
|
||||
|
||||
|
||||
fmapFunPBTestIdentity = testCase "Functor (FunPB a): Strukturerhaltung"
|
||||
$ assertEqual "Es sollte gelten: fmap id == id . " (runFunPB (id funPBTest) "Paula")
|
||||
$ (runFunPB ((fmap id) funPBTest) "Paula")
|
||||
|
||||
fmapFunPBTestComposability = testCase "Functor (FunPB a): Komponierbarkeit"
|
||||
$ assertEqual "Es sollte gelten: fmap f . fmap g == fmap (f . g) . " (runFunPB (fmap snd . fmap separateAreaCode $ funPBTest) "Paula")
|
||||
$ (runFunPB ((fmap (snd.separateAreaCode)) funPBTest) "Paula")
|
||||
|
||||
tests = [atleast5CharFalse,atleast5CharTrue,fmapVielleichtTestIdentity,fmapVielleichtTestComposability,fmapEntwederTestIdentity,fmapEntwederTestComposability,fmapKonstantTestIdentity,fmapKonstantTestComposability,fmapFunPBTestIdentity,fmapFunPBTestComposability]
|
||||
|
||||
main :: IO ()
|
||||
main = defaultMain tests
|
@ -18,74 +18,8 @@ cabal-version: >=1.10
|
||||
-- to solve assignments
|
||||
library
|
||||
hs-source-dirs: src
|
||||
exposed-modules: Lib
|
||||
, DataPB
|
||||
, AreaCode
|
||||
, FunPB
|
||||
, Aufgabe1
|
||||
, Aufgabe2
|
||||
, Aufgabe3
|
||||
, Aufgabe4
|
||||
exposed-modules: Aufgabe4
|
||||
build-depends: base >= 4.7 && < 5
|
||||
, attoparsec
|
||||
, text
|
||||
default-language: Haskell2010
|
||||
|
||||
executable aufgabe1
|
||||
hs-source-dirs: app
|
||||
main-is: Aufgabe1Main.hs
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
build-depends: base
|
||||
, zettel2
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite aufgabe1-tests
|
||||
type: exitcode-stdio-1.0
|
||||
hs-source-dirs: test
|
||||
main-is: Aufgabe1-Spec.hs
|
||||
build-depends: base
|
||||
, zettel2
|
||||
, test-framework
|
||||
, test-framework-hunit
|
||||
, HUnit
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
default-language: Haskell2010
|
||||
|
||||
executable aufgabe2
|
||||
hs-source-dirs: app
|
||||
main-is: Aufgabe2Main.hs
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
build-depends: base
|
||||
, zettel2
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite aufgabe2-tests
|
||||
type: exitcode-stdio-1.0
|
||||
hs-source-dirs: test
|
||||
main-is: Aufgabe2-Spec.hs
|
||||
build-depends: base
|
||||
, zettel2
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
default-language: Haskell2010
|
||||
|
||||
executable aufgabe3
|
||||
hs-source-dirs: app
|
||||
main-is: Aufgabe3Main.hs
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
build-depends: base
|
||||
, zettel2
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite aufgabe3-tests
|
||||
type: exitcode-stdio-1.0
|
||||
hs-source-dirs: test
|
||||
main-is: Aufgabe3-Spec.hs
|
||||
build-depends: base
|
||||
, zettel2
|
||||
, test-framework
|
||||
, test-framework-hunit
|
||||
, HUnit
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
default-language: Haskell2010
|
||||
|
||||
executable aufgabe4
|
||||
@ -94,15 +28,7 @@ executable aufgabe4
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
build-depends: base
|
||||
, zettel2
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite aufgabe4-tests
|
||||
type: exitcode-stdio-1.0
|
||||
hs-source-dirs: test
|
||||
main-is: Aufgabe4-Spec.hs
|
||||
build-depends: base
|
||||
, zettel2
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
, ghc-vis
|
||||
default-language: Haskell2010
|
||||
|
||||
source-repository head
|
||||
|
Loading…
Reference in New Issue
Block a user