首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >定义arity泛型lift

定义arity泛型lift
EN

Stack Overflow用户
提问于 2018-05-29 07:00:33
回答 1查看 165关注 0票数 1

我正在尝试为Haskell定义liftN。像JS这样的动态类型语言中的值级实现相当简单,我只是在用Haskell表达它时遇到了麻烦。

经过反复试验,我得出了以下结论,即检查类型(注意,liftN的整个实现都是undefined):

代码语言:javascript
复制
{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, TypeFamilies, TypeOperators, UndecidableInstances #-}

import Data.Proxy
import GHC.TypeLits

type family Fn x (y :: [*]) where
  Fn x '[]    = x
  Fn x (y:ys) = x -> Fn y ys

type family Map (f :: * -> *) (x :: [*]) where
  Map f '[]     = '[]
  Map f (x:xs)  = (f x):(Map f xs)

type family LiftN (f :: * -> *) (x :: [*]) where
  LiftN f (x:xs)  = (Fn x xs) -> (Fn (f x) (Map f xs))

liftN :: Proxy x -> LiftN f x
liftN = undefined

这为我在ghci中提供了所需的行为:

代码语言:javascript
复制
*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的实际递归实现。

不幸的是,我甚至不能让puremap进行类型检查。下面是我添加的内容(注意go仍然是undefined):

代码语言:javascript
复制
type family Length (x :: [*]) where
  Length '[]    = 0
  Length (x:xs) = 1 + (Length xs)

liftN :: (KnownNat (Length x)) => Proxy x -> LiftN f x
liftN (Proxy :: Proxy x) = go (natVal (Proxy :: Proxy (Length x))) where
  go = undefined

到目前一切尚好。但随后:

代码语言:javascript
复制
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

...disaster strikes:

代码语言:javascript
复制
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的主体?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-05-29 07:37:04

这里有两个问题:

  • 为了确保整个函数的类型检查,你需要的不仅仅是类型级别数字的natVal:你还需要一个证据,证明你递归的结构对应于你引用的类型级别数字。在Integer中,类型没有运行时表示,因此传入Proxy a与传入()相同。你需要在某个地方获取运行时信息。

这两个问题都可以使用单例或类来解决:

代码语言:javascript
复制
{-# 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 #-}

此示例改编自Richard Eisenberg's thesis

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50574309

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档