Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why won't GHC reduce my type family?

Here's an untyped lambda calculus whose terms are indexed by their free variables. I'm using the singletons library for singleton values of type-level strings.

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

import Data.Singletons
import Data.Singletons.TypeLits

data Expr (free :: [Symbol]) where
    Var :: Sing a -> Expr '[a]
    Lam :: Sing a -> Expr as -> Expr (Remove a as)
    App :: Expr free1 -> Expr free2 -> Expr (Union free1 free2)

A Var introduces a free variable. A lambda abstraction binds a variable which appears free in the body (if there's one which matches). Applications join up the free variables of the two parts of the expression, removing duplicates (so the free variables of x y are x and y, while the free variables of x x are just x). I wrote out those type families:

type family Remove x xs where
    Remove x '[] = '[]
    Remove x (x ': xs) = Remove x xs
    Remove x (y ': xs) = y ': Remove x xs

type family Union xs ys where
    Union xs ys = Nub (xs :++ ys)

type family xs :++ ys where
    '[] :++ ys = ys
    (x ': xs) :++ ys = x ': (xs :++ ys)

type family Nub xs where
    Nub xs = Nub' '[] xs

type family Nub' seen xs where
    Nub' seen '[] = '[]
    Nub' seen (x ': xs) = If (Elem x seen) (Nub' seen xs) (Nub' (x ': seen) (x ': xs))

type family If c t f where
    If True t f = t
    If False t f = f

type family Elem x xs where
    Elem x '[] = False
    Elem x (x ': xs) = True
    Elem x (y ': xs) = Elem x xs

I tested this out at the interactive prompt:

ghci> :t Var (sing :: Sing "x")
Var (sing :: Sing "x") :: Expr '["x"]  -- good
ghci> :t (Lam (sing :: Sing "x") (Var (sing :: Sing "x")))
(Lam (sing :: Sing "x") (Var (sing :: Sing "x")))
  :: Expr (Remove "x" '["x"])  -- not so good

I was surprised to learn that the type of the identity function \x. x is Expr (Remove "x" '["x"]), not Expr '[]. GHC seems unwilling to evaluate the type family Remove. I experimented a little more and learned that it's not a problem with my type family per se - GHC is happy to reduce it in this case:

ghci> :t (Proxy :: Proxy (Remove "x" '["x"]))
(Proxy :: Proxy (Remove "x" '["x"])) :: Proxy '[]

So: Why won't GHC reduce Remove "x" '["x"] to '[] when I query the type of my GADT? In general, when will-or-won't the type checker evaluate a type family? Are there heuristics I can use to avoid being surprised by its behaviour?

like image 675
Benjamin Hodgson Avatar asked Jul 16 '15 20:07

Benjamin Hodgson


1 Answers

It works. GHC seems to be just lazy.

λ *Main > :t (Lam (Proxy :: Proxy "x") (Var (Proxy :: Proxy "x")))
(Lam (Proxy :: Proxy "x") (Var (Proxy :: Proxy "x")))
  :: Expr (Remove "x" '["x"])

λ *Main > :t (Lam (Proxy :: Proxy "x") (Var (Proxy :: Proxy "x"))) :: Expr '[]
(Lam (Proxy :: Proxy "x") (Var (Proxy :: Proxy "x"))) :: Expr '[]
  :: Expr '[]

λ *Main > :t (Lam (Proxy :: Proxy "x") (Var (Proxy :: Proxy "x"))) :: Expr '["x"]

<interactive>:1:2:
    Couldn't match type ‘'[]’ with ‘'["x"]’
    Expected type: Expr '["x"]
      Actual type: Expr (Remove "x" '["x"])
    In the expression:
        (Lam (Proxy :: Proxy "x") (Var (Proxy :: Proxy "x"))) ::
          Expr '["x"]

I changed definitions so there isn't dependency on singletons library (easier to test in ad-hoc):

{-# LANGUAGE TypeOperators, DataKinds, TypeFamilies, GADTs #-}

import Data.Proxy
import GHC.TypeLits

type family Remove (x :: Symbol) (xs :: [Symbol]) where
    Remove x '[] = '[]
    Remove x (x ': xs) = Remove x xs
    Remove x (y ': xs) = y ': Remove x xs

data Expr (free :: [Symbol]) where
    Var :: KnownSymbol a => Proxy a -> Expr '[a]
    Lam :: KnownSymbol a => Proxy a -> Expr as -> Expr (Remove a as)
--    App :: Expr free1 -> Expr free2 -> Expr (Union free1 free2)
like image 141
phadej Avatar answered Sep 20 '22 16:09

phadej