[Haskell-cafe] Different advice for type classes for end-user app and libraries?

Michal J Gajda mgajda at mimuw.edu.pl
Fri Aug 13 13:05:19 UTC 2021


Dear Ruben,

> Currently I am working a end user application. Through the years I had
> incorporated the advice from r/haskell that classes without laws were not
> desirable. Their lack meant that we didn't have a contract boundary that
> should be respected by the instances. They secretly could `throw exc` on
> their implementation and you wouldn't notice.

Indeed you are right.
This is especially pressing for the type classes that convey relations
between multiple types.
Luckily, diligent students of Haskell source code will find common
patterns for the laws that occur -- see below.

> But on the current app I am working on, I follow the advice from this
> blogpost [1] that I should use classes for domain modelling. This manifest
> on classes like

>    class UserQueryCapability f where
>        getUser :: Id -> f User

Before we get to the laws, I would recommend that you use technique of
"linguistic analysis" to find a better name for the class. If not, you
may want to remove words that are implicit in the object. Class name
of "UserQuery" may be better, because "capability" is implicit in the
fact that it is a type class.
See example here:
https://olegchursin.medium.com/a-brief-introduction-to-domain-modeling-862a30b38353#f72f

> for interacting with a DB. Functions can be written against the interface
> and I can mock up with QuickCheck some pretty broad cases. The classes
> usually don't have a superclass constrain on `Monad` as in `MonadReader`
> because I *cannot* establish a law relating the monadic behavior and
> `getUser`. The name has Capability in it and not Monad because I won't lie
> that way.

First method is to recognize a common pattern: you have a function
that reads a part of the state. That means it is similar to `lookup
userId <$> ask`.
`ask` follows certain laws, and you may apply these laws here:

getUser u >> getUser u == ask -- idempotence
getUser u >> return () == return () -- no effect of reading

To get inspiration for other laws you may look at how this type class
interacts with other type classes that deal with `User` objects here.

There are several other techniques that apply in special cases. For
example if you have multiple self-contained implementations of a type
class, you may use `quickspec` to get equational laws for different
implementations, and look for common laws between different
implementations.
https://hackage.haskell.org/package/quickspec

These two ways seem to be most applicable from the example you have
provided so far.

> Making sure that these effect classes are orthogonal is something I have to
> check by hand. But in the back of my head there is the concern about the
> lack of laws could bite me even on a end user-app. Is my intuition well
> founded that this case is OK?

Lack of laws may mean one of the following:

* type class is not an abstraction and thus does not allow you to tell
anything abstract about it
* you may have undocumented tacit knowledge about the type class that
gets lost in the implementation
* your users will find it harder to use type class as interface (as it
is intended to be used).

Advert: If the above description is insufficient, and the task is too
challenging to fully solve within haskell-cafe mailing list, you may
contact https://www.migamake.com for consulting on type class design
and training. We do provide business analysis for Haskell projects
too.
-- 
  All the best
    MichaƂ


More information about the Haskell-Cafe mailing list