Sure, with a bit of type class "magic":
{-# LANGUAGE DataKinds, KindSignatures, UndecidableInstances #-}
data Nat = Z | S Nat
data SNat (n :: Nat) where
SZ :: SNat Z
SS :: SNat n -> SNat (S n)
class ApplyNth (n :: Nat) arg fn fn' | n arg fn -> fn', n fn -> arg where
applyNth :: SNat n -> arg -> fn -> fn'
instance ApplyNth Z a (a -> b) b where
applyNth SZ a f = f a
instance ApplyNth n arg' fn fn' => ApplyNth (S n) arg' (arg0 -> fn) (arg0 -> fn') where
applyNth (SS n) a f = \a0 -> applyNth n a (f a0)
The general type for applyNth
says, it takes an index (a natural number - encoded in the type), an argument, a function, and returns a function.
Note the two functional dependencies. The first says that given the index, the argument, and the input function, the type of the output function is known. This much is obvious. The second says that that given the index and the input function, ApplyNth
is able to look inside the function and figure out what argument it needs!
This function plays pretty well with type inference:
>:t \x -> applyNth (SS SZ) x (^)
\x -> applyNth (SS SZ) x (^)
:: (Num fn', Integral b) => b -> fn' -> fn'
>:t applyNth (SS SZ) 0 (^)
applyNth (SS SZ) 0 (^) :: Num fn' => fn' -> fn'
>:t applyNth (SS SZ) (0 :: Integer) (^)
applyNth (SS SZ) (0 :: Integer) (^) :: Num fn' => fn' -> fn'
>:t applyNth (SS SZ) ('a' :: Char) (^)
<interactive>:1:32: Warning:
Could not deduce (Integral Char) arising from a use of `^'
...
applyNth (SS SZ) ('a' :: Char) (^) :: Num fn' => fn' -> fn'
>let squared = applyNth (SS SZ) 2 (^)
>:t squared
squared :: Num fn' => fn' -> fn'
>squared 3
9
>squared 100
10000
>let f a b c d e = mapM_ putStrLn
[ show n ++ ": " ++ x
| (n,x) <- zip [0..]
[show a, show b, show c, show d, show e] ]
>applyNth SZ 'q' $
applyNth (SS $ SZ) [1,8,42] $
applyNth SZ (True, 10) $
applyNth (SS $ SS $ SS SZ) "abcd" $
applyNth (SS $ SS $ SS SZ) pi $
f
0: (True,10)
1: 'q'
2: [1,8,42]
3: 3.141592653589793
4: "abcd"
You can also define it in operator form:
infixl 9 =:
(=:) :: ApplyNth n arg fn fn' => SNat n -> arg -> fn -> fn'
(=:) = applyNth
r =
SZ =: 'q' $
SS SZ =: [1,8,42] $
SZ =: (True, 10) $
(SS $ SS $ SS SZ) =: "abcd" $
(SS $ SS $ SS SZ) =: pi $
f