Hide GHC base library to prevent pattern matching desugaring to GHC.Num.fromInteger use

102 views Asked by At

I have created a replacement Prelude for use in teaching beginning Haskell students, called FirstPrelude. One of the aims is to expunge type classes from the standard library so that error messages are more of the classic Hindley-Milner variety, rather than getting No instance errors. This is working out quite well. However, something I did not anticipate is that, when pattern matching, GHC is side-stepping my redefinition of fromInteger (defined as the identity, monomorphised to only work on Integer) and so for example, with this function:

isZero 0 = True
isZero _ = False

If I ask GHCi for the type, I get:

isZero :: (GHC.Classes.Eq a, GHC.Num.Num a) => a -> Bool

But what I want is to get Integer -> Bool. Dumping the simplified core out of GHC I can see it is using:

(GHC.Num.fromInteger @Integer GHC.Num.$fNumInteger 0)))

I would have thought it would just use my fromInteger :: Integer -> Integer that is in scope, but alas no. Is there a way I can somehow prevent GHC.Num.fromInteger from being used? I guess perhaps this possible at the package level, but really I would love this at the module level for other pedagogical reasons.

1

There are 1 answers

2
Joachim Breitner On BEST ANSWER

This is not tied to pattern matching: In your Example.hs, even literals in expressions are polymorphic. To see this, write something like

simpler :: ()
simpler = 1

and observe the error message

[2 of 2] Compiling Main             ( Example.hs, interpreted )

Example.hs:7:11: error:
    • No instance for (GHC.Num.Num ()) arising from the literal ‘1’
    • In the expression: 1
      In an equation for ‘simpler’: simpler = 1
  |
7 | simpler = 1
  |           ^
Failed, one module loaded.

You may not have noticed because as soon as you use one of “your” operations, GHC specializes the type, and for top-level values may use defaulting.

But note

simpler _ = 1

for which it infers this type

ghci> :t simpler
simpler :: GHC.Num.Num p1 => p2 -> p1

If you enable {-# LANGUAGE RebindableSyntax #-}, it really uses “your” fromInteger and things work as you expect. You can do that in the .cabal file, maybe good enough for your educational needs.