使用GADT在Haskell中重新创建Lisp的`apply`

时间:2015-09-17 10:34:34

标签: haskell dependent-type gadt

作为练习,我试图在Haskell中重新创建Lisp的apply。我不打算将它用于任何实际目的,我认为这是一个很好的机会,可以更熟悉Haskell的类型系统和类型系统。 (所以我也不是在寻找其他人的实现。)

我的想法如下:我可以使用GADT来“标记”列表,其中包含可应用的函数类型。因此,我以类似于使用Nil定义对类型中的列表长度进行编码的方式重新定义ConsNat,但不是使用Peano数字,而是以编码方式编码长度在标记函数类型中(即length对应于函数的参数个数)。

这是我到目前为止的代码:

{-# 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

listApply :: (n -> o) -> FList (n -> o) o a -> o
-- I match on (Cons p Nil) because I always want fun to be a function (n -> o)
listApply fun (Cons p Nil) = fun p
listApply fun (Cons p l) = listApply (fun p) l

main = print $ listApply (+) args2

在最后一行中,我的想法是(+)将是Int -> Int -> Int类型,其中Int -> Int对应于n中的(n -> o)和{ {1}}对应于最后一个o(输出)[1]。据我所知,这种类型似乎与我的Int定义的类型有关。

但是,我收到两个错误,其中我将说明与我相关的组件:

argsN

test.hs:19:43:
    Could not deduce (f ~ (n0 -> f))
    from the context ((n -> o) ~ (a -> f))
      bound by a pattern with constructor
                 Cons :: forall o a f. a -> FList f o a -> FList (a -> f) o a,
               in an equation for ‘listApply’

我不确定如何解释第一个错误。第二个错误使我感到困惑,因为它与我之前用[1]标记的解释不符。

对出现问题的任何见解?

P.S:我非常愿意了解新的扩展程序,如果这样可以解决这个问题。

2 个答案:

答案 0 :(得分:9)

你几乎是对的。递归应该遵循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))

必须说,这看起来很优雅!

即使OP也没有要求其他人实施。您可以稍微改变一下问题,从而产生一个看起来不同但又整洁的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

我们可以使用Nat中的GHC.TypeLits,但我们需要UndecidableInstances。在这个例子中,添加的糖不值得。

如果你想制作多态版本,那也是可能的,但索引不是(n :: Nat) (a :: *)而是(as :: [*])。对于两种编码,同时制作plusN可能是一个很好的练习。

答案 1 :(得分:2)

不一样,但我怀疑你会对free applicative functor所提供的free library感兴趣。它是这样的(基于free中的实现,但使用:<**>构造函数而不是Ap):

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

您可以将这些视为异类型列表,其中包含f x0,...,f xn类型的元素,以Pure (f :: x0 -> ... -> xn -> a)结尾。这就像一个&#34;语法树&#34;对于应用计算,允许您使用常规的应用方法来构建一个&#34;树&#34;可以由解释器功能单独运行。

练习:实施以下实例:

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

提示:Applicative法律提供了一个可以用来实现这些的配方。