6

I can write something impossible with an empty case, and use it with Decision, for example.

{-# LANGUAGE DataKinds, EmptyCase, LambdaCase, TypeOperators #-}

import Data.Type.Equality
import Data.Void

data X = X1 | X2

f :: X1 :~: X2 -> Void
f = \case {}
-- or
-- f x = case x of {}

Is there a way to write the equivalent without using case by directly pattern-matching the parameter?

f :: X1 :~: X2 -> Void
f ???
4
  • Does f _ = undefined work? Commented May 26, 2022 at 0:47
  • 1
    I think that it's all about avoiding undefined to use an empty case. So I'd like to know how I can write it without undefined. Commented May 26, 2022 at 0:54
  • 2
    (Data.Void.absurd is also defined using case x of {}.) Commented May 26, 2022 at 0:58
  • 2
    I think there's no syntax because it just doesn't fit very nicely into function definition syntax. A case normally has one or more branches; generalizing to zero branches (because no pattern could match) still leaves the case ... of header, so there's something to write to indicate that's what you want to do. But function definition syntax has you write an equation for each branch, so zero branches should mean zero equations, leaving nothing at all! But that can't be told apart from just not having implemented the function yet. Commented May 26, 2022 at 1:45

3 Answers 3

7

Well, you can use a horrible CPP hack:

{-# LANGUAGE CPP #-}
#define Absurd      = \case {}

f :: X1 :~: X2 -> Void
f Absurd   -- expands to "f = case {}"

However, if you're looking for a solution using pure Haskell syntax, I'm pretty sure the answer is no. Unlike with an empty case, you can't define f using pattern syntax without at least one pattern. And, there's no pattern that GHC understands as secret code for a term of uninhabited type. (Even if there was, there's no syntax that allows you define an f pat without a right-hand side.)

Sign up to request clarification or add additional context in comments.

Comments

3

Here's an attempt using pattern synonyms. It is not completely satisfactory and probably not what you really want. It only achieves in moving the \case{} away from your eye. We still need to use absurd in some points.

{-# LANGUAGE PatternSynonyms, ViewPatterns, GADTs #-}
{-# LANGUAGE DataKinds, EmptyCase, LambdaCase, TypeOperators #-}

import Data.Type.Equality
import Data.Void

data X = X1 | X2

pattern Abs :: Void -> a
pattern Abs x <- (\case{} -> x)

f :: 'X1 :~: 'X2 -> Void
f (Abs x) = x

g :: 'X1 :~: 'X2 -> a
g (Abs x) = absurd x

{-
    Pattern match(es) are non-exhaustive
    In an equation for `h':
        Patterns of type 'X1 :~: 'X1 not matched: Refl
-}
h :: 'X1 :~: 'X1 -> Void
h (Abs x) = x

Another alternative could be exploiting Template Haskell.

Comments

0

I think if you want to imitate this, the simplest way is akin to an answer posted in the comments:

f _ = undefined

However, this is lazy:

f (error "aw beans")  -- undefined

While the empty case is strict:

(\ case {} :: X1 :~: X2 -> Void) (error "aw beans")  -- beans

So a better replacement would use seq:

impossible :: a -> Void
impossible x = x `seq` impossible x

Or BangPatterns:

{-# Language BangPatterns #-}

impossible :: a -> Void
impossible !x = impossible x

Whereupon you can just write f = impossible, which seems pretty good to me. (The quantifier on a is logically more like “for any” than “for all”, but oh well.) You could of course throw something slightly more descriptive instead of going into an infinite loop in case the situation isn’t actually impossible—error "the impossible was possible after all".

This should also work with the old-fashioned definition of Void as data Void = Void !Void or newtype Void = Void Void in the void package.

2 Comments

Thank you. I think one of the advantages of using an empty case for me is that the compiler will tell me that the pattern is non-exhaustive if I write something wrong such as g :: X1 :~: X1 -> Void. I can no longer take advantage of it with this definition.
@snak: Yeah, an empty case is the right solution, and your version with LambdaCase is about as short as it gets. At best you can make this more tolerant of code motion by repeating the intended type argument, impossible @(X1 :~: X2).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.