Algebraic blindness
Other things can be opaque besides booleans, even Maybe, Either, tuples, and lists. The point is, you have to balance the ease of use of standard types with the difficulty of creating (and converting to/from) custom data types.
The author points out that Haskell makes it easy to refactor from the use of standard types to more maintainable custom data types. Just substitute your custom type somewhere in the code base and the compiler errors will help you put it in everywhere.
Frequently brought up topics in #haskell
Frequently asked questions from the #haskell channel on Freenode IRC.
I need to think about "imposing constraints on data types...put the constraints in the functions, not in the data declaration."
Monad transformers 101
The trick to avoid deeply-nested error-handling code
Inside a do-block for the Either monad, Left short-circuits out of the block. Keep in mind return doesn't. That could be a good reason to use pure instead.
Code smell: boolean blindness
This post points out that it's often difficult to tell what a function does when passed true (or false). The given example of the ubiquitous "filter" function is great; does true or false make you keep a list element? Renaming filter to "select" and/or "discard" makes the boolean clearer.
The author points out that substituting a function that returns the Haskell Maybe type makes more sense in some "filter" scenarios.
mapMaybe :: (a -> Maybe b) -> [a] -> [b]
builds a list with only entries where the given function returns Just b
See Data.Maybe.mapMaybe for lists and the more general Data.Witherable.mapMaybe.
Related: the wrong abstraction
On ad-hoc datatypes
Adding an enum datatype can simplify code. Don't be afraid to add module-internal ones.
Type safety back and forth
If pushing responsibility forward means accepting whatever parameters and having the caller of the code handle possibility of failure, then pushing it back is going to mean we accept stricter parameters that we can’t fail with.
An example of the former is:
uncons :: [a] -> Maybe (a, [a])
and of the latter is:
head :: NonEmpty a -> a
Working with the latter pushes error handling to the data input code and cleans up all the rest.
When type annotations are code too
Type annotations affect runtime behavior in statically compiled languages, but not ones where types are bolted-on after the fact (Python).
This makes runtime behavior depend on the type inference algos and can be difficult to reason about.
The example in Haskell is wild!
How to Specify It!: A Guide to Writing Properties of Pure Functions
A guide to writing property-based tests. The author, John Hughes, co-wrote QuickCheck in Haskell, the first package for property-based testing.
Published conference paper: http://dx.doi.org/10.1007/978-3-030-47147-7_4
Some quotes:
"Avoid replicating your code in your tests," because it's lot of work and likely to contain the same errors as your code.
"Test your tests," because if your generator and shrinker produce invalid values, everything else will fail too.
Validity testing
Validity testing consists of defining a function to check the invariants of your datatypes, writing properties to test that your generators and shrinkers only produce valid results, and writing a property for each function under test that performs a single random call, and checks that the return value is valid.
Postcondition testing
A postcondition tests a single function, calling it with random arguments, and checking an expected relationship between its arguments and its result.
Metamorphic testing
A metamorphic property tests a single function by making (usually) two related calls, and checking the expected relationship between the two results.
Inductive testing
Inductive properties relate a call of the function-under-test to calls with smaller arguments. A set of inductive properties covering all possible cases together test the base case(s) and induction step(s) of an inductive proof-of-correctness. If all the properties hold, then we know the function is correct–inductive properties together make up a complete test.
Model-based testing
A model-based property tests a single function by making a single call, and comparing its result to the result of a related “abstract operation” applied to related abstract arguments. An abstraction functions maps the real, concrete arguments and results to abstract values, which we also call the “model”.
Haskell conditional for
To only perform a monadic or applicative action if a value exists, you can use
for_ value $ actionSo for example, if x is a Maybe String, you could print it if it exists with:
for_ x $ putStrLn xI think that's the beauty of Haskell, this thing with a name from lists, when generalized to (Traversable) Applicatives, can do useful things for all kinds of things.
Since for_ is just traverse_ with it's arguments flipped, you could also do:
traverse_ (action) valueKeep in mind for_ and traverse_ are in Data.Foldable and not present in the Prelude.
Also, oddly, is the linked blog post scheduled to be published in the future?
Haskell guard sequence
guard condition *> action -- Perform action if condition is true else fail
guard condition $> value -- Return value if condition is true else fail
value <$ guard condition -- Like the 1st line, but switched aroundThis can effectively make sure a condition is true before doing something or returning a value.
Keep in mind "failure" can mean lots of things depending on the surrounding Applicative. It could mean returning Nothing, or raising an exception.
You will need the following imports to do this:
import Control.Mondad (guard)
import Data.Functor ((*>),($>),(<$))