first commit, zettel2 without solutions

This commit is contained in:
BergesJ 2017-04-30 20:51:45 +02:00
commit 363316dc2e
30 changed files with 1322 additions and 0 deletions

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
dist
dist-*
cabal-dev
*.o
*.hi
*.chi
*.chs.h
*.dyn_o
*.dyn_hi
.hpc
.hsenv
.cabal-sandbox/
cabal.sandbox.config
*.prof
*.aux
*.hp
*.eventlog
.stack-work/
cabal.project.local

41
.travis.yml Normal file
View File

@ -0,0 +1,41 @@
# This is the simple Travis configuration, which is intended for use
# on applications which do not require cross-platform and
# multiple-GHC-version support. For more information and other
# options, see:
#
# https://docs.haskellstack.org/en/stable/travis_ci/
#
# Copy these contents into the root directory of your Github project in a file
# named .travis.yml
# Use new container infrastructure to enable caching
sudo: false
# Do not choose a language; we provide our own build tools.
language: generic
# Caching so the next build will be fast too.
cache:
directories:
- $HOME/.stack
# Ensure necessary system libraries are present
addons:
apt:
packages:
- libgmp-dev
before_install:
# Download and unpack the stack executable
- mkdir -p ~/.local/bin
- export PATH=$HOME/.local/bin:$PATH
- travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
install:
# Build dependencies
- stack --no-terminal --install-ghc test --only-dependencies
script:
# Build the package, its tests, and its docs and run the tests
- stack --no-terminal test --haddock --no-haddock-deps

30
LICENSE Normal file
View File

@ -0,0 +1,30 @@
Copyright Author name here (c) 2017
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Author name here nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

2
Setup.hs Normal file
View File

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

6
app/Aufgabe1Main.hs Normal file
View File

@ -0,0 +1,6 @@
module Main where
import Aufgabe1
main :: IO ()
main = putStrLn result

6
app/Aufgabe2Main.hs Normal file
View File

@ -0,0 +1,6 @@
module Main where
import Aufgabe2
main :: IO ()
main = putStrLn result

6
app/Aufgabe3Main.hs Normal file
View File

@ -0,0 +1,6 @@
module Main where
import Aufgabe3
main :: IO ()
main = putStrLn result

6
app/Aufgabe4Main.hs Normal file
View File

@ -0,0 +1,6 @@
module Main where
import Aufgabe4
main :: IO ()
main = putStrLn result

27
src/AreaCode.hs Normal file
View File

@ -0,0 +1,27 @@
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

82
src/Aufgabe1.hs Normal file
View File

@ -0,0 +1,82 @@
-- 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)

72
src/Aufgabe1.lhs Normal file
View File

@ -0,0 +1,72 @@
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)

79
src/Aufgabe1.md Normal file
View File

@ -0,0 +1,79 @@
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)
```

41
src/Aufgabe2.hs Normal file
View File

@ -0,0 +1,41 @@
-- 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?"

45
src/Aufgabe2.lhs Normal file
View File

@ -0,0 +1,45 @@
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?"

47
src/Aufgabe2.md Normal file
View File

@ -0,0 +1,47 @@
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 Normal file
View File

@ -0,0 +1,147 @@
-- 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 Normal file
View File

@ -0,0 +1,168 @@
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 Normal file
View File

@ -0,0 +1,171 @@
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")
```

7
src/Aufgabe4.hs Normal file
View File

@ -0,0 +1,7 @@
module Aufgabe4 where
-- Aufgabe 4 finden Sie auf dem branch `ghc-vis`.
-- Sie können auf diesen mit dem Befehl `git checkout ghc-vis` gelangen.
-- Führen Sie dort zunächst das Installationsscript `install_dependencies.sh` aus.
result = "foo"

7
src/Aufgabe4.lhs Normal file
View File

@ -0,0 +1,7 @@
> module Aufgabe4 where
Aufgabe 4 finden Sie auf dem branch `ghc-vis`.
Sie können auf diesen mit dem Befehl `git checkout ghc-vis` gelangen.
Führen Sie dort zunächst das Installationsscript `install_dependencies.sh` aus.
> result = "foo"

7
src/Aufgabe4.md Normal file
View File

@ -0,0 +1,7 @@
> module Aufgabe4 where
Aufgabe 4 finden Sie auf dem branch `ghc-vis`.
Sie können auf diesen mit dem Befehl `git checkout ghc-vis` gelangen.
Führen Sie dort zunächst das Installationsscript `install_dependencies.sh` aus.
> result = "foo"

9
src/DataPB.hs Normal file
View File

@ -0,0 +1,9 @@
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 Normal file
View File

@ -0,0 +1,21 @@
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)

6
src/Lib.hs Normal file
View File

@ -0,0 +1,6 @@
module Lib
( someFunc
) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"

5
stack.yaml Normal file
View File

@ -0,0 +1,5 @@
extra-package-dbs: []
packages:
- '.'
extra-deps: []
resolver: lts-8.9

66
test/Aufgabe1-Spec.hs Normal file
View File

@ -0,0 +1,66 @@
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

7
test/Aufgabe2-Spec.hs Normal file
View File

@ -0,0 +1,7 @@
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"

77
test/Aufgabe3-Spec.hs Normal file
View File

@ -0,0 +1,77 @@
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

5
test/Aufgabe4-Spec.hs Normal file
View File

@ -0,0 +1,5 @@
main :: IO ()
main = putStrLn $ "Für Aufgabe 4 sind keine Tests vorgesehen. \n"
++ "Zur Bearbeitung von Aufgabe 4 wechseln Sie bitte auf den branch `ghc-vis`. \n"
++ "Dies erreichen Sie mit dem Befehl `git checkout ghc-vis`. \n"
++ "Führen Sie dort zunächst das Installationsscript `install_dependencies.sh` aus. "

110
zettel2.cabal Normal file
View File

@ -0,0 +1,110 @@
name: zettel2
version: 0.1.0.0
synopsis: Second Assignment of FFPiHaskell 2017
-- description:
homepage: https://github.com/FFPiHaskell/zettel2-skeleton#readme
license: BSD3
license-file: LICENSE
author: FFPiHaskell Tutors
maintainer: sdressel@techfak.uni-bielefeld.de
copyright: 2017 FFPiHaskell Tutors
category: cli
build-type: Simple
extra-source-files: README.md
cabal-version: >=1.10
-- library for all things common in all exercises/not neccessary for students
-- to solve assignments
library
hs-source-dirs: src
exposed-modules: Lib
, DataPB
, AreaCode
, FunPB
, Aufgabe1
, Aufgabe2
, Aufgabe3
, 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
hs-source-dirs: app
main-is: Aufgabe4Main.hs
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
default-language: Haskell2010
source-repository head
type: git
location: https://github.com/FFPiHaskell/zettel2-skeleton