Haskell in Depth [1 ed.] 9781617295409

Turn the corner from “Haskell student” to “Haskell developer.” Haskell in Depth explores the important language features

779 84 3MB

English Pages 664 Year 2021

Report DMCA / Copyright

DOWNLOAD FILE

Haskell in Depth [1 ed.]
 9781617295409

Table of contents :
Haskell in Depth
brief contents
contents
foreword
preface
Functional programming
Type system
Lazy evaluation
Tooling around Haskell
What can be done using Haskell: Libraries
acknowledgments
about this book
Who should read this book
How this book is organized: A roadmap
About the code
Getting the sources
Using cabal
Using stack
liveBook discussion forum
about the author
about the cover illustration
Part 1: Core Haskell
Chapter 1: Functions and types
1.1 Solving problems in the GHCi REPL with functions
1.2 From GHCi and String to GHC and Text
1.3 Functional programs as sets of IO actions
1.4 Embracing pure functions
1.4.1 Separating I/O from pure functions
1.4.2 Computing the most frequent words by sorting them
1.4.3 Formatting reports
1.4.4 Rule them all with IO actions
Chapter 2: Type classes
2.1 Manipulating a radar antenna with type classes
2.1.1 The problem at hand
2.1.2 Rotating a radar antenna with Eq, Enum, and Bounded
2.1.3 Combining turns with Semigroup and Monoid
2.1.4 Printing and reading data with Show and Read
2.1.5 Testing functions with Ord and Random
2.2 Issues with numbers and text
2.2.1 Numeric types and type classes
2.2.2 Numeric conversions
2.2.3 Computing with fixed precision
2.2.4 More about Show and Read
2.2.5 Converting recursive types to strings
2.3 Abstracting computations with type classes
2.3.1 An idea of a computational context and a common behavior
2.3.2 Exploring different contexts in parallel
2.3.3 The do notation
2.3.4 Folding and traversing
Chapter 3: Developing an application: Stock quotes
3.1 Setting the scene
3.1.1 Inputs
3.1.2 Outputs
3.1.3 Project structure
3.2 Exploring design space
3.2.1 Designing the user interface
3.2.2 Dealing with input data
3.2.3 Formatting reports
3.2.4 Plotting charts
3.2.5 Project dependencies overview
3.3 Implementation details
3.3.1 Describing data
3.3.2 Plotting charts
3.3.3 Preparing reports
3.3.4 Implementing the user interface
3.3.5 Connecting parts
Part 2: Introduction to application design
Chapter 4: Haskell development with modules, packages, and projects
4.1 Organizing Haskell code with modules
4.1.1 Module structure, imports and exports, and module hierarchy
4.1.2 Custom Preludes
4.1.3 Example: containers-mini
4.2 Understanding Haskell packages
4.2.1 Packages at the GHC level
4.2.2 Cabal packages and Hackage
4.3 Tools for project development
4.3.1 Dependency management
4.3.2 Haskell projects as a collection of packages
4.3.3 Common project management activities and tools
Chapter 5: Monads as practical functionality providers
5.1 Basic monads in use: Maybe, Reader, Writer
5.1.1 Maybe monad as a line saver
5.1.2 Carrying configuration all over the program with Reader
5.1.3 Writing logs via Writer
5.2 Maintaining state via the State monad
5.2.1 Basic examples with the State monad
5.2.2 Parsing arithmetic expressions with State
5.2.3 RWS monad to rule them all: The game of dice
5.3 Other approaches to mutability
5.3.1 Mutable references in the IO monad
5.3.2 Mutable references in the ST monad
Chapter 6: Structuring programs with monad transformers
6.1 The problem of combining monads
6.1.1 Evaluating expressions in reverse Polish notation
6.1.2 Introducing monad transformers and monad stacks
6.2 IO-based monad transformer stacks
6.2.1 Describing a monad stack
6.2.2 Exploiting monad stack functionality
6.2.3 Running an application
6.2.4 Can we do it without RWST?
6.3 What is a monad transformer?
6.3.1 Step 0: Defining a type for a transformer
6.3.2 Step 1: Turning a monad stack into a monad
6.3.3 Step 2: Implementing the full monad stack functionality
6.3.4 Step 3: Supplying additional functionality
6.3.5 Using a transformer
6.4 Monad transformers in the Haskell libraries
6.4.1 Identity is where it all starts
6.4.2 An overview of the most common monad transformers
Part 3: Quality assurance
Chapter 7: Error handling and logging
7.1 Overview of error-handling mechanisms in Haskell
7.1.1 The idea of exceptions
7.1.2 To use or not to use?
7.1.3 Programmable exceptions vs. GHC runtime exceptions
7.2 Programmable exceptions in monad stacks
7.2.1 The ExceptT monad transformer
7.2.2 Example: Evaluating RPN expressions
7.3 GHC runtime exceptions
7.3.1 An idea of extensible exceptions
7.3.2 Throwing exceptions
7.3.3 Catching exceptions
7.4 Example: Accessing web APIs and GHC exceptions
7.4.1 Application components
7.4.2 Exception-handling strategies
7.5 Logging
7.5.1 An overview of the monad-logger library
7.5.2 Introducing logging with monad-logger into the suntimes project
Chapter 8: Writing tests
8.1 Setting a scene: IPv4 filtering application
8.1.1 Development process overview
8.1.2 Initial implementation
8.2 Testing the IPv4 filtering application
8.2.1 Overview of approaches to testing
8.2.2 Testing Cabal projects with tasty
8.2.3 Specifications writing and checking with Hspec
8.2.4 Property-based testing with Hedgehog
8.2.5 Golden tests with tasty-golden
8.3 Other approaches to testing
8.3.1 Testing functions à la the REPL with doctest
8.3.2 Lightweight verification with LiquidHaskell
8.3.3 Code quality with hlint
Chapter 9: Haskell data and code at run time
9.1 A mental model for Haskell memory usage at run time
9.1.1 General memory structure and closures
9.1.2 Primitive unboxed data types
9.1.3 Representing data and code in memory with closures
9.1.4 A detour: Lifted types and the concept of strictness
9.2 Control over evaluation and memory usage
9.2.1 Controlling strictness and laziness
9.2.2 Defining data types with unboxed values
9.3 Exploring compiler optimizations by example
9.3.1 Optimizing code manually
9.3.2 Looking at GHC Core
Chapter 10: Benchmarking and profiling
10.1 Benchmarking functions with criterion
10.1.1 Benchmarking implementations of a simple function
10.1.2 Benchmarking an IPv4 filtering application
10.2 Profiling execution time and memory usage
10.2.1 Simulating iplookup usage in the real world
10.2.2 Analyzing execution time and memory allocation
10.2.3 Analyzing memory usage
10.3 Tuning performance of the IPv4 filtering application
10.3.1 Choosing the right data structure
10.3.2 Squeezing parseIP performance
Part 4: Advanced Haskell
Chapter 11: Type system advances
11.1 Haskell types 101
11.1.1 Terms, types, and kinds
11.1.2 Delivering information with types
11.1.3 Type operators
11.2 Data kinds and type-level literals
11.2.1 Promoting types to kinds and values to types
11.2.2 Type-level literals
11.3 Computations over types with type families
11.3.1 Open and closed type synonym families
11.3.2 Example: Avoid character escaping in GHCi
11.3.3 Data families
11.3.4 Associated families
11.4 Generalized algebraic data types
11.4.1 Example: Representing dynamically typed values with GADTs
11.4.2 Example: Representing arithmetic expressions with GADTs
11.5 Arbitrary-rank polymorphism
11.5.1 The meaning
11.5.2 Use cases
11.6 Advice on dealing with type errors
11.6.1 Be explicit about types
11.6.2 Ask the compiler
11.6.3 Saying more about errors
Chapter 12: Metaprogramming in Haskell
12.1 Deriving instances
12.1.1 Basic deriving strategies
12.1.2 The problem of type safety and generalized newtype deriving
12.1.3 Deriving by an example with DerivingVia
12.2 Data-type-generic programming
12.2.1 Generic data-type representation
12.2.2 Example: Generating SQL queries
12.3 Template Haskell and quasiquotes
12.3.1 A tutorial on Template Haskell
12.3.2 Example: Generating remote function calls
Chapter 13: More about types
13.1 Types for specifying a web API
13.1.1 Implementing a web API from scratch
13.1.2 Implementing a web service with servant
13.2 Toward dependent types with singletons
13.2.1 Safety in Haskell programs
13.2.2 Example: Unsafe interface for elevators
13.2.3 Dependent types and substituting them with singletons
13.2.4 Example: Safe interface for elevators
Part 5: Haskell toolkit
Chapter 14: Data-processing pipelines
14.1 Streaming data
14.1.1 General components and naive implementation
14.1.2 The streaming package
14.2 Approaching an implementation of pipeline stages
14.2.1 Reading and writing data efficiently
14.2.2 Parsing data with parser combinators
14.2.3 Accessing data with lenses
14.3 Example: Processing COVID-19 data
14.3.1 The task
14.3.2 Processing data
14.3.3 Organizing the pipeline
Chapter 15: Working with relational databases
15.1 Setting up an example
15.1.1 Sample database
15.1.2 Sample queries
15.1.3 Data representation in Haskell
15.2 Haskell database connectivity
15.2.1 Connecting to a database
15.2.2 Relating Haskell data types to database types
15.2.3 Constructing and executing SELECT queries
15.2.4 Manipulating data in a database
15.2.5 Solving tasks by issuing many queries
15.3 The postgresql-simple library
15.3.1 Connecting to a database
15.3.2 Relating Haskell data types to database types
15.3.3 Executing queries
15.4 The hasql ecosystem
15.4.1 Structuring programs with hasql
15.4.2 Constructing type-safe SQL statements
15.4.3 Implementing database sessions
15.4.4 Running database sessions
15.4.5 The need for low-level operations and decoding data manually
15.5 Generating SQL with opaleye
15.5.1 Structuring programs with opaleye
15.5.2 Describing database tables and their fields
15.5.3 Writing queries
15.5.4 Running queries
Chapter 16: Concurrency
16.1 Running computations concurrently
16.1.1 An implementation of concurrency in GHC
16.1.2 Low-level concurrency with threads
16.1.3 High-level concurrency with the async package
16.2 Synchronization and communication
16.2.1 Synchronized mutable variables and channels
16.2.2 Software transactional memory (STM)
appendix: Further reading
Books
Research papers
index
Symbols
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z

Polecaj historie