3

I am using very simple type-level naturals generated with the singletons package. I am now trying to add an Ord instance to them.

{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell, KindSignatures, DataKinds, ScopedTypeVariables, GADTs, TypeFamilies, FlexibleInstances, TypeOperators, UndecidableInstances, InstanceSigs #-}

module Functions where

import Data.Singletons
import Data.Singletons.TH
import Data.Singletons.Prelude
import Data.Promotion.Prelude

singletons [d|
             data Nat = Z | S Nat
               deriving Eq

             instance Ord Nat where
               (<=)    Z     _  = True
               (<=) (S _)    Z  = False
               (<=) (S n) (S m) = n <= m
             |]

I have been hitting one error after the other. The latest one is:

src/Functions.hs:10:1:
    Couldn't match kind ‘Nat’ with ‘*’
    When matching types
      n0 :: Nat
      t1 :: *
    Expected type: Sing t1
      Actual type: Sing n0
    Relevant bindings include
      n_a9na :: Sing n0 (bound at src/Functions.hs:10:1)
      lambda :: Sing n0 -> Sing m0 -> Sing (Apply (Apply (:<=$) t00) t10)
        (bound at src/Functions.hs:10:1)
    In the second argument of ‘applySing’, namely ‘n_a9na’
    In the first argument of ‘applySing’, namely
      ‘applySing (singFun2 (Proxy :: Proxy (:<=$)) (%:<=)) n_a9na’

src/Functions.hs:10:1:
    Could not deduce (SOrd 'KProxy) arising from a use of ‘%:<=’
    from the context (t00 ~ 'S n)
      bound by a pattern with constructor
                 SS :: forall (z_a9mg :: Nat) (n_a9mh :: Nat).
                       (z_a9mg ~ 'S n_a9mh) =>
                       Sing n_a9mh -> Sing z_a9mg,
               in an equation for ‘%:<=’
      at src/Functions.hs:(10,1)-(18,15)
    or from (t10 ~ 'S n1)
      bound by a pattern with constructor
                 SS :: forall (z_a9mg :: Nat) (n_a9mh :: Nat).
                       (z_a9mg ~ 'S n_a9mh) =>
                       Sing n_a9mh -> Sing z_a9mg,
               in an equation for ‘%:<=’
      at src/Functions.hs:(10,1)-(18,15)
    or from (t00 ~ Apply SSym0 n0, t10 ~ Apply SSym0 m0)
      bound by the type signature for
                 lambda_a9n9 :: (t00 ~ Apply SSym0 n0, t10 ~ Apply SSym0 m0) =>
                                Sing n0 -> Sing m0 -> Sing (Apply (Apply (:<=$) t00) t10)
      at src/Functions.hs:(10,1)-(18,15)
    In the second argument of ‘singFun2’, namely ‘(%:<=)’
    In the first argument of ‘applySing’, namely
      ‘singFun2 (Proxy :: Proxy (:<=$)) (%:<=)’
    In the first argument of ‘applySing’, namely
      ‘applySing (singFun2 (Proxy :: Proxy (:<=$)) (%:<=)) n_a9na’

Does anyone have an idea what the correct way to do this is?

denormal
  • 273
  • 1
  • 8
  • 1
    I think there might be a bug (or I'm missing some known caveats) since even `[d| data Nat = Z | S Nat deriving (Eq,Ord) ]` fails. That said, I get a different error message than you. What version of GHC are you using? – Alec Aug 17 '16 at 17:22
  • Indeed, `deriving Ord` fails for me too. I am using 7.10.3. – denormal Aug 17 '16 at 17:32

2 Answers2

5

I'm not sure why this is failing. I am equally puzzled by a similar failure I get when implementing compare instead, and even more puzzled by the failure I get when trying the (seemingly simple)

singletons [d| data Nat = Z | S Nat deriving (Eq,Ord) |]

My guess is that something in Ord is off... However, this works. I'm gonna try to take a look at the guts of singleton later.

singletons [d|
              data Nat = Z | S Nat
                 deriving (Eq)

              instance Ord Nat where
                compare = compare'

              compare' :: Nat -> Nat -> Ordering
              compare' Z Z  = EQ
              compare' (S _) Z = GT
              compare' Z (S _) = LT
              compare' (S n) (S m) = compare' n m
             |] 

By the way, I'm using GHC 8.0 here.

EDIT

After poking around in singletons, I've found the real source of problems (and been blown away with how much type-level hackery is possible). Using -ddump-splices from GHC I was able to get the actual Haskell code generated (for the initial code in your question). The offending parts were

instance PEq (Proxy :: Proxy Nat_a7Vb) where
  type (:==) (a_a8Rs :: Nat_a7Vb) (b_a8Rt :: Nat_a7Vb) = Equals_1627424016_a8Rr a_a8Rs b_a8Rt

and

instance POrd (Proxy :: Proxy Nat_a7Vb) where
  type (:<=) (a_aa9e :: Nat_a7Vb) (a_aa9f :: Nat_a7Vb) = Apply (Apply TFHelper_1627428966Sym0 a_aa9e) a_aa9f

Compiling the code generated, I received the slightly more useful error message for both of these

Expecting one more argument to ‘Proxy’
Expected kind ‘Proxy Nat_a7Vb’, but ‘Proxy’ has kind ‘k0 -> *’

pertaining to the (Proxy :: Proxy Nat_a7Vb) in the PEq and POrd classes. That won't compile without -XPolyKinds. Checked the repo for singletons and indeed it tells you that you need -XTypeInType enabled, which in turn enables -XPolyKinds.

So, there is no bug, you just need to add either PolyKinds or TypeInType (I recommend the latter, since that is what the package recommends...) to your LANGUAGE pragmas to get everything to work.

Alec
  • 31,829
  • 7
  • 67
  • 114
  • Bizarre. Confirming your solution works on 7.10.3 too. I am not even going to ask how you came up with this (I am pretty new to Haskell let alone this black magic stuff), but congratulations that you did! Should I file a bug report? Anyway thank you. – denormal Aug 17 '16 at 17:40
  • 1
    @denormal The error in both cases (implementing `(<=)` or `compare`) came from the recursive call, and the recursive call is always tougher in a typeclass (because you need the to assume the constraint for the duration of the constraint definition). I thought defining the recursion elsewhere would make this easier. I'm going to dive into this after work today - I'm not sure yet if this is even a bug or I'm missing something else. – Alec Aug 17 '16 at 17:49
  • 1
    @denormal Rereading my comment, I realize it doesn't make much sense. Directly: when I use `(<=)` on the right hand side of an equation in `instance Ord Nat where ..`, I need to have assumed the constraint `Ord Nat`. I think that might be related to what is tripping up `singleton`. – Alec Aug 17 '16 at 17:59
  • This makes sense. I tried writing `(<=)` as a non-recursive call `(<=) = le` and it worked. I also tried writing a manual instance of `Eq` with `(==)` defined recursively and I got the same error message. So, are we to assume that the derived version of `(==)` is defined in the former manner and the derived version of `(<=)` in the latter? Obviously GHC has no problem deducing `Ord Nat` for value-level `Nat`s. – denormal Aug 17 '16 at 18:51
  • @denormal Yeah, I noticed that too. I wonder what the generated `Eq` instance looks like. – Alec Aug 17 '16 at 19:39
2

Working with lifted Boolean relations is never comfortable. Booleans erase the very information you're interested in learning, leaving you in the lurch when you want to do anything with the result of your test. Just say no, kids.

There's a better way. "n is less-than-or-equal to m" is a proposition which can be proved with information-rich evidence. One way of proving that one number is less than another is by giving (a singleton representation of) their difference:

data LE n m where
    LE :: Natty z -> LE n (n :+ z)

We can come up with a procedure for testing whether a given number is less than another. le attempts to subtract n from m, and either fails and returns Nothing or produces their difference, as a Natty, and a proof that the subtraction is correct, packed up in the LE constructor.

le :: Natty n -> Natty m -> Maybe (LE n m)
le Zy m = Just (LE m)
le (Sy n) (Sy m) = fmap (\(LE z) -> LE z) (le n m)
le _ _ = Nothing

This idea can be generalised to give us a "strongly-typed compare". When comparing two numbers, you'll either learn that they're equal, or that one is less than the other. (Either (LE n m) (LE m n) also does the job, but this version is slightly more precise.)

data Compare n m where
    LT :: Natty z -> Compare n (n :+ S z)
    EQ :: Compare n n
    GT :: Natty z -> Compare (m :+ S z) m

compare :: Natty n -> Natty m -> Compare n m
compare Zy Zy = EQ
compare Zy (Sy m) = LT m
compare (Sy n) Zy = GT n
compare (Sy n) (Sy m) = case compare n m of
    LT z -> LT z
    EQ -> EQ
    GT z -> GT z

(I lifted this from Hasochism.)

Note that, unlike le, compare is total. It'll always give you a result: every number is either less than, equal to, or greater than every other number. Our goal was to write a procedure to test which of two numbers was smaller, but we also found ourselves proving that numbers are totally ordered, and writing a type-safe subtraction routine, all in the same function.

Another way of looking at compare is as a view over pairs of natural numbers. When you compare two numbers, you learn which one was less and by how much, refining your knowledge of the numbers themselves. Agda's dot-patterns have good support for this notion of refinement:

compare : (n m : Nat) -> Compare n m
compare zero zero = eq
compare zero (suc m) = lt m
compare (suc n) zero = gt n
compare (suc n)            (suc m)           with compare n m
                        -- see how matching on `lt` refines `m` to `n + suc z`
compare (suc n)            (suc .(n + suc z)) | lt z = lt z
compare (suc m)            (suc .m)           | eq = eq
     -- likewise matching on `gt` refines `n` to `m + suc z`
compare (suc .(m + suc z)) (suc m)            | gt z = gt z

Anyway, I can't speak to the source of your singletons bug directly, but one reason Ord is kinda difficult to work with for singleton values is that it assumes you're comparing values of the same type:

class Ord a where
    compare :: a -> a -> Ordering

When you're comparing two singletons, they generally won't have the same type! That's the whole point of singletons: their type reflects their value directly. If you have two Natty n values (whose ns match) there's not much point in comparing them, since you already know they're equal; and if they're not equal you can't compare them!

It's eminently reasonable that classes like Ord, which were designed in the simply-typed world, won't necessarily be all that useful in dependently-typed programs. If you're using dependent types, the right way to do it is not to abuse the old tools. Usher in this new world of safe, information-rich programming with open arms!

Community
  • 1
  • 1
Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • The confounding thing here is that the same applies to `Eq`, yet that problem never arises with `singleton`! – Alec Aug 17 '16 at 18:07
  • @Alec It does. You can't write `Sy Zy == Zy`, even with the help of `singletons`. It's a perfectly reasonable question, but `==` doesn't know how to answer it. – Benjamin Hodgson Aug 17 '16 at 18:11
  • I meant my comment as a reply to your comment about `Ord` being difficult - yet `singletons [d| data Nat = Z | S Nat deriving (Eq) ]` doesn't choke up like `singletons [d| data Nat = Z | S Nat deriving (Eq,Ord) ]` does. Although checking it now, it appears that I can't write the `Eq` instance I'd like to manually (I have to use the same trick from my answer). – Alec Aug 17 '16 at 18:15
  • @Alec You can write the instance for `Nat` (and indeed for `Natty n`) but my point is that it's not useful. – Benjamin Hodgson Aug 17 '16 at 18:16
  • Oh sure, it may not be useful. That concerns me less at the moment, but you are right! :) – Alec Aug 17 '16 at 18:18
  • @BenjaminHodgson Thanks, this is a good answer and I have a lot to learn. I am actually trying to use the type-level `(<=)` or perhaps `Max` as a constraint (resp. type function) in the definition of a data type (e.g. I'd like to define an integer list that keeps track of its maximum element). It's not apparent how `data LE` or `le` could be used like that? Anyway, while yours is a good anwer, I think I have to have accept Alec's since it specifically addresses the problem in the question. – denormal Aug 18 '16 at 07:08
  • 2
    @denormal One of the valuable pieces of advice in [_How to Keep Your Neighbours in Order_](https://personal.cis.strath.ac.uk/conor.mcbride/Pivotal.pdf) is to avoid using types to _measure_ values from the inside out and instead see them as _constraints_ pushed in from the outside. What are you using your bounded list for? Rather than "a list whose maximum value is precisely `n`" would the slightly looser type `[Fin n]` (a list of numbers bounded by `n`) do the job? – Benjamin Hodgson Aug 18 '16 at 09:45
  • Actually, that's exactly what I am after. :) – denormal Aug 18 '16 at 09:51
  • Thank you very much, I didn't know there was a standard way to define this. – denormal Aug 18 '16 at 10:08