I'm trying to define liftN
for Haskell. The value-level implementation in dynamically typed languages like JS is fairly straightforward, I'm just having trouble expressing it in Haskell.
After some trial and error, I arrived at the following, which typechecks (note the entire implementation of liftN
is undefined
):
{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-}
import Data.Proxy
import GHC.TypeLits
type family Fn x (y :: [*]) where
Fn x '[] = x
Fn x (y:ys) = x -> Fn y ys
type family Map (f :: * -> *) (x :: [*]) where
Map f '[] = '[]
Map f (x:xs) = (f x):(Map f xs)
type family LiftN (f :: * -> *) (x :: [*]) where
LiftN f (x:xs) = (Fn x xs) -> (Fn (f x) (Map f xs))
liftN :: Proxy x -> LiftN f x
liftN = undefined
This gives me the desired behavior in ghci:
*Main> :t liftN (Proxy :: Proxy '[a])
liftN (Proxy :: Proxy '[a]) :: a -> f a
*Main> :t liftN (Proxy :: Proxy '[a, b])
liftN (Proxy :: Proxy '[a, b]) :: (a -> b) -> f a -> f b
and so on.
The part I'm stumped on is how to actually implement it. I was figuring maybe the easiest way is to exchange the type level list for a type level number representing its length, use natVal
to get the corresponding value level number, and then dispatch 1
to pure
, 2
to map
and n
to (finally), the actual recursive implementation of liftN
.
Unfortunately I can't even get the pure
and map
cases to typecheck. Here's what I added (note go
is still undefined
):
type family Length (x :: [*]) where
Length '[] = 0
Length (x:xs) = 1 + (Length xs)
liftN :: (KnownNat (Length x)) => Proxy x -> LiftN f x
liftN (Proxy :: Proxy x) = go (natVal (Proxy :: Proxy (Length x))) where
go = undefined
So far so good. But then:
liftN :: (Applicative f, KnownNat (Length x)) => Proxy x -> LiftN f x
liftN (Proxy :: Proxy x) = go (natVal (Proxy :: Proxy (Length x))) where
go 1 = pure
go 2 = fmap
go n = undefined
...disaster strikes:
Prelude> :l liftn.hs
[1 of 1] Compiling Main ( liftn.hs, interpreted )
liftn.hs:22:28: error:
* Couldn't match expected type `LiftN f x'
with actual type `(a0 -> b0) -> (a0 -> a0) -> a0 -> b0'
The type variables `a0', `b0' are ambiguous
* In the expression: go (natVal (Proxy :: Proxy (Length x)))
In an equation for `liftN':
liftN (Proxy :: Proxy x)
= go (natVal (Proxy :: Proxy (Length x)))
where
go 1 = pure
go 2 = fmap
go n = undefined
* Relevant bindings include
liftN :: Proxy x -> LiftN f x (bound at liftn.hs:22:1)
|
22 | liftN (Proxy :: Proxy x) = go (natVal (Proxy :: Proxy (Length x))) where
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.
At this point it isn't clear to me what exactly is ambiguous or how to disambiguate it.
Is there a way to elegantly (or if not-so-elegantly, in a way that the inelegance is constrained to the function implementation) implement the body of liftN
here?