Relations on the Land of Pure Functional Programming by a Mortal Being
Arnaud Bailly
2016-04-22
Where the author tries to cover the most important aspects of Haskeller’s civilisation in an extremely short span of time, thus probably missing lot of important points and generating frustration in the informed reader, but nevertheless hoping to convey enough tips for the audience to be willing to adopt some of the customs of those strange people.
Haskell’s syntax favours terseness and strives to be close to mathematical language, compare:
with:
\(\{ (x, 2x) | x \in [1,100] \land x\ mod\ 3 = 0 \}\)
Haskellers don’t use lot of punctuation signs but favor using indentation to express nesting
Haskell language is geared towards making composition of expressions straightforward
Referential transparency allows one to factorize all kind of common sub-expressions to remove redundancy
Partial application and composition of functions makes it easy to write function pipelines and build new functions out of old ones
newtype
s provide cheap
encapsulation of other types as they are unpacked by compiler:
One should never refrain from creating such new types, they provide more safety than type aliases
Phantom types provide type-level annotation to add more information to other types
Allow distinguishing between different types with identical representations
Provide thread-safety by ensuring some type variables do not escape local scope
Type classes can be thought as way to define interfaces, asbtracting away implementation details
Type classes can define not only functions, but values and types
Type classes actually define constraints on types that can be accumulated in expressions and checked by the compiler
Type families provide type-level functions, e.g. ways to compute types from types at compilation time
Type families can be nested within
data
and class
definitions to provide
contextual types
They can also be used at toplevel:
data Gratis f a = Effectless a
| forall x. Effectful (f x) (x -> Gratis f a)
data EmailServiceF a where
DoMail :: Emailer -> Email -> EmailServiceF EmailStatus
GetAllEmails :: Confirmation -> EmailServiceF [EmailWithStatus]
type MailService = Gratis EmailServiceF
liftFF :: EmailServiceF a -> MailService a
liftFF c = Effectful c pure
doMail :: Emailer -> Email -> MailService EmailStatus
doMail mailer mail = liftFF $ DoMail mailer mail
doGetAllEmails :: Confirmation -> MailService [ EmailWithStatus ]
doGetAllEmails = liftFF . GetAllEmails
interpret :: MailService a -> ExceptT L.Text (WebStateM s l m) a
interpret (Effectful (DoMail mailer mail) f) =
lift (liftIO $ mailer mail >>= handleSendingResult) >>= interpret . f
interpret (Effectful (GetAllEmails confirmation) f) =
lift (runWithEmails $ doGetEmails confirmation) >>= interpret . f
interpret (Effectless a) = return a
test/FooTest.hs
C-c
C-l
:r
eload until it compileshspec
myTest
QuickCheck is the property-based testing tool for Haskell
Use it for defining formal properties of your code beyond what type system provides
instance Arbitrary ScaleRatio where arbitrary = ...
instance Arbitrary Transaction where arbitrary = ...
prop_scaled_transaction_is_normalized :: Transaction -> ScaleRatio
-> Bool
prop_scaled_transaction_is_normalized tx (ScaleRatio ratio) =
isNormalized tx' && isBalanced tx'
where
tx' = scale ratio tx
sample_v8_Events :: [ByteString]
sample_v8_Events =
["{\"tag\":\"AddedDocuments\",\"contents\":[\"c1f09023b3a9398a1d8a257c372392ab\",[]]}"
, "{\"tag\":\"UpdatedInvestor\",\"contents\":{\"invDocuments\":[],
\"invBankAccount\":{\"bankBranch\":\"Ge\",\"bankAccountName\":\"1W\",\"bankAccountNo\":\"nN\",\"bankName\":\"AU\"},
\"invId\":\"46ed3336bc60a5a423962e9b6343c003\",\"invReferralCode\":{\"refer\":\"\"},\"invAccepttandcs\":false,
\"invLegalEntity\":{\"tag\":\"Corporate\",\"primaryContactPosition\":{\"tag\":\"OtherPosition\",\"contents\":\"jZ\"},\"primaryContact\":{\"personTitle\":\"Y8\",\"personFirstName\":\"qf\",\"personLastName\":\"pz\",\"personIdNumber\":\"yC\",\"personTelephone\":\"nC\",\"personEmail\":\"xwmRJAstJRrvBy1x8FnJ4Y2.6nmGy@opBpRq.sg\"},\"company\":{\"companyPostcode\":\"kT\",\"companyName\":\"-o\",\"companyUEN\":\"LA\",\"legalForm\":\"LTD\",\"companyAddress\":\"oh\"}}}}"
, "{\"tag\":\"NoEvent\",\"contents\":[]}"
, "{\"tag\":\"AddedDocuments\",\"contents\":[\"b9973afdd569f11269fe0be736086049\",[]]}"
genAdminActions =
frequency [ (1, return [])
, (1, do
f <- NewListing <$> arbitrary <*> newFacility <*> rate <*> risk
(f:) <$> genAdminActions)
, (10, (AcceptSomeFacility:) <$> genAdminActions)
, (5, (LookupFacilities:) <$> genAdminActions)
, (6, (AdvanceDay:) . (ConfirmRepayments:) <$> genAdminActions)
]
-Wall
-Werror
when compiling: Warnings are often signs of potential
troublesscalac
’s
slownessNewer services are now developed with Servant which provides a way to express APIs at the type level
type CreateJob = ReqBody '[JSON] Job :> Post '[JSON] JobId
type ListJobs = Get '[JSON] [Job]
type RemoveJob = Capture "jobid" JobId :> Delete '[JSON] JobId
type SchedulerApi = "api" :> "scheduler" :> "jobs" :> CreateJob
:<|> "api" :> "scheduler" :> "jobs" :> ListJobs
:<|> "api" :> "scheduler" :> "jobs" :> RemoveJob
Makes it possible to derive server, client or swagger specs from a single declaration
All the woodcuts illustrating this talk are drawn from the Encyclopédie de Diderot et d’Alembert with the exception of: