├── 9781430262503.jpg ├── LICENSE.txt ├── README.md ├── appendixB ├── Setup.hs ├── appendixB.cabal └── src │ └── Main.hs ├── chapter1 ├── Setup.hs ├── chapter1.cabal └── src │ └── Main.hs ├── chapter10 ├── Setup.hs ├── chapter10.cabal └── src │ ├── Args.hs │ ├── Chapter10 │ ├── Builder.hs │ ├── Parser.hs │ ├── TypeClasses.hs │ └── Types.hs │ └── Main.hs ├── chapter11 ├── Setup.hs ├── chapter11.cabal └── src │ ├── Chapter11 │ ├── Database.hs │ ├── Gender.hs │ ├── Query.hs │ └── Update.hs │ └── Main.hs ├── chapter12 ├── Setup.hs ├── chapter12.cabal ├── fay │ ├── Simple.hs │ ├── Simple.html │ ├── ToScotty.hs │ └── ToScotty.html └── src │ ├── Chapter12 │ └── Database.hs │ ├── ScottyMain.hs │ └── YesodMain.hs ├── chapter13 ├── Setup.hs ├── chapter13.cabal └── src │ └── Chapter13 │ ├── CategoriesFnDeps.hs │ ├── CategoriesTyFams.hs │ ├── CheckPresents.idr │ ├── CheckPresentsFnDeps.hs │ ├── CheckPresentsPromoted.hs │ ├── CheckPresentsTyFams.hs │ ├── CheckRangesPromoted.hs │ ├── Database.hs │ ├── DatabaseSpliced.hs │ ├── GADTs.hs │ ├── Initial.hs │ ├── ListL.idr │ ├── Numbers.hs │ ├── Numbers.idr │ ├── Tree.idr │ ├── Users.hs │ └── VectorsLits.hs ├── chapter14 ├── Setup.hs ├── chapter14.cabal ├── offer-description.html ├── src │ ├── Chapter14 │ │ ├── Description.ag │ │ ├── Expr.ag │ │ ├── Expr2.ag │ │ ├── ExprMonad.hs │ │ ├── ExprType.ag │ │ ├── OfferType.ag │ │ ├── Origami.hs │ │ ├── Presents.ag │ │ ├── Simple.ag │ │ └── SimpleNoAG.hs │ └── Main.hs └── uuagc_options ├── chapter15 ├── Setup.hs ├── chapter15.cabal ├── src │ └── Chapter15 │ │ ├── BinaryTree.hs │ │ ├── BinaryTreeFV.idr │ │ └── RevFV.idr └── test │ ├── HSpec.hs │ └── Tasty.hs ├── chapter16 ├── Setup.hs ├── chapter16.cabal └── src │ └── Chapter16 │ └── FreeMonads.hs ├── chapter2 ├── Setup.hs ├── chapter2.cabal └── src │ └── Chapter2 │ ├── DataTypes.hs │ ├── DefaultValues.hs │ ├── Section2 │ └── Example.hs │ └── SimpleFunctions.hs ├── chapter3 ├── Setup.hs ├── chapter3.cabal └── src │ └── Chapter3 │ ├── Comprehensions.hs │ ├── FnsParams.hs │ ├── Lists.hs │ ├── MoreModules.hs │ ├── Origami.hs │ └── ParamPoly.hs ├── chapter4 ├── Setup.hs ├── chapter4.cabal └── src │ └── Chapter4 │ ├── Containers.hs │ ├── FunctorsFoldables.hs │ ├── MinimumPrice.hs │ └── TypeClasses.hs ├── chapter5 ├── .gitignore ├── Setup.hs ├── chapter5.cabal ├── profiling-example.hp └── src │ ├── Chapter5 │ ├── Annotations.hs │ ├── Infinite.hs │ └── Problems.hs │ └── Main.hs ├── chapter6 ├── Setup.hs ├── chapter6.cabal └── src │ └── Chapter6 │ ├── CombinatorsState.hs │ ├── IncompleteData.hs │ ├── KMeans.hs │ ├── KMeansLens.hs │ ├── KMeansRWS.hs │ ├── KMeansState.hs │ ├── KMeansStateLens.hs │ ├── Lens.hs │ ├── Lens2.hs │ ├── ReaderWriter.hs │ ├── STRef.hs │ ├── StateLenses.hs │ └── Vector.hs ├── chapter7 ├── Setup.hs ├── chapter7.cabal └── src │ ├── Chapter7 │ ├── APriori │ │ ├── FirstImpl.hs │ │ ├── Types.hs │ │ └── WithMonads.hs │ ├── CombiningMonads.hs │ ├── Graph.hs │ ├── MonadPlus.hs │ ├── MoreThanOneValue.hs │ └── UnderAMonad.hs │ └── Main.hs ├── chapter8 ├── Setup.hs ├── chapter8.cabal └── src │ ├── Chapter8 │ └── APriori │ │ ├── Par.hs │ │ ├── Par2.hs │ │ └── Types.hs │ ├── Distr.hs │ ├── Par.hs │ └── Stm.hs ├── chapter9 ├── Setup.hs ├── chapter9.cabal └── src │ ├── Basic.hs │ ├── Chapter9 │ └── Types.hs │ ├── Client.hs │ ├── Exceptions.hs │ ├── Server.hs │ └── Streams.hs └── contributing.md /9781430262503.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/beg-haskell/aaacbf047d553e6177c38807e662cc465409dffd/9781430262503.jpg -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/beg-haskell/aaacbf047d553e6177c38807e662cc465409dffd/LICENSE.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apress Source Code 2 | 3 | This repository accompanies [*Beginning Haskell*](http://www.apress.com/9781430262503) by Alejandro Serrano Mena (Apress, 2014). 4 | 5 |  6 | 7 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 8 | 9 | ## Releases 10 | 11 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 12 | 13 | ## Contributions 14 | 15 | See the file Contributing.md for more information on how you can contribute to this repository. 16 | -------------------------------------------------------------------------------- /appendixB/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /appendixB/appendixB.cabal: -------------------------------------------------------------------------------- 1 | name: appendixB 2 | version: 0.1 3 | cabal-version: >=1.2 4 | build-type: Simple 5 | author: serras 6 | 7 | executable tardis 8 | build-depends: base >= 4, tardis 9 | hs-source-dirs: src 10 | ghc-options: -Wall 11 | main-is: Main.hs 12 | 13 | -------------------------------------------------------------------------------- /appendixB/src/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RecursiveDo #-} 2 | 3 | module Main where 4 | 5 | import Control.Monad.Tardis 6 | 7 | main :: IO () 8 | main = print $ sumListTardis [1,2,3,4] 9 | 10 | sumListTardis :: [Int] -> [(Int,Int)] 11 | sumListTardis lst = evalTardis (sumListTardis' lst) (0, 0) 12 | 13 | sumListTardis' :: [Int] -> Tardis Int Int [(Int,Int)] 14 | sumListTardis' (x:xs) = do 15 | sumFw <- getPast 16 | let newFw = sumFw + x 17 | sendFuture $ newFw 18 | rec let newBw = sumBw + x 19 | sendPast $ newBw 20 | sumBw <- getFuture 21 | rest <- sumListTardis' xs 22 | return $ (newFw, newBw):rest 23 | sumListTardis' [] = return [] 24 | -------------------------------------------------------------------------------- /chapter1/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /chapter1/chapter1.cabal: -------------------------------------------------------------------------------- 1 | name: chapter1 2 | version: 0.1 3 | cabal-version: >=1.2 4 | build-type: Simple 5 | author: serras 6 | 7 | executable chapter1 8 | hs-source-dirs: src 9 | main-is: Main.hs 10 | build-depends: base >= 4 11 | ghc-options: -Wall 12 | 13 | -------------------------------------------------------------------------------- /chapter1/src/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | 4 | 5 | main::IO() 6 | main = undefined -------------------------------------------------------------------------------- /chapter10/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /chapter10/chapter10.cabal: -------------------------------------------------------------------------------- 1 | name: chapter10 2 | version: 0.1 3 | cabal-version: >=1.2 4 | build-type: Simple 5 | author: serras 6 | 7 | executable chapter10 8 | hs-source-dirs: src 9 | main-is: Main.hs 10 | build-depends: base >= 4, conduit, bytestring, text, random, mtl, attoparsec, attoparsec-conduit, aeson, unordered-containers, lens, lens-aeson 11 | ghc-options: -Wall 12 | other-modules: Chapter10.Builder, Chapter10.Parser, Chapter10.TypeClasses 13 | 14 | executable chapter10-args 15 | hs-source-dirs: src 16 | main-is: Args.hs 17 | build-depends: base >= 4, optparse-applicative 18 | ghc-options: -Wall 19 | -------------------------------------------------------------------------------- /chapter10/src/Args.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Options.Applicative 4 | 5 | data Args = Args String Bool -- data type holding the arguments 6 | deriving Show 7 | 8 | args :: Parser Args -- read the arguments 9 | args = Args <$> strOption (long "file" <> help "Database of clients to load") 10 | <*> switch (long "json" <> help "Whether the database uses JSON") 11 | 12 | argsInfo :: ParserInfo Args -- define arguments + help text 13 | argsInfo = info args fullDesc 14 | 15 | main :: IO () 16 | main = do Args fPath json <- execParser argsInfo 17 | print (fPath, json) 18 | -------------------------------------------------------------------------------- /chapter10/src/Chapter10/Builder.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Chapter10.Builder where 4 | 5 | import Data.Conduit 6 | import qualified Data.Conduit.Binary as B 7 | import qualified Data.Conduit.List as L 8 | import qualified Data.Conduit.Text as T 9 | import Data.Monoid 10 | import Data.Text.Lazy 11 | import Data.Text as T 12 | import qualified Data.Text.Lazy.Builder as B 13 | import qualified Data.Text.Lazy.Builder.Int as B 14 | 15 | import Chapter10.Types 16 | 17 | saveClients :: FilePath -> [Client Int] -> IO () 18 | saveClients fpath clients = runResourceT $ 19 | L.sourceList clients $$ L.map clientToText' =$= L.map (toStrict . B.toLazyText) 20 | =$= L.concatMap (\x -> [x, "\n"]) -- write '\n' between clients 21 | =$= T.encode T.utf8 =$ B.sinkFile fpath 22 | 23 | clientToText :: Client Int -> T.Text 24 | clientToText (GovOrg i n) = 25 | "client(gov," <> escapeString (show i) <> "," <> escapeString n <> ")" 26 | clientToText (Company i n p d) = 27 | "client(com," <> escapeString (show i) <> "," <> escapeString n <> "," 28 | <> personToText p <> "," <> escapeString d <> ")" 29 | clientToText (Individual i p) = 30 | "client(ind," <> escapeString (show i) <> "," <> personToText p <> ")" 31 | 32 | personToText :: Person -> T.Text 33 | personToText (Person f l) = "person(" <> escapeString f <> "," <> escapeString l <> ")" 34 | 35 | escapeString :: String -> T.Text 36 | escapeString = T.replace "\n" "\\n" . T.replace "," "\\," . 37 | T.replace "(" "\\(" . T.replace ")" "\\(" . T.pack 38 | 39 | clientToText' :: Client Int -> B.Builder 40 | clientToText' (GovOrg i n) = 41 | "client(gov," <> B.decimal i <> B.singleton ',' 42 | <> B.fromText (escapeString n) <> B.singleton ')' 43 | clientToText' (Company i n p d) = 44 | "client(com," <> B.decimal i <> B.singleton ',' 45 | <> B.fromText (escapeString n) <> B.singleton ',' 46 | <> personToText' p <> B.singleton ',' 47 | <> B.fromText (escapeString d) <> B.singleton ')' 48 | clientToText' (Individual i p) = 49 | "client(ind," <> B.decimal i <> B.singleton ',' 50 | <> personToText' p <> B.singleton ')' 51 | 52 | personToText' :: Person -> B.Builder 53 | personToText' (Person f l) = 54 | "person(" <> B.fromText (escapeString f) <> B.singleton ',' 55 | <> B.fromText (escapeString l) <> B.singleton ')' 56 | 57 | -------------------------------------------------------------------------------- /chapter10/src/Chapter10/Parser.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Chapter10.Parser where 4 | 5 | import Control.Applicative 6 | import Data.Text 7 | import Data.Attoparsec.Text 8 | 9 | import Data.Conduit 10 | import Data.Conduit.Attoparsec 11 | import qualified Data.Conduit.Binary as B 12 | import qualified Data.Conduit.Text as T 13 | 14 | import Chapter10.Types 15 | 16 | data GreetingYear = GreetingYear Text Int 17 | data GreetingYear' = GreetingYear' Greeting Int 18 | 19 | data Greeting = Hello | Bye 20 | 21 | greetingYearParser :: Parser GreetingYear 22 | greetingYearParser = GreetingYear <$> (string "hello" <|> string "bye") <*> decimal 23 | 24 | greetingYearParserSpace :: Parser GreetingYear 25 | greetingYearParserSpace = (\g _ y -> GreetingYear g y) <$> 26 | (string "hello" <|> string "bye") <*> char ' ' <*> decimal 27 | 28 | greetingYearParserSpace' :: Parser GreetingYear 29 | greetingYearParserSpace' = GreetingYear <$> (string "hello" <|> string "bye") <* char ' ' <*> decimal 30 | 31 | 32 | aChar :: Parser Char 33 | aChar = (const ',') <$> (string "\\,") 34 | <|> (const '\n') <$> (string "\\n") 35 | <|> (const '(') <$> (string "\\(") 36 | <|> (const ')') <$> (string "\\)") 37 | <|> satisfy (notInClass ",\n()") 38 | 39 | aString' :: Parser String 40 | aString' = ((:) <$> aChar <*> aString') <|> (pure "") 41 | 42 | aString :: Parser String 43 | aString = many aChar 44 | 45 | aPerson :: Parser Person 46 | aPerson = Person <$ string "person(" <*> aString <* char ',' <*> aString <* char ')' 47 | 48 | aClient :: Parser (Client Int) 49 | aClient = GovOrg <$ string "client(gov," <*> (decimal > "Only integer ids") 50 | <* char ',' <*> aString <* char ')' 51 | <|> Company <$ string "client(com," <*> decimal 52 | <* char ',' <*> aString <* char ',' 53 | <*> aPerson <* char ',' <*> aString <* char ')' 54 | <|> Individual <$ string "client(ind," <*> decimal 55 | <* char ',' <*> aPerson <* char ')' 56 | 57 | parseClients :: Parser [Client Int] 58 | parseClients = sepBy aClient (char '\n') 59 | 60 | loadClients :: FilePath -> IO [Client Int] 61 | loadClients fPath = runResourceT $ 62 | B.sourceFile fPath $$ T.decode T.utf8 =$ sinkParser parseClients 63 | 64 | -------------------------------------------------------------------------------- /chapter10/src/Chapter10/TypeClasses.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-} 2 | 3 | module Chapter10.TypeClasses where 4 | 5 | import Control.Applicative 6 | import Data.Foldable 7 | import Data.Monoid 8 | import Data.Traversable 9 | 10 | data BinaryTree2 a = Node2 a (BinaryTree2 a) (BinaryTree2 a) 11 | | Leaf2 12 | deriving (Show, Functor, Foldable, Traversable) 13 | 14 | {- 15 | instance Functor BinaryTree2 where 16 | fmap f (Node2 x l r) = Node2 (f x) (fmap f l) (fmap f r) 17 | fmap _ Leaf2 = Leaf2 18 | 19 | instance Foldable BinaryTree2 where 20 | foldMap f (Node2 x l r) = (f x) <> (foldMap f l) <> (foldMap f r) 21 | foldMap _ Leaf2 = mempty 22 | 23 | instance Traversable BinaryTree2 where 24 | traverse f (Node2 x l r) = Node2 <$> f x <*> traverse f l <*> traverse f r 25 | traverse _ Leaf2 = pure Leaf2 26 | -} 27 | -------------------------------------------------------------------------------- /chapter10/src/Chapter10/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances, FlexibleContexts, DeriveGeneric, OverloadedStrings #-} 2 | 3 | module Chapter10.Types where 4 | 5 | import Data.Aeson 6 | import Data.Aeson.Types 7 | import Control.Lens ((^?)) 8 | import Control.Lens.Aeson 9 | import Data.Text 10 | import GHC.Generics 11 | import Control.Applicative 12 | 13 | import Data.Conduit 14 | import qualified Data.Conduit.Binary as B 15 | import qualified Data.Conduit.List as L 16 | import qualified Data.ByteString.Lazy as LB 17 | 18 | import qualified Data.HashMap.Strict as M 19 | 20 | data Client i = GovOrg { clientId :: i, clientName :: String } 21 | | Company { clientId :: i, clientName :: String 22 | , person :: Person, duty :: String } 23 | | Individual { clientId :: i, person :: Person } 24 | deriving (Show, Generic) 25 | 26 | data Person = Person { firstName :: String, lastName :: String } 27 | deriving (Show, Read, Generic) 28 | 29 | {- 30 | instance ToJSON i => ToJSON (Client i) 31 | instance FromJSON i => FromJSON (Client i) 32 | instance ToJSON Person 33 | instance FromJSON Person 34 | -} 35 | 36 | clientToJSON :: Client Integer -> Value 37 | clientToJSON (GovOrg i n) = 38 | object [ "type" .= String "govorg" 39 | , "id" .= Number (fromInteger i) 40 | , "name" .= String (pack n) ] 41 | clientToJSON (Company i n p d) = 42 | object [ "type" .= String "company" 43 | , "id" .= Number (fromInteger i) 44 | , "name" .= String (pack n) 45 | , "person" .= personToJSON p 46 | , "duty" .= String (pack d) ] 47 | clientToJSON (Individual i p) = 48 | object [ "type" .= String "individual" 49 | , "id" .= Number (fromInteger i) 50 | , "person" .= personToJSON p ] 51 | 52 | personToJSON :: Person -> Value 53 | personToJSON (Person f l) = object [ "first" .= String (pack f) 54 | , "last" .= String (pack l) ] 55 | {- 56 | jsonToPerson :: Value -> Maybe Person 57 | jsonToPerson (Object o) = do String f <- M.lookup "first" o 58 | String l <- M.lookup "last" o 59 | return $ Person (unpack f) (unpack l) 60 | jsonToPerson _ = Nothing 61 | -} 62 | 63 | jsonToPersonLens :: Value -> Maybe Person 64 | jsonToPersonLens j = do String f <- j ^? key "first" 65 | String l <- j ^? key "last" 66 | return $ Person (unpack f) (unpack l) 67 | 68 | jsonToClient :: FromJSON i => Value -> Parser (Client i) 69 | jsonToClient (Object o) = 70 | case M.lookup "type" o of 71 | Just (String "govorg") -> GovOrg <$> o .: "id" <*> o .: "name" 72 | Just (String "company") -> Company <$> o .: "id" <*> o .: "name" 73 | <*> o .: "person" <*> o .: "duty" 74 | Just (String "individual") -> Individual <$> o .: "id" <*> o .: "person" 75 | _ -> Control.Applicative.empty 76 | jsonToClient _ = Control.Applicative.empty 77 | 78 | jsonToPerson :: Value -> Parser Person 79 | jsonToPerson (Object o) = Person <$> o .: "first" <*> o .: "last" 80 | jsonToPerson _ = Control.Applicative.empty 81 | 82 | instance ToJSON (Client Integer) where 83 | toJSON = clientToJSON 84 | instance ToJSON Person where 85 | toJSON = personToJSON 86 | 87 | instance FromJSON i => FromJSON (Client i) where 88 | parseJSON = jsonToClient 89 | instance FromJSON Person where 90 | parseJSON = jsonToPerson 91 | 92 | saveClients :: FilePath -> [Client Integer] -> IO () 93 | saveClients fPath clients = runResourceT $ 94 | yield (toJSON clients) $$ L.map (LB.toStrict . encode) =$ B.sinkFile fPath 95 | 96 | 97 | -------------------------------------------------------------------------------- /chapter10/src/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-} 2 | 3 | module Main where 4 | 5 | import Control.Monad.Trans 6 | import Data.Conduit 7 | import qualified Data.Conduit.Binary as B 8 | import qualified Data.Conduit.List as L 9 | import qualified Data.Conduit.Text as T 10 | import Data.Monoid 11 | import Data.Text 12 | import System.Random 13 | 14 | import Chapter10.Builder 15 | import Chapter10.Parser 16 | import Chapter10.TypeClasses 17 | 18 | main :: IO() 19 | main = runResourceT $ 20 | B.sourceFile "clients.db" $$ T.decode T.utf8 =$= 21 | T.lines =$= winnersFile =$= L.concatMap (\x -> [x, "\n"]) =$= 22 | T.encode T.utf8 =$ B.sinkFile "clientsWinners.db" 23 | 24 | winnersFile :: (Monad m, MonadIO m) => Conduit Text m Text 25 | winnersFile = do client <- await 26 | case client of 27 | Nothing -> return () 28 | Just c -> do (w :: Bool) <- liftIO $ randomIO 29 | (y :: Int ) <- liftIO $ randomRIO (0, 3000) 30 | yield $ c <> " " <> (pack $ show w) <> " " <> (pack $ show y) 31 | winnersFile 32 | -------------------------------------------------------------------------------- /chapter11/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /chapter11/chapter11.cabal: -------------------------------------------------------------------------------- 1 | name: chapter11 2 | version: 0.1 3 | cabal-version: >=1.2 4 | build-type: Simple 5 | author: serras 6 | 7 | executable chapter11 8 | hs-source-dirs: src 9 | main-is: Main.hs 10 | build-depends: 11 | base >= 4, 12 | persistent, 13 | persistent-sqlite, 14 | persistent-template, 15 | persistent-postgresql, 16 | esqueleto, 17 | time, 18 | transformers 19 | ghc-options: -Wall 20 | other-modules: 21 | Chapter11.Database, 22 | Chapter11.Gender, 23 | Chapter11.Query, 24 | Chapter11.Update 25 | 26 | -------------------------------------------------------------------------------- /chapter11/src/Chapter11/Database.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell, QuasiQuotes, 2 | TypeFamilies, EmptyDataDecls, 3 | FlexibleContexts, GADTs, 4 | OverloadedStrings #-} 5 | 6 | module Chapter11.Database where 7 | 8 | import Database.Persist.TH 9 | import Data.Time.Clock 10 | 11 | import Chapter11.Gender 12 | 13 | share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| 14 | Country 15 | name String 16 | canWeSend Bool default=False 17 | UniqueCountryName name 18 | deriving Show 19 | Client 20 | firstName String 21 | lastName String 22 | address String 23 | country CountryId 24 | gender Gender Maybe 25 | age Int Maybe 26 | UniqueClient firstName lastName address country 27 | deriving Show 28 | Product 29 | name String 30 | description String 31 | price Double 32 | inStock Int 33 | deriving Show 34 | Purchase 35 | client ClientId 36 | product ProductId 37 | number Int 38 | amount Double 39 | deriving Show 40 | |] 41 | -------------------------------------------------------------------------------- /chapter11/src/Chapter11/Gender.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | 3 | module Chapter11.Gender where 4 | 5 | import Database.Persist.TH 6 | 7 | data Gender = Male | Female 8 | deriving (Show, Read, Eq) 9 | derivePersistField "Gender" 10 | -------------------------------------------------------------------------------- /chapter11/src/Chapter11/Query.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings, NoMonomorphismRestriction, TypeFamilies #-} 2 | 3 | module Chapter11.Query where 4 | 5 | import Control.Monad 6 | import qualified Database.Persist as P -- only Persistent 7 | import Database.Esqueleto -- Persistent + Esqueleto 8 | import Database.Persist.Sqlite (runSqlite) 9 | 10 | import Chapter11.Database 11 | -- import Chapter11.Gender 12 | 13 | getClientById :: (P.PersistQuery m, P.PersistMonadBackend m ~ P.PersistEntityBackend Client) => Int -> m (Maybe Client) 14 | getClientById n = get $ Key (PersistInt64 $ fromIntegral n) 15 | 16 | -- getPurchaseClient :: Purchase -> m (Maybe Client) 17 | getPurchaseClient p = get (purchaseClient p) 18 | 19 | -- getPurchaseClient' :: Int -> m (Maybe Client) 20 | getPurchaseClient' pId = do p <- get $ Key (PersistInt64 $ fromIntegral pId) 21 | case p of 22 | Just p' -> get $ purchaseClient p' 23 | Nothing -> return Nothing 24 | 25 | -- getClientByInfo :: String -> String -> String -> String -> m (Maybe Client) 26 | getClientByInfo fName lName addr cnName = do 27 | cn <- getBy $ UniqueCountryName cnName 28 | case cn of 29 | Just (Entity cId _) -> 30 | do cl <- getBy $ UniqueClient fName lName addr cId 31 | case cl of 32 | Just (Entity _ client) -> return $ Just client 33 | Nothing -> return Nothing 34 | Nothing -> return Nothing 35 | 36 | -- getAdultsOfSpainAndGermany :: m [Entity Client] 37 | getAdultsOfSpainAndGermany = do 38 | Just (Entity spId _) <- getBy $ UniqueCountryName "Spain" 39 | Just (Entity geId _) <- getBy $ UniqueCountryName "Germany" 40 | P.selectList [ ClientCountry P.<-. [spId, geId], ClientAge P.>=. Just 18 ] [] 41 | 42 | -- countAdultsOfSpainAndGermany :: m Integer 43 | countAdultsOfSpainAndGermany = do 44 | Just (Entity spId _) <- getBy $ UniqueCountryName "Spain" 45 | Just (Entity geId _) <- getBy $ UniqueCountryName "Germany" 46 | P.count [ ClientCountry P.<-. [spId, geId], ClientAge P.>=. Just 18 ] 47 | 48 | -- getAdultsOfSpainAndUS :: m [Entity Client] 49 | getAdultsOfSpainAndUS = do 50 | Just (Entity spId _) <- getBy $ UniqueCountryName "Spain" 51 | Just (Entity usId _) <- getBy $ UniqueCountryName "United States of America" 52 | P.selectList ( [ ClientCountry P.==. spId, ClientAge P.>=. Just 18 ] 53 | P.||. [ ClientCountry P.==. usId, ClientAge P.>=. Just 21 ] ) 54 | [ P.Desc ClientAge ] 55 | 56 | -- getProductsPage :: Int -> m [Entity Product] 57 | getProductsPage n = P.selectList [ ] [ P.Asc ProductPrice, P.LimitTo 10, P.OffsetBy ((n-1)*10) ] 58 | 59 | -- getCountriesWithBigBuyers :: m [Country] 60 | getCountriesWithBigBuyers = do 61 | buyers <- P.selectKeysList [ ] [ ] 62 | buyersAndPurchases <- mapM (\b -> P.count [ PurchaseClient P.==. b ] >>= \c -> return (b,c)) buyers 63 | let buyersAndPurchases' = filter (\(_,c) -> c > 3) buyersAndPurchases 64 | mapM (\(b,_) -> do Just cl <- get b 65 | Just cn <- get $ clientCountry cl 66 | return cn) 67 | buyersAndPurchases' 68 | 69 | -- getPeopleOver25 :: m [Entity Client] 70 | getPeopleOver25 = 71 | select $ 72 | from $ \client -> do 73 | where_ (client ^. ClientAge >. just (val 25)) 74 | orderBy [ asc (client ^. ClientLastName), asc (client ^. ClientFirstName) ] 75 | return client 76 | 77 | -- getPeopleOver25FromSpainOrGermany :: m [Entity Client] 78 | getPeopleOver25FromSpainOrGermany = 79 | select $ 80 | from $ \(client, country) -> do 81 | where_ ( client ^. ClientAge >. just (val 25) 82 | &&. country ^. CountryName `in_` valList [ "Spain", "Germany" ] 83 | &&. client ^. ClientCountry ==. country ^. CountryId ) 84 | return client 85 | 86 | -- getPeopleOver25FromSpainOrGermanyJoin :: m [Entity Client] 87 | getPeopleOver25FromSpainOrGermanyJoin = 88 | select $ 89 | from $ \(client `InnerJoin` country) -> do 90 | on (client ^. ClientCountry ==. country ^. CountryId) 91 | where_ ( client ^. ClientAge >. just (val 25) 92 | &&. country ^. CountryName `in_` valList [ "Spain", "Germany" ]) 93 | orderBy [ asc (client ^. ClientLastName), asc (client ^. ClientFirstName) ] 94 | return client 95 | 96 | -- getMoneyByClient :: m [(Entity Client, Value (Maybe Double))] 97 | getMoneyByClient = 98 | select $ 99 | from $ \(client `LeftOuterJoin` purchase) -> do 100 | on (client ^. ClientId ==. purchase ^. PurchaseClient) 101 | groupBy (client ^. ClientId) 102 | let s = sum_ (purchase ^. PurchaseAmount) 103 | return (client, s) 104 | -------------------------------------------------------------------------------- /chapter11/src/Chapter11/Update.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings, NoMonomorphismRestriction #-} 2 | 3 | module Chapter11.Update where 4 | 5 | import Control.Monad 6 | import qualified Database.Persist as P -- only Persistent 7 | import Database.Esqueleto -- Persistent + Esqueleto 8 | import Database.Persist.Sqlite (runSqlite) 9 | import Data.Char 10 | 11 | import Chapter11.Database 12 | -- import Chapter11.Gender 13 | 14 | -- capitalizeNamesSlow :: m () 15 | capitalizeNamesSlow = do 16 | clients <- P.selectList [] [] 17 | mapM_ (\(Entity ident client) -> 18 | let c:rest = clientFirstName client 19 | in P.replace ident $ client { clientFirstName = (toUpper c):rest }) 20 | clients 21 | 22 | -- discount :: m () 23 | discount = do 24 | P.updateWhere [ ProductPrice P.<=. 10000 ] [ ProductPrice P.*=. 0.9 ] 25 | P.updateWhere [ ProductPrice P.>. 10000 ] [ ProductPrice P.*=. 0.97 ] 26 | 27 | -- betterDiscount :: m () 28 | betterDiscount = update $ \product -> do 29 | let totalAmount = sub_select $ 30 | from $ \purchase -> do 31 | where_ $ product ^. ProductId ==. purchase ^. PurchaseProduct 32 | groupBy (purchase ^. PurchaseProduct) 33 | return $ sum_ (purchase ^. PurchaseAmount) 34 | where_ $ isNothing totalAmount ||. totalAmount <. just (val 10) 35 | set product [ ProductPrice *=. val 0.9 ] 36 | 37 | -- cleanProductStock :: m () 38 | cleanProductStock = P.deleteWhere [ ProductInStock P.==. 0 ] 39 | 40 | -- cleanProductStock' :: m () 41 | cleanProductStock' = 42 | delete $ 43 | from $ \product -> do 44 | where_ $ product ^. ProductInStock ==. val 0 45 | &&. (notExists $ from $ \purchase -> 46 | where_ (purchase ^. PurchaseProduct ==. product ^. ProductId)) 47 | -------------------------------------------------------------------------------- /chapter11/src/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Main where 4 | 5 | import Control.Monad.IO.Class 6 | -- import Database.Persist -- only Persistent 7 | import Database.Esqueleto -- Persistent + Esqueleto 8 | import Database.Persist.Sqlite 9 | import Data.Time.Clock 10 | 11 | import Chapter11.Database 12 | import Chapter11.Gender 13 | import Chapter11.Query 14 | 15 | main :: IO () 16 | main = do runSqlite "example.db" $ do 17 | -- Create table structure 18 | runMigration migrateAll 19 | -- Insert initial data 20 | insertInitialData 21 | -- Run a query 22 | c1 <- getClientById 1 23 | liftIO $ print c1 24 | results1 <- getPeopleOver25FromSpainOrGermanyJoin 25 | mapM_ (\(Entity _ r) -> liftIO $ print r) results1 26 | results2 <- getMoneyByClient 27 | mapM_ (\(Entity _ r, Value l) -> liftIO $ print r >> print l) results2 28 | 29 | mainWithExplicitConnection :: IO () 30 | mainWithExplicitConnection = withSqliteConn ":memory:" $ \conn -> 31 | flip runSqlPersistM conn $ do 32 | runMigration migrateAll 33 | 34 | mainWithExplicitPool :: IO () 35 | mainWithExplicitPool = withSqlitePool ":memory:" 3 $ \pool -> 36 | flip runSqlPersistMPool pool $ do 37 | runMigration migrateAll 38 | 39 | insertInitialData :: PersistStore m => m () 40 | insertInitialData = do 41 | spain <- insert $ Country "Spain" True 42 | germany <- insert $ Country "Germany" True 43 | uk <- insert $ Country "United Kingdom" False 44 | _usa <- insert $ Country "United States of America" False 45 | 46 | client1 <- insert $ Client "Alejandro" "Serrano" "Home Town, 1" spain (Just Male) Nothing 47 | client2 <- insert $ Client "Werner" "Heisenberg" "A place in Wurzburg" germany (Just Male) (Just 50) 48 | _client3 <- insert $ Client "Doctor" "Who" "Police Box" uk Nothing Nothing 49 | 50 | let longDescription = "blah blah blah" 51 | product1 <- insert $ Product "Travel to the XIX Century" longDescription 12.3 3 52 | product2 <- insert $ Product "TM-223 Machine" longDescription 1245.0 10 53 | 54 | _ <- insert $ Purchase client1 product1 1 20.0 55 | _ <- insert $ Purchase client1 product2 5 1002.3 56 | _ <- insert $ Purchase client2 product1 3 58.0 57 | 58 | return () 59 | -------------------------------------------------------------------------------- /chapter12/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /chapter12/chapter12.cabal: -------------------------------------------------------------------------------- 1 | name: chapter12 2 | version: 0.1 3 | cabal-version: >=1.2 4 | build-type: Simple 5 | author: serras 6 | 7 | executable scotty-server 8 | build-depends: 9 | base >= 4, 10 | scotty, 11 | transformers, 12 | blaze-html, 13 | aeson, 14 | persistent, 15 | persistent-sqlite, 16 | persistent-template, 17 | http-types, 18 | text, 19 | hamlet, 20 | digestive-functors, 21 | digestive-functors-aeson, 22 | digestive-functors-blaze 23 | hs-source-dirs: src 24 | ghc-options: -Wall -rtsopts 25 | main-is: ScottyMain.hs 26 | other-modules: Chapter12.Database 27 | 28 | executable yesod-server 29 | build-depends: 30 | base >= 4, 31 | yesod, 32 | aeson, 33 | persistent, 34 | persistent-sqlite, 35 | persistent-template 36 | hs-source-dirs: src 37 | ghc-options: -Wall -rtsopts 38 | main-is: YesodMain.hs 39 | other-modules: Chapter12.Database 40 | -------------------------------------------------------------------------------- /chapter12/fay/Simple.hs: -------------------------------------------------------------------------------- 1 | module Simple where 2 | 3 | import Prelude 4 | import FFI 5 | 6 | main :: Fay () 7 | main = alert "Hello from Fay!" 8 | 9 | alert :: String -> Fay () 10 | alert = ffi "alert(%1)" 11 | -------------------------------------------------------------------------------- /chapter12/fay/Simple.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |" 50 | , pack productDescription 51 | , "
" 52 | , "" ] 53 | Nothing -> status notFound404 54 | 55 | get "/product2/:productId" $ do 56 | (productId :: Integer) <- param "productId" 57 | product <- liftIO $ flip Db.runSqlPersistMPool pool $ 58 | Db.get $ Db.Key (Db.PersistInt64 $ fromIntegral productId) 59 | case product of 60 | Just (Product { .. }) -> html $ renderHtml $ 61 | H.html $ do 62 | H.head $ 63 | H.title "Time Machine Store" 64 | H.body $ do 65 | H.h1 $ H.toHtml productName 66 | H.p H.! A.id "descr" $ H.toHtml productDescription 67 | Nothing -> status notFound404 68 | 69 | get "/product3/:productId" $ do 70 | (productId :: Integer) <- param "productId" 71 | product <- liftIO $ flip Db.runSqlPersistMPool pool $ 72 | Db.get $ Db.Key (Db.PersistInt64 $ fromIntegral productId) 73 | case product of 74 | Just p -> html $ renderHtml [shamlet| 75 | 76 | 77 |#{productDescription p} 81 | |] 82 | Nothing -> status notFound404 83 | 84 | get "/json/product/:productId" $ do 85 | (productId :: Integer) <- param "productId" 86 | (product :: Maybe Product) <- 87 | liftIO $ flip Db.runSqlPersistMPool pool $ 88 | Db.get $ Db.Key (Db.PersistInt64 $ fromIntegral productId) 89 | case product of 90 | Just p -> do setHeader "Access-Control-Allow-Origin" "*" 91 | json p 92 | Nothing -> status notFound404 93 | 94 | get "/products" $ do 95 | (products :: [Db.Entity Product]) <- 96 | liftIO $ flip Db.runSqlPersistMPool pool $ Db.selectList [] [] 97 | html $ renderHtml [shamlet| 98 | 99 |
100 |