12

I would like to have a type which can contain values 0 to n, where n lives on the type level.

I was trying something like:

import GHC.TypeLits
import Data.Proxy

newtype FiniteNat n = FiniteNat { toInteger :: Integer }

smartConstructFiniteNat :: (KnownNat n) => Proxy n -> Integer -> Maybe (FiniteNat (Proxy n))
smartConstructFiniteNat pn i 
  | 0 <= i && i < n = Just (FiniteNat i)
  | otherwise       = Nothing
  where n = natVal pn

which works basically, but it's not really satisfying somehow. Is there a "standard" solution, or even a library to achieve this? There is a lot of fuss about dependenty typed list-lengths, but I was unable to find something exactly for this. Also - I assume using GHC.TypeLits is necessary, because my n can take on rather large values, so inductive definition would probably be very slow.

Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
user1747134
  • 2,374
  • 1
  • 19
  • 26
  • 8
    You can easily translate Idris's `Fin`: `data Fin n where { FZ :: Fin (S n) ; FS :: Fin n -> Fin (S n) }`. If you're sure you need an efficient runtime representation then you basically have to do what you did in your question - stuff a machine `Int` into a `newtype` and use a phantom type for its size. To make up for the lack of GADT constructors you have to write higher order programs using `fold :: (forall n. r n -> r (S n)) -> (forall n. r (S n)) -> Fin m -> r m` (and implementing `fold` requires `unsafeCoerce`). – Benjamin Hodgson May 17 '17 at 10:21
  • 3
    The Orgazoid - you should upgrade that to an answer. – rampion May 17 '17 at 15:46
  • @rampion Good idea :) – Benjamin Hodgson May 17 '17 at 16:42
  • 2
    For what it's worth, the thing you write in your question is already offered as the http://hackage.haskell.org/package/finite-typelits library – Justin L. May 18 '17 at 08:32
  • @JustinL. thank you, that is probably the most useful to me right now. – user1747134 May 18 '17 at 09:35

2 Answers2

12

You can directly translate Idris's Fin into the usual Haskell mishmash of sort-of-dependently-typed features.

data Fin n where
    FZ :: Fin (S n)
    FS :: Fin n -> Fin (S n)

(!) :: Vec n a -> Fin n -> a
(x :> xs) ! FZ = x
(x :> xs) ! (FS f) = xs ! f

With TypeInType you can even have singleton Fins!

data Finny n (f :: Fin n) where
    FZy :: Finny (S n) FZ
    FSy :: Finny n f -> Finny (S n) (FS f)

This allows you to fake up dependent quantification over runtime stuff, eg,

type family Fin2Nat n (f :: Fin n) where
    Fin2Nat (S _) FZ = Z
    Fin2Nat (S n) (FS f) = S (Fin2Nat n f)

-- tighten the upper bound on a given Fin as far as possible
tighten :: Finny n f -> Fin (S (Fin2Nat n f))
tighten FZy = FZ
tighten (FSy f) = FS (tighten f)

but, ugh, it kinda sucks to have to duplicate everything at the value and type level, and writing out all your kind variables (n) can get pretty tedious.


If you're really sure you need an efficient runtime representation of Fin, you can do basically what you did in your question: stuff a machine Int into a newtype and use a phantom type for its size. But the onus is on you, the library implementer, to make sure the Int fits the bound!

newtype Fin n = Fin Int

-- fake up the constructors
fz :: Fin (S n)
fz = Fin 0
fs :: Fin n -> Fin (S n)
fs (Fin n) = Fin (n+1)

This version lacks real GADT constructors, so you can't manipulate type equalities using pattern matching. You have to do it yourself using unsafeCoerce. You can give clients a type-safe interface in the form of fold, but they have to be willing to write all their code in a higher-order style, and (since fold is a catamorphism) it becomes harder to look at more than one layer at a time.

-- the unsafeCoerce calls assert that m ~ S n
fold :: (forall n. r n -> r (S n)) -> (forall n. r (S n)) -> Fin m -> r m
fold k z (Fin 0) = unsafeCoerce z
fold k z (Fin n) = unsafeCoerce $ k $ fold k z (Fin (n-1))

Oh, and you can't do type level computation (as we did with Fin2Nat above) with this representation of Fin, because type level Ints don't permit induction.

For what it's worth, Idris's Fin is just as inefficient as the GADT one above. The docs contain the following caveat:

It's probably not a good idea to use Fin for arithmetic, and they will be exceedingly inefficient at run time.

I've heard noises about a future version of Idris being able to spot "Nat with types"-style datatypes (like Fin) and automatically erase the proofs and pack the values into machine integers, but as far as I know we're not there yet.

Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • Is there a library that implements something like this? – user1747134 May 17 '17 at 17:28
  • Not that I'm aware of. DT programming in Haskell is still unusual (and painful) enough that we don't have a huge collection of mature libs. Maybe someone will arrive here with a suggestion... – Benjamin Hodgson May 17 '17 at 17:30
  • I'm of the opinion that this sort of stuff is fun but usually not a good idea for production code. I think this opinion is reasonably widespread, which would partly explain the lack of community libraries. – Benjamin Hodgson May 17 '17 at 17:32
  • @user1747134 There's finite-typelits http://hackage.haskell.org/package/finite-typelits but I'm not sure if it truly corresponds to Idris' `Fin`. It is used in the package vector-sized http://hackage.haskell.org/package/vector-sized – danidiaz May 17 '17 at 18:33
  • 5
    You can also use `PatternSynonyms` to make the constructor values above into two-way patterns - `pattern FZ :: Fin (S n) ; pattern FZ = Fin 0` – rampion May 17 '17 at 19:00
  • 1
    The safe way to do the dangerous optimization is to use `Natural` instead of `Int`. That avoids overflow trouble. If @rampion hadn't suggested pattern synonyms, I would have; it's the Right Way to offer an interface. Be sure to use a `COMPLETE` pragma with GHC 8.2 to let it know that `FZ` and `FS` cover all the cases. – dfeuer May 17 '17 at 21:10
  • @rampion @dfeuer Thanks, I didn't know you could give type signatures to pattern synonyms. Looks like they still don't behave like GADTs though? `pattern FS :: Fin n -> Fin (S n) ; pattern FS f <- (Fin . pred . unFin -> f) where FS f = Fin (unFin f + 1)` gives a type error when you attempt to match `FS` on a value typed as `Fin n` – Benjamin Hodgson May 17 '17 at 23:46
  • @The Organzoid - that's not a very good pattern for `FS` - it'll match `FZ` so `case FZ of { FS (Fin i) -> i ; _ -> 1 }` evaluates to -1. You want a conditional - `pattern FS f <- (finPred -> Just f) where FS = finSucc` – rampion May 18 '17 at 02:22
  • [here's a gist](https://gist.github.com/rampion/e05fad464a0c5cd4916fc0c027bc8467) though if you're on GHC 8.2+ you should definitely use a `COMPLETE` pragma. – rampion May 18 '17 at 02:26
  • 2
    @rampion, your gist doesn't actually get it quite right. We want matching on the patterns to reveal type information. With your code, we need the type information to use the patterns. For example, try writing a function `f :: Fin n -> Int` that matches on the constructors; it will not typecheck, because it needs to know that `n` is a successor to use the patterns. See my answer. – dfeuer May 18 '17 at 02:34
  • @dfeuer - that's a good point, but it's worth saying that this `Fin Z` is just a fancy name for `Void`, so stating `Fin (S n)` is just saying "this is not `Void`" – rampion May 18 '17 at 02:53
  • 1
    @rampion, you get a bit more out of it than that. As you pattern match through `FS` constructors, you learn more and more about the `Fin` index. You can never pin it down entirely (without additional evidence), but you can bound it from below. – dfeuer May 18 '17 at 03:09
6

rampion suggested pattern synonyms, and I agreed, but it is admittedly not entirely trivial to work out how to structure their signatures properly. Thus I figured I'd write a proper answer to give the full code.

First, the usual boilerplate:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE Trustworthy #-}

module FakeFin (Nat (..), Fin (FZ, FS), FinView (..), viewFin) where
import Numeric.Natural
import Unsafe.Coerce

Now the basic types:

data Nat = Z | S Nat

-- Fin *must* be exported abstractly (or placed in an Unsafe
-- module). Users can use its constructor to implement
-- unsafeCoerce!
newtype Fin (n :: Nat) = Fin Natural
deriving instance Show (Fin n)

It is much easier to work via a view type rather than directly, so let's define one:

data FinView n where
  VZ :: FinView ('S n)
  VS :: !(Fin n) -> FinView ('S n)
deriving instance Show (FinView n)

It is important to note that we could have defined FinView using explicit equality constraints, because we will have to think in those terms to give correct pattern signatures:

data FinView n where
  VZ :: n ~ 'S m => FinView n
  VS :: n ~ 'S m => !(Fin m) -> FinView n

Now the actual view function:

viewFin :: Fin n -> FinView n
viewFin (Fin 0) = unsafeCoerce VZ
viewFin (Fin n) = unsafeCoerce (VS (Fin (n - 1)))

The pattern signatures precisely mirror the signatures of the FinView constructors.

pattern FZ :: () => n ~ 'S m => Fin n
pattern FZ <- (viewFin -> VZ) where
  FZ = Fin 0

pattern FS :: () => n ~ 'S m => Fin m -> Fin n
pattern FS m <- (viewFin -> VS m) where
  FS (Fin m) = Fin (1 + m)

-- Let GHC know that users need only match on `FZ` and `FS`.
-- This pragma only works for GHC 8.2 (and presumably future
-- versions).
{-# COMPLETE FZ, FS #-}

For completeness (because it took me rather more effort to write this than I expected), here's one way to write unsafeCoerce if this module accidentally exports the Fin data constructor. I imagine there are probably simpler ways.

import Data.Type.Equality

type family YahF n a b where
  YahF 'Z a _ = a
  YahF _ _ b = b

newtype Yah n a b = Yah (YahF n a b)

{-# NOINLINE finZBad #-}
finZBad :: 'Z :~: n -> Fin n -> a -> b
finZBad pf q =
  case q of
    FZ -> blah (trans pf Refl)
    FS _ -> blah (trans pf Refl)
  where
    blah :: forall a b m. 'Z :~: 'S m -> a -> b
    blah pf2 a = getB pf2 (Yah a)

    {-# NOINLINE getB #-}
    getB :: n :~: 'S m -> Yah n a b -> b
    getB Refl (Yah b) = b

myUnsafeCoerce :: a -> b
myUnsafeCoerce = finZBad Refl (Fin 0)

finZBad is where all the action happens, but it doesn't do anything remotely improper! If someone really gives us a non-bottom value of type Fin 'Z, then something has already gone terribly wrong. The explicit type equality evidence here is necessary because if GHC sees code wanting 'Z ~ 'S m, it will simply reject it out of hand; GHC doesn't really like hypothetical reasoning in constraints. The NOINLINE annotations are necessary because GHC's simplifier itself uses type information; handling evidence of things it knows very well are impossible confuses it terribly, with extremely arbitrary results. So we block it up and successfully implement The Evil Function.

Community
  • 1
  • 1
dfeuer
  • 48,079
  • 5
  • 63
  • 167