3

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.

dorchard
  • 1,198
  • 8
  • 15
  • Are you using `-XRebindableSyntax`? – Joachim Breitner Jan 17 '23 at 15:10
  • I'd recommend to investigate about type defaulting. [here](https://www.youtube.com/watch?v=53DnugEwLb0&ab_channel=Tweag) there is a video – lsmor Jan 17 '23 at 15:11
  • Not using RebindableSyntax, but that does help explain things here, although I'm not sure about doing this from a pedagogical perspective... Thanks @Ismor re type defaulting, that is useful but doesn't quite given everything I need. – dorchard Jan 18 '23 at 16:39

1 Answers1

3

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.

Joachim Breitner
  • 25,395
  • 6
  • 78
  • 139
  • Thanks for this (I am using defaulting as well). This was a really helpful explanation. Enabling RebindableSyntax does indeed solve things here, but I'm not sure about this from a pedagogical perspective. I'd like to try to minimise the overhead as much as possible to students (reduce amount of magic). Sadly language extensions are applied on a per module / file basis so I can't even just put this into the FirstPrelude and scoop it up by importing FirstPrelude, and at this point in the course they are not using cabal yet to make packages for their code. Any further ideas? – dorchard Jan 18 '23 at 16:38
  • 1
    Give them a shell script to call instead of GHC with the right flags? Or just tell them “copy thee three lines to the beginning and don’t worry”. You already have “strange” imports. And with `{-# LANGUAGE NoImplicitPrelude #-}`, you have a cleaner variant of `import Prelude()`. – Joachim Breitner Jan 18 '23 at 19:55