Home > database >  What is an empty constraint useful for?
What is an empty constraint useful for?

Time:03-30

In Haskell it is absolutely legal to write

f :: () => Int -> Int

Or even

f :: (Num i, ()) => i -> i

Why is this () allowed? To me it looks like it brought no information, but since it is valid syntax, I guess there are cases where it comes useful.

CodePudding user response:

The empty constraint can be useful in type aliases and type families that produce constraints. For instance, here's a contrived example.

type family F :: Type -> Constraint where
   F (a -> b) = ()
   F a        = (Show a, Read a)

Then, foo :: F t => ... would only require Show and Read on non-function types. Combined with GADTs / singletons, it can be exploited to create type-level machinery.

One could write a trivial constraint instead of (), like Num Int or Bool ~ Bool, but using a special syntax like () better expresses the intent.

CodePudding user response:

You can work with the ConstraintKinds extension to define aliases for constraints, for example:

{-# LANGUAGE ConstraintKinds #-}

type Foo a = (Num a, Ord a)

myFun :: Foo a => a -> Bool
myFun = (0 <)

But it is thus not known per se how many constraints you will list. It thus accepts tuples of any arity, since the number of constraints can be zero, one, or more.

CodePudding user response:

When you get into extensions such as ConstraintKinds and TypeFamilies, then constraints can be computed, not just statically listed.

As such, there are times when an interface requires you to provide something of kind Constraint, but you may not need any actual constraint. You then need a way of representing an "empty" constraint; one that imposes no actual requirements (and also grants no additional power when it holds), but is nevertheless a thing of kind Constraint. Given that we can combine multiple constraints into a single item of kind Constraint by writing (C1, C2, C3, ...), then the obvious syntax for an empty constraint is (). (In exactly the same way we get () as the syntax for "unit", which can be interpreted as an "empty tuple")

Given that () is a valid thing with kind Constraint, you can use it anywhere something with kind Constraint is required. That includes as one member of a tuple of constraints on a type signature, like foo :: (EQ a, ()) => .... It also includes as the entirety of the constraints, like bar :: () => .... It's not useful to do so but it's perfectly clear what it means, by all the same rules as apply to any other Constraint.

In general, you make an understandable programming language by carefully thinking out the rules such that any possible combination of them is meaningful, and then rigidly sticking to them as much as possible. Trying to add more and more special cases to rule out "useless" uses of particular features is a never ending task, makes everyone using the system have to memorise all the special cases as well as the normal rules, and sooner or later someone is bound to come up with a context you didn't think of where actually the "useless" thing isn't useless after all. And only a miniscule proportion of all the "useless" code that can be written could ever hope to be detected by trivial syntactic rules like "in a type signature left of the => you can write any constraint except ()". So generally, programming language designers need a very good reason to forbid something like empty constraints; asking for why something like that is allowed frequently just has the boring answer: "why not?"

  • Related