Recreating Lisp's `apply` in Haskell using GADTs

走远了吗. 提交于 2019-12-04 05:11:52

You got it almost right. Recursion should follow the structure of GADT:

{-# LANGUAGE GADTs #-}
-- n represents structure of the function I apply to
-- o represents output type of the function
-- a represents argument type of the function (all arguments same type)
data FList n o a where
  -- with Nil the function is the output
  Nil :: FList o o a
  -- with Cons the corresponding function takes one more argument
  Cons :: a -> FList f o a -> FList (a -> f) o a

args0 = Nil :: FList Int Int Int -- will not apply an argument
args1 = Cons 1 args0 -- :: FList (Int -> Int) Int Int
args2 = Cons 2 args1 -- :: FList (Int -> Int -> Int) Int Int
args3 = Cons 3 args2 -- :: FList (Int -> Int -> Int -> Int) Int Int

-- n, not (n -> o)
listApply :: n -> FList n o a -> o
listApply fun Nil = fun
listApply fun (Cons p l) = listApply (fun p) l

main = print $ listApply (+) args2

three :: Int
three = listApply (+) (Cons 2 (Cons 1  Nil))

oof :: String
oof = listApply reverse (Cons "foo" Nil)

true :: Bool
true = listApply True Nil -- True

-- The return type can be different than the arguments:

showplus :: Int -> Int -> String
showplus x y = show (x + y)

zero :: String
zero = listApply showplus (Cons 2 (Cons 1 Nil))

Must say, that this looks quite elegant!


Even OP doesn't ask for other's people implementation. You can approach problem a bit differently, resulting in a different looking but neat API:

{-# LANGUAGE KindSignatures #-}
{-# LANGuAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE AllowAmbiguousTypes #-}

import Data.Proxy

data N = O | S N

p0 :: Proxy O
p1 :: Proxy (S O)
p2 :: Proxy (S (S O))
p0 = Proxy
p1 = Proxy
p2 = Proxy

type family ArityNFun (n :: N) (a :: *) (b :: *) where
  ArityNFun O a b = b
  ArityNFun (S n) a b = a -> ArityNFun n a b

listApply :: Proxy n -> ArityNFun n a b -> ArityNFun n a b
listApply _ = id

three :: Int
three = listApply p2 (+) 2 1

oof :: String
oof = listApply p1 reverse "foo"

true :: Bool
true = listApply p0 True

showplus :: Int -> Int -> String
showplus x y = show (x + y)

zero :: String
zero = listApply p2 showplus 0 0

Here we could use Nat from GHC.TypeLits, but then we'd need UndecidableInstances. The added sugar is not worth the trouble in this example.

If you want to make polymorphic version, that's also possible, but then index is not (n :: Nat) (a :: *) but (as :: [*]). Also making plusN could be a nice exercise, for both encodings.

Not the same thing, but I suspect you'll be interested in the free applicative functor, which the free library provides. It goes something like this (based on the implementation in free, but using a :<**> constructor instead of Ap):

data Ap f a where
  Pure :: a -> Ap f a
  (:<**>) :: f x -> Ap f (x -> a) -> Ap f a

You can think of these as a heterogeneously-typed list with elements of types f x0, ..., f xn, terminated by Pure (f :: x0 -> ... -> xn -> a). This is like a "syntax tree" for applicative computations, allowing you to use the regular applicative methods to build up a "tree" that can be separately run by interpreter functions.


Exercise: implement the following instances:

instance Functor f => Functor (Ap f) where ...
instance Functor f => Applicative (Ap f) where ...

Hint: the Applicative laws provide a recipe that you can use to implement these.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!