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

标签 haskell dependent-type gadt

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

我的想法如下:我可以使用 GADT 来“标记”一个列表,其中包含它可以应用到的函数的类型。所以,我重新定义 NilCons以类似的方式,我们将使用 Nat 对类型中的列表长度进行编码。定义,但不使用 Peano 数字,长度以某种方式编码在标记函数类型中(即长度对应于函数的参数数量)。

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

{-# 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)o对应于最后 Int (输出)[1]。据我所知,这种类型似乎适用于我的 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’


test.hs:21:34:
    Couldn't match type ‘Int’ with ‘Int -> Int’
    Expected type: FList (Int -> Int -> Int) (Int -> Int) Int
      Actual type: FList (Int -> Int -> Int) Int Int
    In the second argument of ‘listApply’, namely ‘args2’

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

对出了什么问题有任何见解吗?

P.S:如果这能让这项工作正常进行,我非常愿意了解新的扩展。

最佳答案

你几乎是对的。递归应该遵循 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对于两种编码来说,这可能是一个很好的练习。

关于haskell - 使用 GADT 在 Haskell 中重新创建 Lisp 的 `apply`,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32628236/

相关文章:

haskell - 模式匹配 - Prolog vs. Haskell

types - Agda:形成所有对 {(x , y) | x 在 xs,y 在 ys}

haskell - 用于平衡二叉树根的单通透镜

pattern-matching - 使用 GADT 建模语法,但类型参数无法统一

haskell - monad 中的 "run"是什么意思?

postgresql - 如何在 Haskell 中使用 postgresql-simple 插入 bytea 值

agda - 将一种类型提升到更高的宇宙

haskell - 除了安全之外,强打字还有其他好处吗?

haskell - 从包装器中动态匹配嵌套的 GADT

haskell - `wreq` Get/Post 带异常处理