Most of the datatypes you’re used to follow a simple pattern: the data constructors’ type parameters match up with the kind of the type constructor. This series is about ways to deviate from the regular path and define slightly more exotic types. 1. By placing equality constraints (
~) on a data constructor, we can reduce the number of parameters it has relative to the kind of the type constructor; 2. Using the
forall keyword in a data constructor, we can increase the number of the type parameters it has relative to the kind of the type constructor. The first lessons will establish some foundations, and subsequent lessons will apply these concepts to practical demonstrations.
This lesson explains what it means for data constructors to have constraints, a special constraint called type equality, written as
(~), and why placing a type equality constraint on a constructor has interesting ramifications.
In the previous lesson we looked at an interesting bifunctor, but in this lesson we’re going to come back to the
Data.Bifunctor module and its laws. The
Bifunctor laws are closely related to the
Functor laws which we have already seen, but there are some interesting new things to look at here.
Previously we looked at the connection between
liftA2, their connection to
pure function, and the monoidal structure of applicatives. We will still want to give some closeup attention to a few other applicatives, especially functions and lists, but this lesson will focus on the other operators in the typeclass (
*>) before we move on to those. While you almost never need to implement them yourself – because the compiler can derive them from your implementations of pure and
liftA2) – they are useful, yet often ignored.
Previously we talked about the
Functor for the function type – or, more precisely, for
(->) a, the partially applied function type. Looking carefully at the type of
fmap instantiated to that type
(->) a led us to the revelation that the
fmap is identical to ordinary function composition. In this lesson we again discuss the function type, this time looking at its
Applicative instance. For the purposes of
Monad, and monad transformers, we usually refer to it with a
newtype wrapper called
Reader, so we take our first look at that in this lesson as well.
In this lesson we look at lists and spend a little more time with the connection between applicatives and monoids. We do not typically think of types having more than one valid
Applicative instance. The connection between a type and a typeclass, expressed in an
instance declaration, must be unique. Ideally, there should only one sensible or legal way to implement the functions for that type. And, indeed, that’s how
Functor works, which is why we can easily derive
Functor instances. And since applicatives are functors, we might expect that to carry over, and most of the time it does work out that way. However, applicatives are not mere functors; they are monoidal functors.
GHC is the Haskell compiler. In addition the compiler, GHC also includes a REPL called GHCi (“GHC interactive”), an executable called
runghc to run Haskell source files directly without a compilation step, and a great number of extensions to the Haskell language which can enable features beyond what is given in the Haskell language specification.
Here we demonstrate getting the current time, comparing two moments in time, and converting between a few different timestamp representations. None of the code here involves time zones. This program will not display the same current time as the clock on your wall, but it will behave the same regardless of what part of the world it runs in. This makes the types in this example suitable for a “machine” view of time such as you might use for recording the times of events in a log file, or for scheduling tasks in a multi-threaded or distributed application.
Decorator syntax refers to some syntactic sugar in Python that lets you simultaneously define a function
f and apply a function
g to the function you just defined.
f is called the “decorated” function, and
g is called the “decorator.” Here we look at several examples using decorators in Python, compare them to similar situations in Haskell, and discuss how lambdas let us achieve the same result without having a special syntax for decorators.
iterator is a sort of sequence that, unlike a
list, computes each subsequent value only as it is needed rather than producing the entire list all at once. For example, from a range, we can produce an iterator of numbers. Here we construct an iterator
xs that will produce three values:
xs = iter(range(1, 4)). We can use the
next function to demonstrate pulling values from the iterator one at a time.
Here we look at the closely-related Python functions
enumerate, and the Haskell functions
A semigroup is a type together with a binary operation. The operation must be “closed”, meaning its output has the same type as its operands, and it must be “associative”:
x <> (y <> z) =
(x <> y) <> z. Many sets have more than one such operation over them. Integers, for example, are semigroups under both a “min” and a “max” operation.
Threads are concurrent flows of execution within one process. Haskell chooses to implement its own multithreading abstraction instead of simply using native OS threads directly, because OS threads are slower to initialize and have a greater memory cost. For applications written entirely in Haskell, there is no semantic difference between OS threads and Haskell threads, but there are some reasons to appreciate the distinction between the two. By default, Haskell programs only use a single OS thread. You must build with the
-threaded flag to enable multiple OS threads.
Using newtypes like
Password instead of a generic string type can increase type safety and expressiveness, but, as we saw previously, it can also lead to annoyances where you have to convert between two types that are, in one sense, the same. This lesson of the Validation course introduces the
Coercible class as general way of handling those conversions.
This lesson of the Validation course looks into the prisms from the
validation library and how we can use them to generalize our code to be, as they say, more liberal in what we accept. Doing this requires us to look into the
lens library and some of its functions, as well as take a deeper look at what it means for types to be isomorphic to each other.
Using Attoparsec’s incremental parsing ability, we fed the parser small chunks at a time. This approach eliminates the lazy I/O and affords us some more manual control over the I/O at the expense of being slightly more cumbersome. A streaming library like
pipes provides a good middle ground between the two approaches, giving us back some of the convenience of lazy datatypes – and some additional ability to decompose our code into smaller cohesive pieces – without sacrificing fine-grained control over the operations of the program.