# luminance 0.8 and existential quantification

It’s been a while I haven’t released anything on my blog. I just wrote a few changes for the latest version of luminance, luminance-0.8.2 and I decided to write about it because I think those changes are interesting on a Haskell level.

## The problem

If you haven’t read the changelog yet, I changed the `createProgram`

function and the way it handles uniform interfaces. In luminance < 0.8, you were provided with as many functions as there are uniform kinds. Up to now, luminance supports two uniform kinds:

- simple uniforms;
- uniform block (UBO).

So you had two rank-2 functions like `forall a. (Uniform a) => Either String Natural -> UniformInterface m (U a)`

and `forall a. (UniformBlock a) => String -> UniformInterface m (U (Region rw (UB a)))`

to map whichever uniforms you wanted to.

The issue with that is that it requires to break the interface of `createProgram`

each time we want to add a new kind of uniform, and it’s also a pretty hard to read function signature!

So… how does luminance-0.8 solve that?

## (Generalized) Algebraic data types, rank-2 and existential quantification

What is the only way we have to select uniforms? Names. Names can either be a `String`

or a `Natural`

for explicit semantics. We could encode such a name using an algebraic data type:

```
data UniformName
= UniformName String
| UniformSemantic Natural
deriving (Eq,Show)
```

That’s a good start. Though, we still have the problem of choosing the kind of uniform because we still have several functions – one per kind. We could encode the kind of the uniform directly into the name. After all, when we ask for a uniform mapping through a name, we require to know the kind. So that kind of makes sense. Let’s change our `UniformName`

type:

```
data UniformName :: * -> * where
UniformName :: String -> UniformName a
UniformSemantic :: Natural -> UniformName a
UniformBlockName :: String -> UniformName (Region rw (UB a))
```

That’s neat, but with that definition, we won’t go anywhere, because we’re too polymorphic. Indeed, `UniformName "foo" :: UniformName a`

can have any `a`

. We need to put constraints on `a`

. And that’s where *GADTs* come in so handy! We can hide the constraints in the constructors and bring them into scope when pattern matching. That’s a very neat feature of *GADTs*. So now, let’s add some constraints to our constructors:

```
data UniformName :: * -> * where
UniformName :: (Uniform a) => String -> UniformName a
UniformSemantic :: (Uniform a) => Natural -> UniformName a
UniformBlockName :: (UniformBlock a) => String -> UniformName (Region rw (UB a))
```

Yes! Now, we can write a function that takes a `UniformName a`

, pattern matches it and call the appropriate function regarding the infered shape of `a`

!

However, how do we forward the error? In older version of luminance, we were using `ProgramError`

and more especially two of its constructors: `InactiveUniform`

and `InactiveUniformBlock`

. We need to shrink that to a single `InactiveUniform`

constructor and find a way to store our `UniformName`

… But we can’t yet, because of the `a`

parameter! So the idea is to hide it through existential quantification!

```
data SomeUniformName = forall a. SomeUniformName (UniformName a)
instance Eq SomeUniformName where
-- …
instance Show SomeUniformName where
-- …
```

And now we can store `SomeUniformName`

in `InactiveUniform`

. We won’t need to recover the type, we just need the constructor and the carried name. By pattern matching, we can recover both those information!

## Conclusion

Feel free to have a look at the new `createProgram`

function. As you will see, the type signature is easier to read and to work with! :)

Have fun, and keep the vibe!

## No comments:

## Post a Comment