Haskell 广义提升的定义
我正试图为哈斯克尔定义liftN。像JS这样的动态类型语言中的值级实现相当简单,我只是在用Haskell表达它时遇到了麻烦 经过一些尝试和错误,我得出以下结论,类型检查注意到liftN的整个实现是未定义的: 这为我提供了ghci中所需的行为:Haskell 广义提升的定义,haskell,dependent-type,applicative,type-families,Haskell,Dependent Type,Applicative,Type Families,我正试图为哈斯克尔定义liftN。像JS这样的动态类型语言中的值级实现相当简单,我只是在用Haskell表达它时遇到了麻烦 经过一些尝试和错误,我得出以下结论,类型检查注意到liftN的整个实现是未定义的: 这为我提供了ghci中所需的行为: *Main> :t liftN (Proxy :: Proxy '[a]) liftN (Proxy :: Proxy '[a]) :: a -> f a *Main> :t liftN (Proxy :: Proxy '[a, b])
*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
等等
我遇到的难题是如何实际实现它。我在想,也许最简单的方法是将类型级别列表交换为表示其长度的类型级别编号,使用natVal获得相应的值级别编号,然后将1分派给pure,2分派给map,n分派给最终的liftN的实际递归实现
不幸的是,我甚至不能让pure和map案例进行类型检查。以下是我添加的注释go仍然未定义:
到目前为止还不错。但是:
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
…灾难袭击:
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.
在这一点上,我不清楚什么是模棱两可的,或者如何消除模棱两可
有没有一种方法可以优雅地,或者如果没有那么优雅,那么,以一种方式,将不优雅限制在函数实现中,在这里实现liftN的主体?这里有两个问题: 您需要的不仅仅是类型级别号的natVal来确保整个函数类型检查:您还需要一个证明,证明您正在递归的结构对应于您所引用的类型级别号。Integer本身会丢失所有类型级别的信息。 相反,您需要的运行时信息不仅仅是类型:在Haskell中,类型没有运行时表示,因此传入代理a与传入相同。您需要在某个地方获取运行时信息。 这两个问题都可以使用单例或类来解决:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
data Nat = Z | S Nat
type family AppFunc f (n :: Nat) arrows where
AppFunc f Z a = f a
AppFunc f (S n) (a -> b) = f a -> AppFunc f n b
type family CountArgs f where
CountArgs (a -> b) = S (CountArgs b)
CountArgs result = Z
class (CountArgs a ~ n) => Applyable a n where
apply :: Applicative f => f a -> AppFunc f (CountArgs a) a
instance (CountArgs a ~ Z) => Applyable a Z where
apply = id
{-# INLINE apply #-}
instance Applyable b n => Applyable (a -> b) (S n) where
apply f x = apply (f <*> x)
{-# INLINE apply #-}
-- | >>> lift (\x y z -> x ++ y ++ z) (Just "a") (Just "b") (Just "c")
-- Just "abc"
lift :: (Applyable a n, Applicative f) => (b -> a) -> (f b -> AppFunc f n a)
lift f x = apply (fmap f x)
{-# INLINE lift #-}
此示例改编自。这里有两个问题: 您需要的不仅仅是类型级别号的natVal来确保整个函数类型检查:您还需要一个证明,证明您正在递归的结构对应于您所引用的类型级别号。Integer本身会丢失所有类型级别的信息。 相反,您需要的运行时信息不仅仅是类型:在Haskell中,类型没有运行时表示,因此传入代理a与传入相同。您需要在某个地方获取运行时信息。 这两个问题都可以使用单例或类来解决:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
data Nat = Z | S Nat
type family AppFunc f (n :: Nat) arrows where
AppFunc f Z a = f a
AppFunc f (S n) (a -> b) = f a -> AppFunc f n b
type family CountArgs f where
CountArgs (a -> b) = S (CountArgs b)
CountArgs result = Z
class (CountArgs a ~ n) => Applyable a n where
apply :: Applicative f => f a -> AppFunc f (CountArgs a) a
instance (CountArgs a ~ Z) => Applyable a Z where
apply = id
{-# INLINE apply #-}
instance Applyable b n => Applyable (a -> b) (S n) where
apply f x = apply (f <*> x)
{-# INLINE apply #-}
-- | >>> lift (\x y z -> x ++ y ++ z) (Just "a") (Just "b") (Just "c")
-- Just "abc"
lift :: (Applyable a n, Applicative f) => (b -> a) -> (f b -> AppFunc f n a)
lift f x = apply (fmap f x)
{-# INLINE lift #-}
此示例改编自。谢谢。如果我理解正确的话,核心问题是不可能在代理1、代理2等上进行调度。在运行时,它们看起来都一样。如果我改为用具体的数据类型对算法进行编码,正如您所看到的那样,我的原始代码会工作吗?编辑:我想我没有领会这篇总结中第一点的要点。基本上,要使用类型级信息,您需要能够往返于类型级和运行时表示。当您使用natVal时,您会得到一个整数:但是,您不能从整数返回到类型级别,因为它没有关于其值的类型级别信息。因此,当你匹配它时,即在go 1=…,当你想知道右边应该有对应于1个案例的类型时,GHC只能知道右边的类型与所有其他案例的类型相同。我明白了。因此,这两个因素的结合意味着我不能直接在异构列表本身的代理上分派,因为它没有运行时表示,也不能因为丢失信息而将其转换为Zs和Succs。我想唯一的解决办法是直接匹配函数。遗憾的是,Haskell不允许在类型族的RHS中使用量化变量,否则LiftN可以通过类型族LiftN f:*->*n:“Nat where.是的,这是正确的。另一种方法是使用单例,它将运行时和类型级别的信息封装在一起,允许您在每个数据之间进行转换;Sy::Natty n->Natty S nThanks。如果我理解正确的话,核心问题是不可能在代理1、代理2等上进行调度。在运行时,它们看起来都一样。如果我改为用具体的数据类型对算法进行编码,正如您所看到的那样,我的原始代码会工作吗?编辑:我想我没有领会这篇总结中第一点的要点。基本上,要使用类型级信息,您需要能够往返于类型级和运行时表示。当您使用natVal时,您会得到一个整数:但是,您不能从整数返回到类型级别,因为它没有关于其值的类型级别信息。因此,当你匹配它时,即在go 1=…,当你想知道右边应该有对应于1个案例的类型时,GHC只能知道右边的类型与所有其他案例的类型相同。我明白了。所以这两个因素的结合意味着我不能分派directl
因为它没有运行时表示,所以不能在异构列表本身的代理上使用y,也不能因为丢失信息而将其转换为Zs和Succs。我想唯一的解决办法是直接匹配函数。遗憾的是,Haskell不允许在类型族的RHS中使用量化变量,否则LiftN可以通过类型族LiftN f:*->*n:“Nat where.是的,这是正确的。另一种方法是使用单例,它将运行时和类型级别的信息封装在一起,允许您在每个数据之间进行转换;Sy::Natty n->Natty S nIn应用程序,liftAn f x1。。xn最近变得不那么流行,经常被f x1 x2取代。。。推广到任意n的xn。你能用点类似的吗?@chi遗憾的是不能。我在这里的用法不明确;我需要提升各种算术函数,然后用提升的函数做更多的事情,而不是直接应用它们。我可以做\x y z w->f x y…,但那很烦人。在应用程序中,liftAn f x1。。xn最近变得不那么流行,经常被f x1 x2取代。。。推广到任意n的xn。你能用点类似的吗?@chi遗憾的是不能。我在这里的用法不明确;我需要提升各种算术函数,然后用提升的函数做更多的事情,而不是直接应用它们。我可以做\x y z w->f x y…,但那很烦人。
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
data Nat = Z | S Nat
type family AppFunc f (n :: Nat) arrows where
AppFunc f Z a = f a
AppFunc f (S n) (a -> b) = f a -> AppFunc f n b
type family CountArgs f where
CountArgs (a -> b) = S (CountArgs b)
CountArgs result = Z
class (CountArgs a ~ n) => Applyable a n where
apply :: Applicative f => f a -> AppFunc f (CountArgs a) a
instance (CountArgs a ~ Z) => Applyable a Z where
apply = id
{-# INLINE apply #-}
instance Applyable b n => Applyable (a -> b) (S n) where
apply f x = apply (f <*> x)
{-# INLINE apply #-}
-- | >>> lift (\x y z -> x ++ y ++ z) (Just "a") (Just "b") (Just "c")
-- Just "abc"
lift :: (Applyable a n, Applicative f) => (b -> a) -> (f b -> AppFunc f n a)
lift f x = apply (fmap f x)
{-# INLINE lift #-}