I have some function. And I want to restrict types that can be passed to this function. Let's say only types that are cacheable. I can enumerate such types with a type family like:
type family Cacheable a::Bool where
Cacheable X = 'True
Cacheable _ = 'False
And add such a constraint to my function:
myFunc :: forall a. (Cacheable a ~ 'True) => ....
but this constraint is little bit redundant: I can remove it from the function's signature, and nothing changes. Nothing forces it to be in the signature.
Another approach is to create some typeclass whose usage in the body of myFunc will force me to add this constraint to myFunc's signature:
class Cacheable a
ensureCacheable :: ()
ensureCacheable = ()
instance Cacheable X
myFunc :: forall a. (Cacheable a) => ...
myFunc =
let _ = ensureCacheable @a
in ...
but it looks little bit funny.
What is the right/canonical way to do it in Haskell?
Let's suppose that Cacheable cannot have any reasoning methods. Think about it like about some classifier. Another name is IsQuery (vs IsCommand) and queries are cacheable, commands - no.