• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Web programming in Haskell
 

Web programming in Haskell

on

  • 1,008 views

This presentation is the final presentation I gave to get my MSc. degree. For more info on this work, see http://github.com/chriseidhof/thesis

This presentation is the final presentation I gave to get my MSc. degree. For more info on this work, see http://github.com/chriseidhof/thesis

Statistics

Views

Total Views
1,008
Views on SlideShare
999
Embed Views
9

Actions

Likes
0
Downloads
18
Comments
0

2 Embeds 9

http://www.docshut.com 5
http://www.slideshare.net 4

Accessibility

Categories

Upload Details

Uploaded via as Apple Keynote

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • <br />
  • Ordina uses Haskell to generate PHP + MySQL! <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • Continuations, views, database <br />
  • Continuations, views, database <br />
  • Continuations, views, database <br />
  • Continuations, views, database <br />
  • Continuations, views, database <br />
  • <br />
  • <br />
  • What&#x2019;s the catch: you cannot store the name in the URL <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • Go through visual syntax: entities, relationships, cardinality <br />
  • Go through visual syntax: entities, relationships, cardinality <br />
  • Go through visual syntax: entities, relationships, cardinality <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />

Web programming in Haskell Web programming in Haskell Presentation Transcript

  • Web programming in Haskell Chris Eidhof
  • Current state of affairs • Web programming is done in dynamic languages • There is no competing Haskell framework • There are interesting FP techniques waiting to be applied
  • Using Haskell for web programming • Generic Programming • Type inferencing • Lenses • Type-level programming
  • The Pesto Framework Three libraries for programming the web • A library to store data • A library to display data • A library for control flow
  • Quiz Demo
  • The Quiz Code takeQuiz :: Web (Ref QuizModel Quiz) () takeQuiz = proc ref -> do quiz <- basil find -< ref addQuiz :: Web () () case quiz of instance Formlet Email where formlet x = Email <$> formlet addQuiz = proc () -> do Just quiz -> do (unEmail <$> x) q <- input -< Just qsR <- basil (findRels DL ixQuestions) -< ref data Quiz = Quiz { subject :: String, () qs <- basil (mapM find) -< description :: String} ref <- (basil (x -> new ixQuiz x PNil)) -< S.toList qsR data Question = Question { title :: String, choiceA, q () <- display ghtml -< quiz choiceB, choiceC :: String} _ <- addQuestions -< response <- form'' responseForm -< () data Response = Response { name :: String ref as <- many getAnswer -< , email :: Email display (const $ X.toHtml "added quiz with questions.") -< (catMaybes qs) , date :: Date () display ghtml -< , answers :: [Answer] addQuestions :: Web (Ref QuizModel Quiz) ([Ref QuizModel response {answers = as} } Question]) Nothing -> display (const "Not Found") -< () data Answer = QA | QB | QC deriving (Show, Eq, Enum) addQuestions = proc q -> do data ResponseView = ResponseView { _name :: String , $(mkTList "QuizModel" [''Quiz, ''Question, ''Response]) qs <- inputMany -< () _email :: Email } type Questions = Rel QuizModel One Quiz Many Question basil ((qs,qz) -> mapM (newQuestion qz) qs) -< (qs, q) responseProj :: Response :-> ResponseView type Responses = Rel QuizModel One Quiz Many Response newQuestion :: Ref QuizModel Quiz -> Question -> M (Ref responseProj = Lens $ ResponseView <$> _name `for` lName <*> questions :: Questions QuizModel Question) _email `for` lEmail questions = Rel One ixQuiz "quiz" Many ixQuestion newQuestion refQuiz question = new ixQuestion responseForm :: Form Response "questions" question responseForm = projectedForm responseProj emptyResponse responses :: Responses (PCons (refQuiz, DR, where emptyResponse :: Response responses = Rel One ixQuiz "quiz" Many ixResponse ixQuestions) PNil) emptyResponse = Response "" (Email "") (Date "now") "responses" listQuizzes :: Web () () [] $(mkTlist "QuizRelations" [''Questions, ''Responses]) listQuizzes = proc () -> do getAnswer :: Web Question Answer instance ERModel QuizModel QuizRelations where qs <- basil' (findAll ixQuiz) -< () getAnswer = form''' answerForm relations = TCons4 questions $ TCons4 responses $ TNil4 case qs of answerForm :: Question -> Form Answer witnesses = WCons ixQuiz $ WCons ixQuestion $ WCons [] -> display X.toHtml -< "No answerForm q = F.plug ((title q +++ X.br) +++) ixResponse $ WNil quizzes yet." $ F.enumRadio [(QA, choiceA q),(QB, choiceB $(deriveAll ''Quiz "PFQuiz") _ -> display (X.concatHtml . map quizWithLink) -< qs q),(QC, choiceC q)] $(deriveAll ''Question "PFQuestion") quizWithLink :: (Ref QuizModel Quiz, Quiz) -> X.Html Nothing $(deriveAll ''Response "PFResponse") quizWithLink (Ref _ i, q) = X.concatHtml homepage = X.toHtml "Welcome to the quiz system." type instance PF Quiz = PFQuiz [ ghtml q, X.br, static (View i) "view" data QuizForm = QuizForm {_subject :: String, _description :: type instance PF Question = PFQuestion , X.br, static (Take i) "take quiz" ] TextArea} type instance PF Response = PFResponse viewQuiz :: Web (Ref QuizModel Quiz) () quizProj :: Quiz :-> QuizForm $(mkLabels [''Quiz, ''Question, ''Response]) viewQuiz = proc ref -> do quizProj = Lens $ QuizForm <$> _subject `for` lSubject $(mkEqualities [''Quiz, ''Question, ''Response]) quiz <- basil find -< ref <*> _description `for` data QuizRoute = List | Add | View Int | Take Int case quiz of (textAreaToString % lDescription) deriving Show Just quiz -> do instance DefaultForm Quiz where form = projectedForm instance ToURL QuizRoute where Just qsR <- basil (findRels DL ixQuestions) -< ref quizProj (Quiz "" "") toURL = gtoURL . from qs <- basil (mapM find) -< instance DefaultForm Question where form = gform Nothing fromURL = fmap to . gfromURL S.toList qsR type M a = Basil QuizModel QuizRelations a $(deriveAll ''QuizRoute "PFQuizRoute") display quizWithQuestions -< (ref, type St = BasilState QuizModel QuizRelations type instance PF QuizRoute = PFQuizRoute quiz, catMaybes qs) type Web i o = WebT (MVar St) IO i o static :: QuizRoute -> String -> X.Html Nothing -> display (const "Not Found") -< () template x = X.thehtml ( (X.header $ linkStyle "/public/ static u s = X.anchor X.! [X.href $ "/" ++ renderURL (toURL quizWithQuestions :: (Ref QuizModel Quiz, Quiz, [Question]) - style.css") u)] X.<< X.toHtml s > X.Html +++ (X.body $ templateHeader +++ x)) handle :: QuizRoute -> Continuation (MVar St) IO () quizWithQuestions (Ref _ i, q, qs) = ghtml q templateHeader = (X.h1 X.! [X.theclass "page"]) (X.toHtml handle Add = Cont () addQuiz +++ X.br "Quiz system") +++ menu handle List = Cont () listQuizzes +++ (X.concatHtml $ intersperse menu = X.unordList X.! [X.theclass "menu"] $ [static Add "add handle (View i) = Cont (Ref ixQuiz i) viewQuiz X.br $ (map ghtml) qs) quiz", static List "list quizzes"] handle (Take i) = Cont (Ref ixQuiz i) takeQuiz +++ static (Take i) "take quiz" linkStyle name = (X.thelink X.! [X.rel "stylesheet", X.href name]) X.noHtml $(deriveAll ''QuizForm "PFQuizForm") type instance PF QuizForm = PFQuizForm $(deriveAll ''ResponseView "PFResponseView")
  • The Quiz Code takeQuiz :: Web (Ref QuizModel Quiz) () takeQuiz = proc ref -> do quiz <- basil find -< ref addQuiz :: Web () () case quiz of instance Formlet Email where formlet x = Email <$> formlet addQuiz = proc () -> do Just quiz -> do (unEmail <$> x) q <- input -< Just qsR <- basil (findRels DL ixQuestions) -< ref data Quiz = Quiz { subject :: String, () qs <- basil (mapM find) -< description :: String} ref <- (basil (x -> new ixQuiz x PNil)) -< S.toList qsR data Question = Question { title :: String, choiceA, q () <- display ghtml -< quiz choiceB, choiceC :: String} _ <- addQuestions -< response <- form'' responseForm -< () data Response = Response { name :: String ref as <- many getAnswer -< , email :: Email display (const $ X.toHtml "added quiz with questions.") -< (catMaybes qs) , date :: Date () display ghtml -< , answers :: [Answer] addQuestions :: Web (Ref QuizModel Quiz) ([Ref QuizModel response {answers = as} } Question]) Nothing -> display (const "Not Found") -< () data Answer = QA | QB | QC deriving (Show, Eq, Enum) addQuestions = proc q -> do data ResponseView = ResponseView { _name :: String , $(mkTList "QuizModel" [''Quiz, ''Question, ''Response]) qs <- inputMany -< () _email :: Email } type Questions = Rel QuizModel One Quiz Many Question basil ((qs,qz) -> mapM (newQuestion qz) qs) -< (qs, q) responseProj :: Response :-> ResponseView type Responses = Rel QuizModel One Quiz Many Response newQuestion :: Ref QuizModel Quiz -> Question -> M (Ref responseProj = Lens $ ResponseView <$> _name `for` lName <*> questions :: Questions Model code QuizModel Question) _email `for` lEmail questions = Rel One ixQuiz "quiz" Many ixQuestion newQuestion refQuiz question = new ixQuestion responseForm :: Form Response "questions" question responseForm = projectedForm responseProj emptyResponse responses :: Responses (PCons (refQuiz, DR, where emptyResponse :: Response responses = Rel One ixQuiz "quiz" Many ixResponse ixQuestions) PNil) emptyResponse = Response "" (Email "") (Date "now") "responses" listQuizzes :: Web () () [] $(mkTlist "QuizRelations" [''Questions, ''Responses]) listQuizzes = proc () -> do getAnswer :: Web Question Answer instance ERModel QuizModel QuizRelations where qs <- basil' (findAll ixQuiz) -< () getAnswer = form''' answerForm relations = TCons4 questions $ TCons4 responses $ TNil4 case qs of answerForm :: Question -> Form Answer witnesses = WCons ixQuiz $ WCons ixQuestion $ WCons [] -> display X.toHtml -< "No answerForm q = F.plug ((title q +++ X.br) +++) ixResponse $ WNil quizzes yet." $ F.enumRadio [(QA, choiceA q),(QB, choiceB $(deriveAll ''Quiz "PFQuiz") _ -> display (X.concatHtml . map quizWithLink) -< qs q),(QC, choiceC q)] $(deriveAll ''Question "PFQuestion") quizWithLink :: (Ref QuizModel Quiz, Quiz) -> X.Html Nothing $(deriveAll ''Response "PFResponse") quizWithLink (Ref _ i, q) = X.concatHtml homepage = X.toHtml "Welcome to the quiz system." type instance PF Quiz = PFQuiz [ ghtml q, X.br, static (View i) "view" data QuizForm = QuizForm {_subject :: String, _description :: type instance PF Question = PFQuestion , X.br, static (Take i) "take quiz" ] TextArea} type instance PF Response = PFResponse viewQuiz :: Web (Ref QuizModel Quiz) () quizProj :: Quiz :-> QuizForm $(mkLabels [''Quiz, ''Question, ''Response]) viewQuiz = proc ref -> do quizProj = Lens $ QuizForm <$> _subject `for` lSubject $(mkEqualities [''Quiz, ''Question, ''Response]) quiz <- basil find -< ref <*> _description `for` data QuizRoute = List | Add | View Int | Take Int case quiz of (textAreaToString % lDescription) deriving Show Just quiz -> do instance DefaultForm Quiz where form = projectedForm instance ToURL QuizRoute where Just qsR <- basil (findRels DL ixQuestions) -< ref quizProj (Quiz "" "") toURL = gtoURL . from qs <- basil (mapM find) -< instance DefaultForm Question where form = gform Nothing fromURL = fmap to . gfromURL S.toList qsR type M a = Basil QuizModel QuizRelations a $(deriveAll ''QuizRoute "PFQuizRoute") display quizWithQuestions -< (ref, type St = BasilState QuizModel QuizRelations type instance PF QuizRoute = PFQuizRoute quiz, catMaybes qs) type Web i o = WebT (MVar St) IO i o static :: QuizRoute -> String -> X.Html Nothing -> display (const "Not Found") -< () template x = X.thehtml ( (X.header $ linkStyle "/public/ static u s = X.anchor X.! [X.href $ "/" ++ renderURL (toURL quizWithQuestions :: (Ref QuizModel Quiz, Quiz, [Question]) - style.css") u)] X.<< X.toHtml s > X.Html +++ (X.body $ templateHeader +++ x)) handle :: QuizRoute -> Continuation (MVar St) IO () quizWithQuestions (Ref _ i, q, qs) = ghtml q templateHeader = (X.h1 X.! [X.theclass "page"]) (X.toHtml handle Add = Cont () addQuiz +++ X.br "Quiz system") +++ menu handle List = Cont () listQuizzes +++ (X.concatHtml $ intersperse menu = X.unordList X.! [X.theclass "menu"] $ [static Add "add handle (View i) = Cont (Ref ixQuiz i) viewQuiz X.br $ (map ghtml) qs) quiz", static List "list quizzes"] handle (Take i) = Cont (Ref ixQuiz i) takeQuiz +++ static (Take i) "take quiz" linkStyle name = (X.thelink X.! [X.rel "stylesheet", X.href name]) X.noHtml $(deriveAll ''QuizForm "PFQuizForm") type instance PF QuizForm = PFQuizForm $(deriveAll ''ResponseView "PFResponseView")
  • The Quiz Code takeQuiz :: Web (Ref QuizModel Quiz) () takeQuiz = proc ref -> do quiz <- basil find -< ref addQuiz :: Web () () case quiz of instance Formlet Email where formlet x = Email <$> formlet addQuiz = proc () -> do Just quiz -> do (unEmail <$> x) q <- input -< Just qsR <- basil (findRels DL ixQuestions) -< ref data Quiz = Quiz { subject :: String, () qs <- basil (mapM find) -< description :: String} ref <- (basil (x -> new ixQuiz x PNil)) -< S.toList qsR data Question = Question { title :: String, choiceA, q () <- display ghtml -< quiz choiceB, choiceC :: String} _ <- addQuestions -< response <- form'' responseForm -< () data Response = Response { name :: String ref as <- many getAnswer -< , email :: Email display (const $ X.toHtml "added quiz with questions.") -< (catMaybes qs) , date :: Date () display ghtml -< , answers :: [Answer] addQuestions :: Web (Ref QuizModel Quiz) ([Ref QuizModel response {answers = as} } Question]) Nothing -> display (const "Not Found") -< () data Answer = QA | QB | QC deriving (Show, Eq, Enum) addQuestions = proc q -> do data ResponseView = ResponseView { _name :: String , $(mkTList "QuizModel" [''Quiz, ''Question, ''Response]) qs <- inputMany -< () _email :: Email } type Questions = Rel QuizModel One Quiz Many Question basil ((qs,qz) -> mapM (newQuestion qz) qs) -< (qs, q) responseProj :: Response :-> ResponseView type Responses = Rel QuizModel One Quiz Many Response newQuestion :: Ref QuizModel Quiz -> Question -> M (Ref responseProj = Lens $ ResponseView <$> _name `for` lName <*> questions :: Questions Model code QuizModel Question) _email `for` lEmail questions = Rel One ixQuiz "quiz" Many ixQuestion newQuestion refQuiz question = new ixQuestion responseForm :: Form Response "questions" question responseForm = projectedForm responseProj emptyResponse responses :: Responses (PCons (refQuiz, DR, where emptyResponse :: Response responses = Rel One ixQuiz "quiz" Many ixResponse ixQuestions) PNil) emptyResponse = Response "" (Email "") (Date "now") "responses" listQuizzes :: Web () () [] $(mkTlist "QuizRelations" [''Questions, ''Responses]) listQuizzes = proc () -> do getAnswer :: Web Question Answer instance ERModel QuizModel QuizRelations where qs <- basil' (findAll ixQuiz) -< () getAnswer = form''' answerForm relations = TCons4 questions $ TCons4 responses $ TNil4 case qs of answerForm :: Question -> Form Answer witnesses = WCons ixQuiz $ WCons ixQuestion $ WCons [] -> display X.toHtml -< "No answerForm q = F.plug ((title q +++ X.br) +++) ixResponse $ WNil quizzes yet." $ F.enumRadio [(QA, choiceA q),(QB, choiceB $(deriveAll ''Quiz "PFQuiz") _ -> display (X.concatHtml . map quizWithLink) -< qs q),(QC, choiceC q)] $(deriveAll ''Question "PFQuestion") quizWithLink :: (Ref QuizModel Quiz, Quiz) -> X.Html Nothing $(deriveAll ''Response "PFResponse") quizWithLink (Ref _ i, q) = X.concatHtml homepage = X.toHtml "Welcome to the quiz system." type instance PF Quiz = PFQuiz [ ghtml q, X.br, static (View i) "view" data QuizForm = QuizForm {_subject :: String, _description :: type instance PF Question = PFQuestion , X.br, static (Take i) "take quiz" ] TextArea} type instance PF Response = PFResponse viewQuiz :: Web (Ref QuizModel Quiz) () quizProj :: Quiz :-> QuizForm $(mkLabels [''Quiz, ''Question, ''Response]) viewQuiz = proc ref -> do quizProj = Lens $ QuizForm <$> _subject `for` lSubject $(mkEqualities [''Quiz, ''Question, ''Response]) quiz <- basil find -< ref <*> _description `for` data QuizRoute = List | Add | View Int | Take Int case quiz of (textAreaToString % lDescription) deriving Show Just quiz -> do instance DefaultForm Quiz where form = projectedForm instance ToURL QuizRoute where Just qsR <- basil (findRels DL ixQuestions) -< ref quizProj (Quiz "" "") toURL = gtoURL . from qs <- basil (mapM find) -< instance DefaultForm Question where form = gform Nothing fromURL = fmap to . gfromURL S.toList qsR type M a = Basil QuizModel QuizRelations a $(deriveAll ''QuizRoute "PFQuizRoute") display quizWithQuestions -< (ref, type St = BasilState QuizModel QuizRelations type instance PF QuizRoute = PFQuizRoute quiz, catMaybes qs) type Web i o = WebT (MVar St) IO i o static :: QuizRoute -> String -> X.Html Nothing -> display (const "Not Found") -< () template x = X.thehtml ( (X.header $ linkStyle "/public/ static u s = X.anchor X.! [X.href $ "/" ++ renderURL (toURL URL handling quizWithQuestions :: (Ref QuizModel Quiz, Quiz, [Question]) - style.css") u)] X.<< X.toHtml s > X.Html +++ (X.body $ templateHeader +++ x)) handle :: QuizRoute -> Continuation (MVar St) IO () quizWithQuestions (Ref _ i, q, qs) = ghtml q templateHeader = (X.h1 X.! [X.theclass "page"]) (X.toHtml handle Add = Cont () addQuiz +++ X.br "Quiz system") +++ menu handle List = Cont () listQuizzes +++ (X.concatHtml $ intersperse menu = X.unordList X.! [X.theclass "menu"] $ [static Add "add handle (View i) = Cont (Ref ixQuiz i) viewQuiz X.br $ (map ghtml) qs) quiz", static List "list quizzes"] handle (Take i) = Cont (Ref ixQuiz i) takeQuiz +++ static (Take i) "take quiz" linkStyle name = (X.thelink X.! [X.rel "stylesheet", X.href name]) X.noHtml $(deriveAll ''QuizForm "PFQuizForm") type instance PF QuizForm = PFQuizForm $(deriveAll ''ResponseView "PFResponseView")
  • The Quiz Code takeQuiz :: Web (Ref QuizModel Quiz) () takeQuiz = proc ref -> do quiz <- basil find -< ref addQuiz :: Web () () case quiz of instance Formlet Email where formlet x = Email <$> formlet addQuiz = proc () -> do Just quiz -> do (unEmail <$> x) q <- input -< Just qsR <- basil (findRels DL ixQuestions) -< ref data Quiz = Quiz { subject :: String, () qs <- basil (mapM find) -< description :: String} ref <- (basil (x -> new ixQuiz x PNil)) -< S.toList qsR data Question = Question { title :: String, choiceA, q () <- display ghtml -< quiz choiceB, choiceC :: String} _ <- addQuestions -< response <- form'' responseForm -< () data Response = Response { name :: String ref as <- many getAnswer -< , email :: Email display (const $ X.toHtml "added quiz with questions.") -< (catMaybes qs) , date :: Date () display ghtml -< , answers :: [Answer] addQuestions :: Web (Ref QuizModel Quiz) ([Ref QuizModel response {answers = as} } Question]) Nothing -> display (const "Not Found") -< () data Answer = QA | QB | QC deriving (Show, Eq, Enum) addQuestions = proc q -> do data ResponseView = ResponseView { _name :: String , $(mkTList "QuizModel" [''Quiz, ''Question, ''Response]) qs <- inputMany -< () _email :: Email } type Questions = Rel QuizModel One Quiz Many Question basil ((qs,qz) -> mapM (newQuestion qz) qs) -< (qs, q) responseProj :: Response :-> ResponseView type Responses = Rel QuizModel One Quiz Many Response newQuestion :: Ref QuizModel Quiz -> Question -> M (Ref responseProj = Lens $ ResponseView <$> _name `for` lName <*> questions :: Questions Model code QuizModel Question) _email `for` lEmail Controller + View questions = Rel One ixQuiz "quiz" Many ixQuestion newQuestion refQuiz question = new ixQuestion responseForm :: Form Response "questions" question responseForm = projectedForm responseProj emptyResponse responses :: Responses (PCons (refQuiz, DR, where emptyResponse :: Response responses = Rel One ixQuiz "quiz" Many ixResponse ixQuestions) PNil) emptyResponse = Response "" (Email "") (Date "now") "responses" listQuizzes :: Web () () [] $(mkTlist "QuizRelations" [''Questions, ''Responses]) listQuizzes = proc () -> do getAnswer :: Web Question Answer instance ERModel QuizModel QuizRelations where qs <- basil' (findAll ixQuiz) -< () getAnswer = form''' answerForm relations = TCons4 questions $ TCons4 responses $ TNil4 case qs of answerForm :: Question -> Form Answer witnesses = WCons ixQuiz $ WCons ixQuestion $ WCons [] -> display X.toHtml -< "No answerForm q = F.plug ((title q +++ X.br) +++) ixResponse $ WNil $(deriveAll ''Quiz "PFQuiz") $(deriveAll ''Question "PFQuestion") _ Controller + View quizzes yet." -> display (X.concatHtml . map quizWithLink) -< qs quizWithLink :: (Ref QuizModel Quiz, Quiz) -> X.Html $ F.enumRadio [(QA, choiceA q),(QB, choiceB q),(QC, choiceC q)] Nothing $(deriveAll ''Response "PFResponse") quizWithLink (Ref _ i, q) = X.concatHtml homepage = X.toHtml "Welcome to the quiz system." type instance PF Quiz = PFQuiz [ ghtml q, X.br, static (View i) "view" data QuizForm = QuizForm {_subject :: String, _description :: type instance PF Question = PFQuestion , X.br, static (Take i) "take quiz" ] TextArea} type instance PF Response = PFResponse viewQuiz :: Web (Ref QuizModel Quiz) () quizProj :: Quiz :-> QuizForm $(mkLabels [''Quiz, ''Question, ''Response]) viewQuiz = proc ref -> do quizProj = Lens $ QuizForm <$> _subject `for` lSubject $(mkEqualities [''Quiz, ''Question, ''Response]) quiz <- basil find -< ref <*> _description `for` data QuizRoute = List | Add | View Int | Take Int case quiz of (textAreaToString % lDescription) deriving Show Just quiz -> do instance DefaultForm Quiz where form = projectedForm instance ToURL QuizRoute where Just qsR <- basil (findRels DL ixQuestions) -< ref quizProj (Quiz "" "") toURL = gtoURL . from qs <- basil (mapM find) -< instance DefaultForm Question where form = gform Nothing fromURL = fmap to . gfromURL S.toList qsR type M a = Basil QuizModel QuizRelations a $(deriveAll ''QuizRoute "PFQuizRoute") display quizWithQuestions -< (ref, type St = BasilState QuizModel QuizRelations type instance PF QuizRoute = PFQuizRoute quiz, catMaybes qs) type Web i o = WebT (MVar St) IO i o static :: QuizRoute -> String -> X.Html Nothing -> display (const "Not Found") -< () template x = X.thehtml ( (X.header $ linkStyle "/public/ static u s = X.anchor X.! [X.href $ "/" ++ renderURL (toURL URL handling quizWithQuestions :: (Ref QuizModel Quiz, Quiz, [Question]) - style.css") u)] X.<< X.toHtml s > X.Html +++ (X.body $ templateHeader +++ x)) handle :: QuizRoute -> Continuation (MVar St) IO () quizWithQuestions (Ref _ i, q, qs) = ghtml q templateHeader = (X.h1 X.! [X.theclass "page"]) (X.toHtml handle Add = Cont () addQuiz +++ X.br "Quiz system") +++ menu handle List = Cont () listQuizzes +++ (X.concatHtml $ intersperse menu = X.unordList X.! [X.theclass "menu"] $ [static Add "add handle (View i) = Cont (Ref ixQuiz i) viewQuiz X.br $ (map ghtml) qs) quiz", static List "list quizzes"] handle (Take i) = Cont (Ref ixQuiz i) takeQuiz +++ static (Take i) "take quiz" linkStyle name = (X.thelink X.! [X.rel "stylesheet", X.href name]) X.noHtml $(deriveAll ''QuizForm "PFQuizForm") type instance PF QuizForm = PFQuizForm $(deriveAll ''ResponseView "PFResponseView")
  • The Quiz Code takeQuiz :: Web (Ref QuizModel Quiz) () takeQuiz = proc ref -> do quiz <- basil find -< ref addQuiz :: Web () () case quiz of instance Formlet Email where formlet x = Email <$> formlet addQuiz = proc () -> do Just quiz -> do (unEmail <$> x) q <- input -< Just qsR <- basil (findRels DL ixQuestions) -< ref data Quiz = Quiz { subject :: String, () qs <- basil (mapM find) -< description :: String} ref <- (basil (x -> new ixQuiz x PNil)) -< S.toList qsR data Question = Question { title :: String, choiceA, q () <- display ghtml -< quiz choiceB, choiceC :: String} _ <- addQuestions -< response <- form'' responseForm -< () data Response = Response { name :: String ref as <- many getAnswer -< , email :: Email display (const $ X.toHtml "added quiz with questions.") -< (catMaybes qs) , date :: Date () display ghtml -< , answers :: [Answer] addQuestions :: Web (Ref QuizModel Quiz) ([Ref QuizModel response {answers = as} } Question]) Nothing -> display (const "Not Found") -< () data Answer = QA | QB | QC deriving (Show, Eq, Enum) addQuestions = proc q -> do data ResponseView = ResponseView { _name :: String , $(mkTList "QuizModel" [''Quiz, ''Question, ''Response]) qs <- inputMany -< () _email :: Email } type Questions = Rel QuizModel One Quiz Many Question basil ((qs,qz) -> mapM (newQuestion qz) qs) -< (qs, q) responseProj :: Response :-> ResponseView type Responses = Rel QuizModel One Quiz Many Response newQuestion :: Ref QuizModel Quiz -> Question -> M (Ref responseProj = Lens $ ResponseView <$> _name `for` lName <*> questions :: Questions Model code QuizModel Question) _email `for` lEmail Controller + View questions = Rel One ixQuiz "quiz" Many ixQuestion newQuestion refQuiz question = new ixQuestion responseForm :: Form Response "questions" question responseForm = projectedForm responseProj emptyResponse responses :: Responses (PCons (refQuiz, DR, where emptyResponse :: Response responses = Rel One ixQuiz "quiz" Many ixResponse ixQuestions) PNil) emptyResponse = Response "" (Email "") (Date "now") "responses" listQuizzes :: Web () () [] $(mkTlist "QuizRelations" [''Questions, ''Responses]) listQuizzes = proc () -> do getAnswer :: Web Question Answer instance ERModel QuizModel QuizRelations where qs <- basil' (findAll ixQuiz) -< () getAnswer = form''' answerForm relations = TCons4 questions $ TCons4 responses $ TNil4 case qs of answerForm :: Question -> Form Answer witnesses = WCons ixQuiz $ WCons ixQuestion $ WCons [] -> display X.toHtml -< "No answerForm q = F.plug ((title q +++ X.br) +++) ixResponse $ WNil $(deriveAll ''Quiz "PFQuiz") $(deriveAll ''Question "PFQuestion") _ Controller + View quizzes yet." -> display (X.concatHtml . map quizWithLink) -< qs quizWithLink :: (Ref QuizModel Quiz, Quiz) -> X.Html $ F.enumRadio [(QA, choiceA q),(QB, choiceB q),(QC, choiceC q)] Nothing $(deriveAll ''Response "PFResponse") quizWithLink (Ref _ i, q) = X.concatHtml homepage = X.toHtml "Welcome to the quiz system." type instance PF Quiz = PFQuiz [ ghtml q, X.br, static (View i) "view" data QuizForm = QuizForm {_subject :: String, _description :: type instance PF Question = PFQuestion , X.br, static (Take i) "take quiz" ] TextArea} type instance PF Response = PFResponse viewQuiz :: Web (Ref QuizModel Quiz) () quizProj :: Quiz :-> QuizForm $(mkLabels [''Quiz, ''Question, ''Response]) viewQuiz = proc ref -> do quizProj = Lens $ QuizForm <$> _subject `for` lSubject $(mkEqualities [''Quiz, ''Question, ''Response]) quiz <- basil find -< ref <*> _description `for` data QuizRoute = List | Add | View Int | Take Int case quiz of (textAreaToString % lDescription) deriving Show Just quiz -> do instance DefaultForm Quiz where form = projectedForm instance ToURL QuizRoute where Just qsR <- basil (findRels DL ixQuestions) -< ref quizProj (Quiz "" "") toURL = gtoURL . from qs <- basil (mapM find) -< instance DefaultForm Question where form = gform Nothing fromURL = fmap to . gfromURL S.toList qsR type M a = Basil QuizModel QuizRelations a $(deriveAll ''QuizRoute "PFQuizRoute") display quizWithQuestions -< (ref, type St = BasilState QuizModel QuizRelations type instance PF QuizRoute = PFQuizRoute quiz, catMaybes qs) type Web i o = WebT (MVar St) IO i o static :: QuizRoute -> String -> X.Html Nothing -> display (const "Not Found") -< () template x = X.thehtml ( (X.header $ linkStyle "/public/ static u s = X.anchor X.! [X.href $ "/" ++ renderURL (toURL URL handling quizWithQuestions :: (Ref QuizModel Quiz, Quiz, [Question]) - style.css") u)] X.<< X.toHtml s > X.Html +++ (X.body $ templateHeader +++ x)) handle :: QuizRoute -> Continuation (MVar St) IO () quizWithQuestions (Ref _ i, q, qs) = ghtml q Miscellaneous templateHeader = (X.h1 X.! [X.theclass "page"]) (X.toHtml handle Add = Cont () addQuiz +++ X.br "Quiz system") +++ menu handle List = Cont () listQuizzes +++ (X.concatHtml $ intersperse menu = X.unordList X.! [X.theclass "menu"] $ [static Add "add handle (View i) = Cont (Ref ixQuiz i) viewQuiz X.br $ (map ghtml) qs) quiz", static List "list quizzes"] handle (Take i) = Cont (Ref ixQuiz i) takeQuiz +++ static (Take i) "take quiz" linkStyle name = (X.thelink X.! [X.rel "stylesheet", X.href name]) X.noHtml $(deriveAll ''QuizForm "PFQuizForm") type instance PF QuizForm = PFQuizForm $(deriveAll ''ResponseView "PFResponseView")
  • Adding a Quiz addQuiz :: Web () () addQuiz = proc () → do q ← input () ref ← (basil (λx → new ixQuiz x PNil)) q ← addQuestions ref display (const $ X.toHtml "added quiz with questions.") () 1
  • Adding a Quiz Input type addQuiz :: Web () () addQuiz = proc () → do q ← input () ref ← (basil (λx → new ixQuiz x PNil)) q ← addQuestions ref display (const $ X.toHtml "added quiz with questions.") () 1
  • Adding a Quiz Input type Output type addQuiz :: Web () () addQuiz = proc () → do q ← input () ref ← (basil (λx → new ixQuiz x PNil)) q ← addQuestions ref display (const $ X.toHtml "added quiz with questions.") () 1
  • Adding a Quiz Input type Output type Show a form addQuiz :: Web () () addQuiz = proc () → do q ← input () ref ← (basil (λx → new ixQuiz x PNil)) q ← addQuestions ref display (const $ X.toHtml "added quiz with questions.") () 1
  • Adding a Quiz Input type Output type Show a form addQuiz :: Web () () Store in the database addQuiz = proc () → do q ← input () ref ← (basil (λx → new ixQuiz x PNil)) q ← addQuestions ref display (const $ X.toHtml "added quiz with questions.") () 1
  • Adding a Quiz Input type Output type Show a form addQuiz :: Web () () Store in the database addQuiz = proc () → do q ← input () ref ← (basil (λx → new ixQuiz x PNil)) q ← addQuestions ref display (const $ X.toHtml "added quiz with questions.") () Arrow inputs 1
  • Library 1 Web Interactions
  • Web interactions • Problem: HTTP is stateless • Solution: use continuations to model state
  • The Arc Challenge 1. Ask for a user’s name 2. Show a link “click here” 3. Display their name again
  • Solution arc :: Web () () arc = input &&& link "Click Here" >> display (λx → X.toHtml $ "Hello, " + fst x) > + 4
  • Solution (improved) arc = proc () → do name ← input () link "Click here" () display (λn → X.toHtml $ "Hello, " + n) + name 5
  • Web Interactions • Functional way to describe interactions • Type-safe way of combining them • Related: • Monadic interface
  • Library 2: Views Using generic programming to reduce boilerplate code
  • Generating HTML ghtml :: (Regular a, GHtml (PF a)) ⇒ a → X.Html data Quiz = Quiz{subject :: String , description :: String } Quiz system add quiz list quizzes Quiz Subject: What programming language are you? Description: This quiz is about what kind of programming language you are. view take quiz 6
  • Generating HTML ghtml :: (Regular a, GHtml (PF a)) ⇒ a → X.Html data Quiz = Quiz{subject :: String , description :: String } Quiz system add quiz list quizzes Quiz Subject: What programming language are you? Description: This quiz is about what kind of programming language you are. view take quiz 6
  • Generating HTML Sometimes the generated HTML needs to be changed
  • Generating HTML Sometimes the generated HTML needs to be changed Quiz system add quiz list quizzes data User = User{name :: String User , email :: String Name: chris , password :: String Email: chris@eidhof.nl } Password: haskell98 Continue
  • Solution: custom HTML • Error-prone • What if the input changes? • What if the HTML library changes?
  • Solution: change datatype data User = User{name :: String , email :: String , password :: NoHtml String } • Confuses view and model code • What if there are multiple views?
  • Solution: change datatype data User = User{name :: String , email :: String , password :: NoHtml String } • Confuses view and model code • What if there are multiple views?
  • Non Solution: change datatype data User = User{name :: String , email :: String , password :: NoHtml String } • Confuses view and model code • What if there are multiple views?
  • Solution: view datatypes userToUserView :: User → UserView userToUserView u = UserView (name u) (email u) data UserView = UserView{name :: String , email :: String } 10
  • Solution: view datatypes • We can keep using generic programming • Offer a lot of flexibility for removing, reordering and displaying of fields • We still allow manual HTML
  • Generating forms Sometimes the generated forms need to be changed
  • Generating forms Sometimes the generated forms need to be changed Quiz system add quiz list quizzes Name: Email: Date: Submit
  • Generating forms Sometimes the generated forms need to be changed Quiz system add quiz list quizzes Name: Email: Date: Submit Quiz system add quiz list quizzes subject: description: Larger! Submit
  • Generating forms Sometimes the generated forms need to be changed Quiz system Quiz system add quiz add quiz list quizzes list quizzes Name: name: Email: email: Date: Submit Submit Quiz system add quiz list quizzes subject: description: Larger! Submit
  • Generating forms Sometimes the generated forms need to be changed Quiz system Quiz system add quiz add quiz list quizzes list quizzes Name: name: Quiz system Email: email: add quiz list quizzes Date: Submit subject: Submit Quiz system description: add quiz list quizzes subject: description: Larger! Submit Submit
  • Generic Forms gform :: (Regular a, GFormlet (PF a)) ⇒ Maybe a → Form a 11
  • Generic Forms gform :: (Regular a, GFormlet (PF a)) ⇒ Maybe a → Form a userForm :: Maybe User → Form User userForm = gform 12
  • User Forms gform :: (Regular a, GFormlet (PF a)) ⇒ Maybe a → Form a userForm :: Maybe User → Form UserView userForm = gform ◦ fmap userToUserView 13
  • User Forms gform :: (Regular a, GFormlet (PF a)) ⇒ Maybe a → Form a userForm :: Maybe User → Form UserView userForm = gform ◦ fmap userToUserView How to convert to a User? 13
  • Solution: lenses convertUser :: User → UserView convertUser = Lens (UserView < > name ‘for‘ lName $ email ‘for‘ lEmail ) get convertUser :: (User → UserView) set convertUser :: (UserView → User → User) 13
  • Solution: lenses convertUser :: User → UserView convertUser = Lens (UserView < > name ‘for‘ lName $ email ‘for‘ lEmail ) Construct a view get convertUser :: (User → UserView) set convertUser :: (UserView → User → User) 13
  • Solution: lenses convertUser :: User → UserView convertUser = Lens (UserView < > name ‘for‘ lName $ email ‘for‘ lEmail ) Construct a view get convertUser :: (User → UserView) set convertUser :: (UserView → User → User) Update original with values from view 13
  • Solution: lenses projectedForm :: (a → b) → a → Form a userForm :: User → Form User userForm = projectedForm convertUser 14
  • Library 2: Summary • Using generic programming and view datatypes • Consistent UI • More applications: • API generation • API documentation
  • Library 3: Data Models
  • Current approaches • HaskellDB • Happstack’s data layer • HDBC • Sirenial • ...
  • First approach data Quiz = Quiz {subject :: String , description :: String , questions :: [Question] , responses :: [Response] } data Question = Question{title :: String , choiceA, choiceB, choiceC :: String , quiz :: Quiz } data Response = Response{name :: String , email :: Email , date :: Date , answers :: [Answer] , quiz :: Quiz } 15
  • First approach data Quiz = Quiz {subject :: String , description :: String , questions :: [Question] , responses :: [Response] } data Question = Question{title :: String , choiceA, choiceB, choiceC :: String , quiz :: Quiz } data Response = Response{name :: String , email :: Email , date :: Date , answers :: [Answer] , quiz :: Quiz } 15
  • First approach: problems • How to keep relationships in sync? • How to serialize?
  • Solution: entity relationship models Subject Name Description * 1 Date Response responses Quiz 1 Email questions Answers * ChoiceA Question ChoiceB ChoiceC Title
  • Solution: entity relationship models Attribute Subject Name Description * 1 Date Response responses Quiz 1 Email questions Answers * ChoiceA Question ChoiceB ChoiceC Title
  • Solution: entity relationship models Attribute Subject Name Description Entity * 1 Date Response responses Quiz 1 Email questions Answers * ChoiceA Question ChoiceB ChoiceC Title
  • Solution: entity relationship models Attribute Subject Name Description Entity * 1 Date Response responses Quiz 1 Email questions Answers Relationship * ChoiceA Question ChoiceB ChoiceC Title
  • ER models • Conceptual model • Compile to logical models: • In-memory database • Relational database • Diagrams • ...
  • ER models: constraints 1. Entities should have all the attributes 2. Cardinality is one-to-one, one-to-many, many-to-one, many-to-many 3. Relationships are only between entities in the same ER model 4. Relationships should be initialized when creating a new entity
  • Encoding entities data Quiz = Quiz{subject :: String , description :: String } Subject Description Quiz
  • Encoding entities data Quiz = Quiz{subject :: String , description :: String } Subject Description Quiz 1. Entities should have all the attributes
  • Encoding entities type QuizModel = Quiz :∗: Question :∗: Response :∗: Nil ixQuiz :: Ix QuizModel Quiz ixQuestion :: Ix QuizModel Question ixResponse :: Ix QuizModel Response 17
  • Encoding entities Type-level list type QuizModel = Quiz :∗: Question :∗: Response :∗: Nil ixQuiz :: Ix QuizModel Quiz ixQuestion :: Ix QuizModel Question ixResponse :: Ix QuizModel Response 17
  • Encoding entities Type-level list type QuizModel = Quiz :∗: Question :∗: Response :∗: Nil ixQuiz :: Ix QuizModel Quiz ixQuestion :: Ix QuizModel Question ixResponse :: Ix QuizModel Response Typed indexes 17
  • Encoding relationships 2. Cardinality is one-to-one, one-to-many, many-to-one, many-to-many 3. Relationships are only between entities in the same ER model
  • A first step Quiz data Rel cardL entityL cardR entityR where Rel :: Rel cardL entitityL cardR entityR 1 questions :: Rel QuizModel One Quiz Many Question questions questions = Rel * Question 18
  • A first step Cardinality Quiz data Rel cardL entityL cardR entityR where Rel :: Rel cardL entitityL cardR entityR 1 questions :: Rel QuizModel One Quiz Many Question questions questions = Rel * Question 18
  • Constraining cardinality data One data Many data Cardinality a where One :: Cardinality One Many :: Cardinality Many data Rel cardL entityL cardR entityR where Rel :: Cardinality cardinalityL → Cardinality cardinalityR → Rel cardinalityL l cardinalityR r 19
  • Constraining cardinality Type-level data One data Many data Cardinality a where One :: Cardinality One Many :: Cardinality Many data Rel cardL entityL cardR entityR where Rel :: Cardinality cardinalityL → Cardinality cardinalityR → Rel cardinalityL l cardinalityR r 19
  • Constraining cardinality Type-level data One data Many data Cardinality a where Value-level One :: Cardinality One Many :: Cardinality Many data Rel cardL entityL cardR entityR where Rel :: Cardinality cardinalityL → Cardinality cardinalityR → Rel cardinalityL l cardinalityR r 19
  • Constraining cardinality Type-level data One data Many data Cardinality a where Value-level One :: Cardinality One Many :: Cardinality Many data Rel cardL entityL cardR entityR where Rel :: Cardinality cardinalityL → Cardinality cardinalityR → Rel cardinalityL l cardinalityR r 2. Cardinality is one-to-one, one-to-many, many-to-one, many-to-many 19
  • Constraining entities Quiz data Rel entities cardinalityL l cardinalityR r where Rel :: Cardinality cardinalityL → Ix entities l 1 → Cardinality cardinalityR → Ix entities r → Rel entities cardinalityL l cardinalityR r questions questions :: Rel QuizModel One Quiz Many Question * questions = Rel One ixQuiz Many ixQuestion Question 20
  • Constraining entities List of all entities (type-level) Quiz data Rel entities cardinalityL l cardinalityR r where Rel :: Cardinality cardinalityL → Ix entities l 1 → Cardinality cardinalityR → Ix entities r → Rel entities cardinalityL l cardinalityR r questions questions :: Rel QuizModel One Quiz Many Question * questions = Rel One ixQuiz Many ixQuestion Question 20
  • Constraining entities List of all entities (type-level) Quiz data Rel entities cardinalityL l cardinalityR r where Rel :: Cardinality cardinalityL → Ix entities l Indexes into list 1 → Cardinality cardinalityR → Ix entities r → Rel entities cardinalityL l cardinalityR r questions questions :: Rel QuizModel One Quiz Many Question * questions = Rel One ixQuiz Many ixQuestion Question 20
  • Constraining entities List of all entities (type-level) Quiz data Rel entities cardinalityL l cardinalityR r where Rel :: Cardinality cardinalityL → Ix entities l Indexes into list 1 → Cardinality cardinalityR → Ix entities r → Rel entities cardinalityL l cardinalityR r questions questions :: Rel QuizModel One Quiz Many Question * questions = Rel One ixQuiz Many ixQuestion Question 3. Relationships are only between entities in the same ER model 20
  • What have we done? • Described entities • Described relationships • Guaranteed 3 out of 4 constraints
  • Encoding relationships 4. Relationships should be initialized when creating a new entity
  • Adding a Question Quiz new :: Ix entities entity → entity → PList entities entity (InitialValues entities entity rels rels) rels ? 1 → Basil entities rels (Ref entities entity) questions * Question 22
  • Adding a Question Quiz new :: Ix entities entity → entity → PList entities entity (InitialValues entities entity rels rels) rels 1 → Basil entities rels (Ref entities entity) questions * Question 22
  • Adding a Question Quiz new :: Ix entities entity → entity → PList entities entity (InitialValues entities entity rels rels) rels 1 → Basil entities rels (Ref entities entity) questions All to-one relationships involving * this entity Question 22
  • InitialValues function type family InitialValues entities r rels originalRels :: ∗ type instance InitialValues entities r Nil o = Nil type instance InitialValues entities r (Rel entities c1 from Many to :∗: xs) o = InitialValues entities r (Rel entities c1 from Many to :∗: xs) o type instance InitialValues entities r (Rel entities c1 from One to :∗: xs) o = AppendIfTrue (TypeEq r from) (InitialValue entities r L (Rel entities c1 from One to) o) (InitialValues entities r (Rel entities c1 from One to :∗: xs) o) 23
  • InitialValues function List of all entities (type-level) type family InitialValues entities r rels originalRels :: ∗ type instance InitialValues entities r Nil o = Nil type instance InitialValues entities r (Rel entities c1 from Many to :∗: xs) o = InitialValues entities r (Rel entities c1 from Many to :∗: xs) o type instance InitialValues entities r (Rel entities c1 from One to :∗: xs) o = AppendIfTrue (TypeEq r from) (InitialValue entities r L (Rel entities c1 from One to) o) (InitialValues entities r (Rel entities c1 from One to :∗: xs) o) 23
  • InitialValues function List of all entities (type-level) Current entity type type family InitialValues entities r rels originalRels :: ∗ type instance InitialValues entities r Nil o = Nil type instance InitialValues entities r (Rel entities c1 from Many to :∗: xs) o = InitialValues entities r (Rel entities c1 from Many to :∗: xs) o type instance InitialValues entities r (Rel entities c1 from One to :∗: xs) o = AppendIfTrue (TypeEq r from) (InitialValue entities r L (Rel entities c1 from One to) o) (InitialValues entities r (Rel entities c1 from One to :∗: xs) o) 23
  • InitialValues function List of all entities (type-level) Current entity type List of relationships type family InitialValues entities r rels originalRels :: ∗ type instance InitialValues entities r Nil o = Nil type instance InitialValues entities r (Rel entities c1 from Many to :∗: xs) o = InitialValues entities r (Rel entities c1 from Many to :∗: xs) o type instance InitialValues entities r (Rel entities c1 from One to :∗: xs) o = AppendIfTrue (TypeEq r from) (InitialValue entities r L (Rel entities c1 from One to) o) (InitialValues entities r (Rel entities c1 from One to :∗: xs) o) 23
  • InitialValues function List of all entities (type-level) Current entity type List of relationships type family InitialValues entities r rels originalRels :: ∗ type instance InitialValues entities r Nil o = Nil type instance InitialValues entities r (Rel entities c1 from Many to :∗: xs) o = InitialValues entities r (Rel entities c1 from Many to :∗: xs) o type instance InitialValues entities r (Rel entities c1 from One to :∗: xs) o = AppendIfTrue (TypeEq r from) (InitialValue entities r L (Rel entities c1 from One to) o) (InitialValues entities r (Rel entities c1 from One to :∗: xs) o) 23
  • InitialValues function List of all entities (type-level) Current entity type List of relationships type family InitialValues entities r rels originalRels :: ∗ type instance InitialValues entities r Nil o = Nil type instance InitialValues entities r (Rel entities c1 from Many to :∗: xs) o = InitialValues entities r (Rel entities c1 from Many to :∗: xs) o type instance InitialValues entities r (Rel entities c1 from One to :∗: xs) o = AppendIfTrue (TypeEq r from) (InitialValue entities r L (Rel entities c1 from One to) o) (InitialValues entities r (Rel entities c1 from One to :∗: xs) o) 23
  • InitialValues function List of all entities (type-level) Current entity type List of relationships type family InitialValues entities r rels originalRels :: ∗ type instance InitialValues entities r Nil o = Nil type instance InitialValues entities r (Rel entities c1 from Many to :∗: xs) o = InitialValues entities r (Rel entities c1 from Many to :∗: xs) o type instance InitialValues entities r (Rel entities c1 from One to :∗: xs) o = AppendIfTrue (TypeEq r from) (InitialValue entities r L (Rel entities c1 from One to) o) (InitialValues entities r (Rel entities c1 from One to :∗: xs) o) 23
  • InitialValues function List of all entities (type-level) Current entity type List of relationships type family InitialValues entities r rels originalRels :: ∗ type instance InitialValues entities r Nil o = Nil type instance InitialValues entities r (Rel entities c1 from Many to :∗: xs) o = InitialValues entities r (Rel entities c1 from Many to :∗: xs) o type instance InitialValues entities r (Rel entities c1 from One to :∗: xs) o = AppendIfTrue (TypeEq r from) (InitialValue entities r L (Rel entities c1 from One to) o) (InitialValues entities r (Rel entities c1 from One to :∗: xs) o) 4. Relationships should be initialized when creating a new entity 23
  • ER model: encoding entities data Quiz = Quiz {subject :: String, description :: String} data Question = Question{title :: String, choiceA, choiceB, choiceC :: String} data Response = Response{name :: String , email :: Email , date :: Date , answers :: [Answer] } data Answer = QA | QB | QC type QuizModel = Quiz :∗: Question :∗: Response :∗: Nil 28
  • ER model: encoding relationships type Questions = Rel QuizModel One Quiz Many Question type Responses = Rel QuizModel One Quiz Many Response questions :: Questions questions = Rel One ixQuiz "quiz" Many ixQuestion "questions" responses :: Responses responses = Rel One ixQuiz "quiz" Many ixResponse "responses" type QuizRelations = Questions :∗: Responses :∗: Nil 29
  • ER model: wrap up • Enforcing constraints • In-memory backend • Relational backend • Query language
  • ER model: wrap up • Enforcing constraints • In-memory backend Same interface! • Relational backend • Query language
  • Conclusion • Three libraries: model, view, controller • A new start for Haskell web development • Notably missing: AJAX
  • Query language data Person = Person{name :: String, age :: Int} chris = Person "chris" 25 belowDrinkingAge :: Expr Person Bool belowDrinkingAge = age . < . Constant 18 isChris :: Expr Person Bool isChris = Not (belowDrinkingAge) .∧. name . ≡ . Constant "chris" In-memory database eval :: Expr entity a → (entity → a) toSql :: Expr entity a → String Relational database 25
  • In-memory database newtype TypeCache a = TypeCache{ cached :: M.Map Int a} deriving Show type Cache entities = HList (TMap TypeCache entities) type family RelationStorage rel :: ∗ type instance RelationStorage (Rel entities One r1 One r2) = M.Map (Ref entities r1) (Ref entities r2) type instance RelationStorage (Rel entities One r1 Many r2) = M.Map (Ref entities r2) (Ref entities r1) type instance RelationStorage (Rel entities Many r1 One r2) = M.Map (Ref entities r1) (Ref entities r2) type instance RelationStorage (Rel entities Many r1 Many r2) = (M.Map (Ref entities r1) (S.Set (Ref entities r2)) , M.Map (Ref entities r2) (S.Set (Ref entities r1))) 26
  • Relational Database type Table tables atts = HList2 (Attr tables) atts data TableT a = ∀env schema.TableT {unTableT :: Table env schema , trans :: a ⇔ HList schema } table :: (Regular a, GName (PF a), GSchema (PF a) schema) ⇒ a → TableT a class AddRelationship rels tables newTables | rels tables → newTables where addRelationships :: TList4 Rel rels → HList2 TableT tables → HList2 TableT newTables liftOperations :: TList4 Rel rels → Operation tables r → Operation newTables r 27