# Nullary operations on GADT with constraints

**URL:** <https://discourse.haskell.org/t/nullary-operations-on-gadt-with-constraints/14676>\
**Category:** Learn\
**Created:** [September 11, 2026, 8:35am UTC](https://discourse.haskell.org/t/nullary-operations-on-gadt-with-constraints/14676 "2026-09-11T08:35:00Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![olf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/olf/32/3813_2.png) [@olf](https://discourse.haskell.org/u/olf)\
**Post date:** [September 11, 2026, 8:35am UTC](https://discourse.haskell.org/t/nullary-operations-on-gadt-with-constraints/14676/1 "2026-09-11T08:35:00Z")

</div>

I am trying to define something similar to `Data.Monoid.Ap` but as GADT with constraints on the constructors:

```haskell
import Data.Monoid (Ap(..))
-- instance (Monoid a, Applicative f) => Monoid (Ap f a)

data Lifted f a where
   Lift :: (Monoid a, Applicative f) => Ap f a -> Lifted f a 

```

One can have a `Semigroup` instance wherein the constraints are hidden:

```haskell
instance Semigroup (Lifted f a) where
   (Lift a) <> (Lift b) = Lift (a <> b)

```

This works because by matching on the `Lift` constructor, the constraints necessary for the `Semigroup (Ap f a)` instance are brought into scope.  
However, this approach does **not** work for nullary operations like `mempty`, because there is nothing to pattern-match on. Can it be done nevertheless? Perhaps with proxies?

A possible work-around would be to introduce a second constructor taking a rank-2 argument:

```haskell
data Lifted f a where
   Lift :: (Monoid a, Applicative f) => Ap f a -> Lifted f a
   Constant :: (forall x. Monoid x => x) -> Lifted f a

instance Monoid (Lifted f a) where
   mempty = Constant mempty

```

The drawback is combinatorial explosion in the definition of the semigroup operation (or any other n-ary operation). Instead of one case, it now must handle four.

The class `Monoid` is only an example. My question applies to all classes of algebraic structures that have constants.

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [September 11, 2026, 8:54am UTC](https://discourse.haskell.org/t/nullary-operations-on-gadt-with-constraints/14676/2 "2026-09-11T08:54:01Z")

</div>

This reminds me of Kmett’s type classes vs the world (around 7:48):

[![](https://i.ytimg.com/vi/hIZxTQP1ifo/hqdefault.jpg "Edward Kmett - Type Classes vs. the World") ](https://www.youtube.com/watch?v=hIZxTQP1ifo)

He uses the example of `fromInteger` which suffers from the same issue.

---

<div class="post-metadata">

**Author:** ![Leary](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/leary/32/4348_2.png) [@Leary](https://discourse.haskell.org/u/Leary)\
**Post date:** [September 11, 2026, 9:25am UTC](https://discourse.haskell.org/t/nullary-operations-on-gadt-with-constraints/14676/3 "2026-09-11T09:25:16Z")

</div>

You’re bundling constraints with the values, thus requiring them upon construction but not destruction.

If you impose constraints on the values instead, then you’ll need them upon destruction but not construction. That will allow you to produce these values without first matching on another:

```haskell
newtype Monoidal a where
  MkMonoidal :: (Monoid a => a) -> Monoidal a

instance Semigroup (Monoidal a) where
  MkMonoidal x <> MkMonoidal y = MkMonoidal (x <> y)

instance Monoid (Monoidal a) where
  mempty = MkMonoidal mempty

```

---

<div class="post-metadata">

**Author:** ![olf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/olf/32/3813_2.png) [@olf](https://discourse.haskell.org/u/olf)\
**Post date:** [September 11, 2026, 9:46am UTC](https://discourse.haskell.org/t/nullary-operations-on-gadt-with-constraints/14676/4 "2026-09-11T09:46:09Z")

</div>

Yes, that is exactly what I described as my work-around: `MkMonoidal = Constant`.

It turns out the combinatorial explosion is not too bad, because one can reduce binary operations involving constants to unary operations `forall a. Monoid a => a -> a`.

The only remaining downside is when there are specific class members that identify two constants:

```haskell
newtype Bounds a where
   Bound :: (Bounded a => a) -> Bounds a

instance Bounded (Bounds a) where
   maxBound = Bound maxBound
   minBound = Bound minBound

```

We have

```haskell
minBound == () == maxBound

```

But `minBound :: Bounds ()` can be distinguished from `maxBound :: Bounds ()` by matching the `Bound` constructor and casting the value to another non-singleton type.

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [September 11, 2026, 9:54am UTC](https://discourse.haskell.org/t/nullary-operations-on-gadt-with-constraints/14676/5 "2026-09-11T09:54:49Z")

</div>

> [@olf](#):
>
> But `minBound :: Bounds ()` can be distinguished from `maxBound :: Bounds ()` by matching the `Bound` constructor and casting the value to another non-singleton type.

That’s possible for your original `Constant` approach, but not for this `MkMonoidal`/`Bound` approach, because the latter doesn’t put the quantification inside the constructor.

---

<div class="post-metadata">

**Author:** ![olf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/olf/32/3813_2.png) [@olf](https://discourse.haskell.org/u/olf)\
**Post date:** [September 11, 2026, 10:31am UTC](https://discourse.haskell.org/t/nullary-operations-on-gadt-with-constraints/14676/6 "2026-09-11T10:31:12Z")

</div>

Ah, so the difference is

```haskell
Constant :: (forall x. Monoid x => x) -> Lifted f a
MkMonoidal :: ( Monoid a => a) -> Monoidal a

```

so `MkMonoidal` takes a monomorphic argument. The consequence of this is that `Monoidal a` is the empty type when `a` is not already a monoid, whereas `Lifted f a` always has an inhabitant.  
I’ll experiment and see whether the monomorphic variant has any other drawbacks.
