Opened 4 years ago

Closed 23 months ago

#10577 closed bug (fixed)

Use empty cases where appropriate when deriving instances for empty types

Reported by: rwbarton Owned by:
Priority: normal Milestone: 8.4.1
Component: Compiler Version: 7.11
Keywords: deriving Cc: omeragacan@…, RyanGlScott, dfeuer
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: #13117, #7401 Differential Rev(s): Phab:D4047
Wiki Page:

Description

In some sense the correct instance for

data X
deriving instance Eq X

is not (what GHC currently produces)

instance Eq X where
  (==) = error "Void =="

but rather

instance Eq X where
  a == b = case a of {}     -- using the EmptyCase extension

See comments starting at ticket:7401#comment:28 for justification.

The list of classes that GHC can currently derive is

  • Eq, Ord, Enum, Bounded, Show, Read (Haskell 2010 Report)
  • Functor, Foldable, Traversable, Typeable, Generic, Data, "any class" (-XDerive*)

Deriving Enum and Bounded is not currently allowed for empty data types. The showList method of Show and the whole Read instance are easy and already implemented correctly. All the remaining methods of Haskell 2010 derivable type classes take at least one argument of the instance head type a. I propose that all these methods be defined as an empty case on the first such argument.

Similarly in Functor, Foldable and Traversable, each method has a single argument of the form t _ where t :: * -> * is the instance head type, and the method should be defined as an empty case on that argument.

In all these cases so far, the derived methods for a non-empty type would, at the outermost level, be a (non-empty) case on that first argument, or the equivalent (series of pattern matches, one for each constructor), so the use of an empty case is justified for an empty type.

Typeable does not care about the values or even the kind of the type at all, so it's not relevant here.

Generic and especially Data are above my pay grade, but generally I expect that methods which are normally defined by case analysis on an argument of the instance head type should be defined by an empty case, and methods that (sometimes) produce a value of the instance head type should do whatever they normally do when unable to produce such a value (like readsPrec returning an empty list, or read calling error (if read were actually a method of Read)).

DeriveAnyClass doesn't generate any new code, so like Typeable it's not relevant.

None of this behavior is specified by the Haskell 2010 Report, which disallows deriving any class for a type with no constructors; but see #7401. So, we are entitled to do what we think is best here.

Change History (11)

comment:1 Changed 4 years ago by osa1

Cc: omeragacan@… added

comment:2 Changed 4 years ago by osa1

One thing to note: We have two ways to derive instances, using deriving (..) syntax and StandaloneDeriving. If we want to make changes described here and also keep this two methods of instance deriving consistent with each other(currently they're not), it means breaking change.

IMHO, StandaloneDeriving and deriving (..) should work the same, so we should either do this breaking change or merge https://phabricator.haskell.org/D978.

Also, I'm not sure how relevant it is, but Data.Void.Voids methods are currently not forcing the arguments.

comment:3 Changed 4 years ago by RyanGlScott

Cc: RyanGlScott added

comment:4 Changed 2 years ago by dfeuer

This is fixed for Functor, Foldable, Traversable, Generic, and Generic1. I believe some Data methods already do the right thing here, but I don't know if they all do.

comment:5 Changed 2 years ago by dfeuer

Cc: dfeuer added

comment:6 Changed 2 years ago by RyanGlScott

Keywords: deriving added

comment:7 Changed 2 years ago by RyanGlScott

Differential Rev(s): Phab:D4047
Status: newpatch

Phab:D4047 fixes this ticket in the sense that more classes now use EmptyCase in derived code for empty data types (namely, Show, Lift, and Data). Note that Eq and Ord are not among these classes—refer to the proposal which Phab:D4047 implements.

comment:8 Changed 2 years ago by mpickering

I still think there should be a "few examples where folks tied knots with fixed points to get inhabitants of Void" seeing as that is the primary motivation for this implementation. They are good for tests and notes even though I accept I will find them dubious.

comment:9 Changed 2 years ago by RyanGlScott

I'm not sure if you're asking me to add something here or in the Diff. If the latter, are you asking for a test case to see if something like this evaluates to True?

{-# LANGUAGE EmptyDataDeriving #-}
module Main where

import Data.Function

data Foo deriving Eq

foo1 :: Foo
foo1 = fix id

foo2 :: Foo
foo2 = let x = y
           y = x
        in y

main :: IO ()
main = print (foo1 == foo2)

comment:10 Changed 23 months ago by Ben Gamari <ben@…>

In 1317ba62/ghc:

Implement the EmptyDataDeriving proposal

This implements the `EmptyDataDeriving` proposal put forth in
https://github.com/ghc-proposals/ghc-proposals/blob/dbf51608/proposals/0006-deriving-empty.rst.
This has two major changes:

* The introduction of an `EmptyDataDeriving` extension, which
  permits directly deriving `Eq`, `Ord`, `Read`, and `Show` instances
  for empty data types.
* An overhaul in the code that is emitted in derived instances for
  empty data types. To see an overview of the changes brought forth,
  refer to the changes to the 8.4.1 release notes.

Test Plan: ./validate

Reviewers: bgamari, dfeuer, austin, hvr, goldfire

Reviewed By: bgamari

Subscribers: rwbarton, thomie

GHC Trac Issues: #7401, #10577, #13117

Differential Revision: https://phabricator.haskell.org/D4047

comment:11 Changed 23 months ago by RyanGlScott

Milestone: 8.4.1
Resolution: fixed
Status: patchclosed
Note: See TracTickets for help on using tickets.