replicate function for a length-indexed list using GHC.TypeLits and singletons

后端 未结 1 951
刺人心
刺人心 2021-01-16 14:27

I\'m trying to write a replicate function for a length-indexed list using the machinery from GHC.TypeLits, singletons, and constraints.

The Vect type an

1条回答
  •  不思量自难忘°
    2021-01-16 15:16

    axiom :: Dict a is very unsafe because the runtime representation of a Dict a depends on the constraint a (which corresponds to a dictionary that is captured by the Dict constructor).

    A KnownNat constraint corresponds to an integer value at runtime, so it is not correct to construct a Dict of KnownNat using unsafeCoerce on a dummy dictionary (in cmpNatGT0KnownNatLaw). In particular, this integer is used in replicateVec to check whether the integer is 0.

    Type equalities (~) are special in that they have no meaningful runtime representation, hence axiom-atizing equalities, if they are correct, technically does not lead to bad runtime behavior because the coerced dictionary is never used, but coercing from Dict () to a Dict (a ~ b) is certainly not a supported use of unsafeCoerce. Coercing between equalities might be more reliable.

    To solve KnownNat constraints, constraints internally associates the type-level operations to their term-level counterparts, see magic in Data.Constraints.Nat and reconstructs the KnownNat dictionary based on implicit knowledge about how GHC represents type classes.


    Anyway, for an inductive construction like replicate, we can avoid KnownNat, and use a different singleton type that reflects the inductive nature of Nat.

    data Sing n where
      Z :: Sing 0
      S :: Sing n -> Sing (1 + n)
    

    This singleton is actually annoying to use because (+) is not injective. (\x -> (1 + x) technically is injective, but GHC can't tell that much.) It would be easier with an actually inductively defined Nat, but still, with the right set of constraints, we can do some things. For example, singleton reflection (mapping from the type-level n to a Sing n value):

    class SingN n where
      singN :: Sing n
    
    instance {-# OVERLAPPING #-} SingN 0 where
      singN = Z
    
    instance (n ~ (1 + n'), n' ~ (n - 1), SingN n') => SingN n where
      singN = S (singN @n')
    

    The list type should be similarly structured:

    data List n a where
      Nil :: List 0 a
      Cons :: a -> List n a -> List (1 + n) a
    

    The reason to set up the type index n this way instead of Sing (n-1) -> Sing n and a -> List (n-1) a -> List n a is to forbid some silly values:

    oops :: Sing 0
    oops = S undefined
    
    ouch :: List 0 ()
    ouch = Cons () undefined
    

    which would be a problem because functions would actually need to handle those cases which make no sense.

    replicate turns out to be straightforward to implement because List and Sing have a lot of structure in common.

    replicate :: Sing n -> a -> List n a
    replicate Z _ = Nil
    replicate (S n) a = Cons a (replicate n a)
    

    We can now apply replicate as follows:

    replicate (singN @3) "x"
    
    • Compilable gist

    0 讨论(0)
提交回复
热议问题