# Generic-case: automatic case analysis with no boilerplate

**URL:** <https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517>\
**Category:** Announcements\
**Created:** [February 28, 2025, 5:49pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517 "2025-02-28T17:49:06Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![fpringle](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/fpringle/32/3433_2.png) [@fpringle](https://discourse.haskell.org/u/fpringle)\
**Post date:** [February 28, 2025, 5:49pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/1 "2025-02-28T17:49:06Z")

</div>

I’m happy to announce a new package candidate: [generic-case: Generic case analysis](https://hackage.haskell.org/package/generic-case-0.1.0.0/candidate).

`Data.Maybe.maybe`, `Data.Bool.bool` and `Data.Either.either` all follow a pattern, which is sometimes called “case analysis”: for each constructor of a given type `T`, provide me a function from the product of the constructor types to a result type `r`; and I’ll give you back a function from `T` to `r`.

`generic-case` unifies this pattern and generalises it for any type with an instance of `Generic` from `generics-sop`.

Honestly, my main motivation for putting this package together was to have an excuse to play with `generics-sop` a bit, which is a beautiful library but one I never seem to get a chance to use.

Any feedback would be greatly appreciated 🙂

---

<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:** [February 28, 2025, 6:19pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/2 "2025-02-28T18:19:53Z")

</div>

This looks cool!

I wonder, if I need to define a new case function, do I need to know its type beforehand or is there some way I can automatically get the type? Can GHC infer it?

Would it be much effort to also provide a generic fold function? Example usage might look like this:

```haskell
listFoldR :: forall a r. r -> (a -> r -> r) -> [a] -> r
listFoldR = gfoldR @[a]

```

---

<div class="post-metadata">

**Author:** ![fpringle](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/fpringle/32/3433_2.png) [@fpringle](https://discourse.haskell.org/u/fpringle)\
**Post date:** [February 28, 2025, 6:38pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/3 "2025-02-28T18:38:59Z")

</div>

Thanks! Currently you could do it two ways. You can write out the type signature manually:

```haskell
maybe :: forall a r. r -> (a -> r) -> Maybe a -> r
maybe = gcaseR @(Maybe a)

```

Or you can use the `ChainsR` type from the library:

```haskell
maybe :: forall a r. ChainsR (Code (Maybe a)) (Maybe a) r
maybe = gcaseR @(Maybe a)

```

The latter is easier for you (just replace `Maybe a` with your type), but if you’re writing a library, your downstream users won’t have any idea what the type signature means, in which case you should use the former.

I do wonder if a GHC plugin would be able to rewrite the type inline…

Come to think of it, I should probably provide a nicer type synonym for `ChainsR (Code a) a r`.

Interesting question about generic folds, I’d have to think about it. What do you imagine the type looking like for, say, `Either a b`?

---

<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:** [February 28, 2025, 6:45pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/4 "2025-02-28T18:45:07Z")

</div>

> [@fpringle](#):
>
> I do wonder if a GHC plugin would be able to rewrite the type inline…

Maybe a code action for HLS could be added such that it could evaluate type families. Ah, I guess you can already do that in GHCi:

```haskell
ghci> :k! ChainsR (Code [a]) [a] r
ChainsR (Code [a]) [a] r :: *
= r -> (a -> [a] -> r) -> [a] -> r

```

> [@fpringle](#):
>
> What do you imagine the type looking like for, say, `Either a b`?

`Either` is not recursive, so a fold over `Either` has the same type as the case function.

---

<div class="post-metadata">

**Author:** ![fpringle](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/fpringle/32/3433_2.png) [@fpringle](https://discourse.haskell.org/u/fpringle)\
**Post date:** [February 28, 2025, 6:57pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/5 "2025-02-28T18:57:01Z")

</div>

> [@jaror](#):
>
> `Either` is not recursive, so a fold over `Either` has the same type as the case function.

To my understanding `generics-sop` does “non-recusive” (or “shallow”) generic transformations, in the sense that if a type refers to itself, it’s not expanded in the `Rep`. So I think a fold in this sense would be non-trivial to implement (I’m not even sure it’d be possible), but I’ll think about it more.

---

<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:** [February 28, 2025, 7:07pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/6 "2025-02-28T19:07:01Z")

</div>

Right, I was thinking something like this:

```haskell
type family ChainFold f xs r where
  ChainFold _ '[] r = r
  ChainFold f (f ': xs) r = r -> ChainFold f xs r
  ChainFold f (x ': xs) r = x -> ChainFold f xs r

type family ChainsFold xss a r where
  ChainsFold '[] a r = a -> r
  ChainsFold (xs ': xss) a r = ChainFold a xs r -> ChainsFold xss a r

```

But that only works if the type is fully concrete:

```haskell
ghci> :k! ChainsFold (Code [a]) [a] r
ChainsFold (Code [a]) [a] r :: *
= r -> ChainFold [a] [a, [a]] r -> [a] -> r

```

```haskell
ghci> :k! ChainsFold (Code [Int]) [Int] r
ChainsFold (Code [Int]) [Int] r :: *
= r -> (Int -> r -> r) -> [Int] -> r

```

---

<div class="post-metadata">

**Author:** ![fpringle](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/fpringle/32/3433_2.png) [@fpringle](https://discourse.haskell.org/u/fpringle)\
**Post date:** [February 28, 2025, 7:15pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/7 "2025-02-28T19:15:29Z")

</div>

> [@jaror](#):
>
> ```haskell
> ghci> :k! ChainsFold (Code [Int]) [Int] r
> ChainsFold (Code [Int]) [Int] r :: *
> = r -> (Int -> r -> r) -> [Int] -> r
> 
> ```

I think the issue here is that GHC can’t figure out that `[a]` and `a` don’t unify:

```haskell
type family Compare x y where
  Compare a a = True
  Compare a b = False

ghci> :k! Compare [a] a
Compare [a] a :: Bool
= Compare [a] a

```

---

<div class="post-metadata">

**Author:** ![soulomoon](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/soulomoon/32/2272_2.png) [@soulomoon](https://discourse.haskell.org/u/soulomoon)\
**Post date:** [February 28, 2025, 8:26pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/8 "2025-02-28T20:26:28Z")

</div>

> I think the issue here is that GHC can’t figure out that `[a]` and `a` don’t unify:

see [#11424: "Occurs check" not considered when reducing closed type families · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/11424#note_113742)

---

<div class="post-metadata">

**Author:** ![Lysxia](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/lysxia/32/2916_2.png) [@Lysxia](https://discourse.haskell.org/u/Lysxia)\
**Post date:** [February 28, 2025, 10:37pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/9 "2025-02-28T22:37:45Z")

</div>

> Would it be much effort to also provide a generic fold function?

I did this once

> **[GitHub - Lysxia/generic-recursion-schemes: Folds for recursive types with GHC Generics](https://github.com/Lysxia/generic-recursion-schemes)**
>
> Folds for recursive types with GHC Generics

with not quite the same types as what you propose, but that’s minor details compared to the trickery to figure out where the recursion is in a `Rep`.

---

<div class="post-metadata">

**Author:** ![fpringle](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/fpringle/32/3433_2.png) [@fpringle](https://discourse.haskell.org/u/fpringle)\
**Post date:** [February 28, 2025, 10:46pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/10 "2025-02-28T22:46:51Z")

</div>

I eventually managed to make this work, but it’s very fiddly, makes me feel a bit icky for some reason, and, as you pointed out, only works for concrete types.

> <https://gist.github.com/fpringle/827ee56e26ee7e1b4fe9e2e304bb6ceb>

TLDR:

```haskell
ghci> foldListG [] (:) [1, 2, 3]
[1,2,3]

ghci> foldr (:) [] [1, 2, 3]
[1,2,3]

```

---

<div class="post-metadata">

**Author:** ![Lysxia](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/lysxia/32/2916_2.png) [@Lysxia](https://discourse.haskell.org/u/Lysxia)\
**Post date:** [February 28, 2025, 10:47pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/11 "2025-02-28T22:47:23Z")

</div>

There is also [generic-match](https://hackage.haskell.org/package/generic-match) on Hackage. Since it also uses generics-sop it would be an interesting exercise to carefully compare the implementation with your generic-case.

---

<div class="post-metadata">

**Author:** ![fpringle](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/fpringle/32/3433_2.png) [@fpringle](https://discourse.haskell.org/u/fpringle)\
**Post date:** [February 28, 2025, 10:49pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/12 "2025-02-28T22:49:03Z")

</div>

Oops - didn’t come across that in my (admittedly brief) “prior art” research. I’ll take a look!

---

<div class="post-metadata">

**Author:** ![fpringle](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/fpringle/32/3433_2.png) [@fpringle](https://discourse.haskell.org/u/fpringle)\
**Post date:** [February 28, 2025, 10:51pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/13 "2025-02-28T22:51:08Z")

</div>

By the way - I played around (before this post) with using [metamorph](https://github.com/Lysxia/metamorph) for testing these polymorphic functions, but couldn’t quite get it to work.

---

<div class="post-metadata">

**Author:** ![raehik](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/raehik/32/5337_2.png) [@raehik](https://discourse.haskell.org/u/raehik)\
**Post date:** [February 28, 2025, 10:56pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/14 "2025-02-28T22:56:07Z")

</div>

Apologies for diverting further from the original fold question, but I have related work in [generic-data-functions](https://hackage.haskell.org/package/generic-data-functions) where I provide a kind of generic `foldMap`:

```haskell
genericFoldMapNonSum
    :: forall tag a
    . ( Generic a, GFoldMapNonSum tag (Rep a)
    ) => a -> GenericFoldMapM tag

```

See [Generic.Data.Function.FoldMap](https://hackage.haskell.org/package/generic-data-functions-0.6.0/docs/Generic-Data-Function-FoldMap.html#v:genericFoldMapNonSum).

The phantom `tag` is used to grab a [`GenericFoldMap`](https://hackage.haskell.org/package/generic-data-functions-0.6.0/docs/Generic-Data-Function-FoldMap.html#t:GenericFoldMap) instance, which provides the `Monoid` instance you want to use for each field in your `foldMap`. Perhaps this sort of approach could be relevant here.

---

<div class="post-metadata">

**Author:** ![Lysxia](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/lysxia/32/2916_2.png) [@Lysxia](https://discourse.haskell.org/u/Lysxia)\
**Post date:** [February 28, 2025, 11:26pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/15 "2025-02-28T23:26:17Z")

</div>

That’s okay. It’s missing a lot of pieces anyway if the goal is to have an automated test suite.

On the topic of testing, here’s another idea: you can use generics to construct your own test cases, for example, to obtain a function `testFold` which takes a fold-like function, say `bool :: Bool -> a -> a -> a`, and tests that `bool False 0 1` and `bool True 0 1` have the expected values. Of course the goal is for `testFold` to work with arbitrary generic types.

---

<div class="post-metadata">

**Author:** ![fpringle](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/fpringle/32/3433_2.png) [@fpringle](https://discourse.haskell.org/u/fpringle)\
**Post date:** [February 28, 2025, 11:37pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/16 "2025-02-28T23:37:16Z")

</div>

That’s actually what I did in the test suite of this library! Complete with generation of the folding functions using QuickCheck’s [Fun](https://hackage.haskell.org/package/QuickCheck-2.14.2/docs/Test-QuickCheck-Function.html#t:Fun).

See [test/Util](https://github.com/fpringle/generic-case/blob/cab73ab2411ae17add5802cd91d2ee06255a6a96/test/Util.hs) for the generic functions, and [specBool](https://github.com/fpringle/generic-case/blob/cab73ab2411ae17add5802cd91d2ee06255a6a96/test/Generics/Case/BoolSpec.hs#L9-L15) for example.

For example:

```haskell
goodMaybe :: r -> (a -> r) -> Maybe a -> r
goodMaybe r f Nothing = r
goodMaybe r f (Just a) = f a

badMaybe :: r -> (a -> r) -> Maybe a -> r
badMaybe r f _ = r

spec :: H.Spec
spec = do
  specMaybe @() @Char "goodMaybe" goodMaybe
  specMaybe @() @Char "badMaybe" badMaybe

```

Then we get:

```haskell
  goodMaybe = maybe [✔]
    +++ OK, passed 100 tests.
  badMaybe = maybe [✘]

Failures:

  test/Util.hs:43:3: 
  1) Generics.Case.Maybe badMaybe = maybe
       Falsified (after 1 test and 4 shrinks):
         'a'
         {_->'b'}
         Just ()
         maybe 'a' {_->'b'} (Just ()) = 'b'
         badMaybe 'a' {_->'b'} (Just ()) = 'a'

```

---

<div class="post-metadata">

**Author:** ![hellwolf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/hellwolf/32/3020_2.png) [@hellwolf](https://discourse.haskell.org/u/hellwolf)\
**Post date:** [March 1, 2025, 2:25pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/17 "2025-03-01T14:25:21Z")

</div>

This is cool. I am about to write such a generic deriving for PatternMatchable instances for eDSLs.

Fwiw, PatternMatchable looks like this (I posted an article [here](http://discourse.haskell.org/t/patternmatchable-yoneda-embedding-and-adjunction/11411)):

```haskell
class PatternMatchable m k p c | m -> k, m p -> c, c -> m p where
  match :: ∀ b. k b => m p -> (c -> m b) -> m b
  be :: ∀ . c -> m p

```

The code you generated seems very close to `match`:

```haskell
maybe :: forall a r. r -> (a -> r) -> Maybe a -> r
maybe = gcaseR @(Maybe a)

```

---

<div class="post-metadata">

**Author:** ![lylek](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/lylek/32/2937_2.png) [@lylek](https://discourse.haskell.org/u/lylek)\
**Post date:** [March 3, 2025, 12:53am UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/18 "2025-03-03T00:53:44Z")

</div>

Isn’t this basically a catamorphism?

---

<div class="post-metadata">

**Author:** ![fpringle](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/fpringle/32/3433_2.png) [@fpringle](https://discourse.haskell.org/u/fpringle)\
**Post date:** [March 4, 2025, 1:54pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/19 "2025-03-04T13:54:50Z")

</div>

If you mean the generic folds above, then yes absolutely. Just a pretty hacky implementation…

---

<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:** [March 4, 2025, 9:55pm UTC](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517/20 "2025-03-04T21:55:08Z")

</div>

> [@fpringle](#):
>
> all follow a pattern, which is sometimes called “case analysis”

Not that it matters much, but this is also sometimes called Church encoding, isn’t it? Still very cool to have the encoding derived for me!

[Next page](https://discourse.haskell.org/t/generic-case-automatic-case-analysis-with-no-boilerplate/11517.md?page=2)
