【发布时间】:2018-09-20 14:16:57
【问题描述】:
使用conkin 包:https://hackage.haskell.org/package/conkin
我希望能够获取任何 Conkin.Traversable 并将其转储到 Tuple 留下 indices 到 Tuple 中,以便我可以重建它。
我正在使用一些语言扩展:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
模块声明
module TupleDump where
进口
import Control.Monad.State (State, runState)
import qualified Control.Monad.State as State
import Data.Functor.Compose (getCompose)
import Data.Functor.Const (Const (Const), getConst)
import Conkin (Dispose (..), Flip (..), Tuple (..))
import qualified Conkin
我不想使用 unsafeCoerce,但看不到绕过它的方法:
import Unsafe.Coerce (unsafeCoerce)
让我们将Index 定义为:
data Index (xs :: [k]) (x :: k) where
IZ :: Index (x ': xs) x
IS :: Index xs i -> Index (x ': xs) i
我们可以使用索引从Tuple 中提取项目:
(!) :: Tuple xs a -> Index xs x -> a x
(!) (Cons x _) IZ = x
(!) (Cons _ xs) (IS i) = xs ! i
我们应该能够将Conkin.Traversable 的任何实例转储到Tuple,留下一个索引来代替每个元素。那么从索引和元组的结构我们可以重构出原来的Traversable结构:
data TupleDump t a = forall xs. TupleDump (t (Index xs)) (Tuple xs a)
toTupleDump :: forall (t :: (k -> *) -> *) (a :: k -> *). Conkin.Traversable t
=> t a -> TupleDump t a
fromTupleDump :: Conkin.Functor t => TupleDump t a -> t a
重构部分很简单:
fromTupleDump (TupleDump inds vals) = Conkin.fmap (vals !) inds
这个问题具体是如何实现toTupleDump。以下是我迄今为止的最佳尝试:
它涉及到很多辅助函数和一个unsafeCoerce
存在量化的函子:
data Some (a :: k -> *) = forall (x :: k). Some (a x)
给定一个Int,构造一些Index:
mkIndex :: Tuple xs a -> Int -> Some (Index xs)
mkIndex Nil _ = error "Index out of bounds"
mkIndex _ n | n < 0 = error "Index out of bounds"
mkIndex (Cons _ _) 0 = Some IZ
mkIndex (Cons _ xs) n = case mkIndex xs (n - 1) of Some i -> Some $ IS i
给定一个存在量化的函子列表,将它们分组为(翻转的)Tuple:
fromList :: [Some a] -> Some (Flip Tuple a)
fromList [] = Some $ Flip Nil
fromList (Some x : xs) = case fromList xs of
Some (Flip t) -> Some (Flip (Cons x t))
在Prelude.Applicative(而不是Conkin.Applicative)内遍历
traverseInPrelude :: (Prelude.Applicative f, Conkin.Traversable t)
=> (forall x. a x -> f (b x)) -> t a -> f (t b)
traverseInPrelude fn t =
Conkin.fmap (unComposeConst . getFlip) . getCompose <$>
getDispose (Conkin.traverse (Dispose . fmap ComposeConst . fn) t)
newtype ComposeConst a b c = ComposeConst {unComposeConst :: a b}
现在我们可以定义toTupleDump:
toTupleDump t =
我们首先将索引作为Int 进行跟踪,并将我们的元素转储到普通列表中。
由于我们使用(:) 构建列表,所以它会倒退。
let
nextItem :: forall (x :: k). a x -> State (Int, [Some a]) (Const Int x)
nextItem x = do
(i, xs') <- State.get
State.put (i + 1, Some x : xs')
return $ Const i
(res, (_, xs)) = runState (traverseInPrelude nextItem t) (0, [])
in
现在我们反转列表并将其转换为Tuple:
case fromList (reverse xs) of
Some (Flip (tup :: Tuple xs a)) ->
我们需要在 res 结构上使用 fmap 将所有 Ints 更改为 Indexes
let
indexedRes = Conkin.fmap (coerceIndex . mkIndex tup . getConst) res
这是unsafeCoerce。由于这种方法涉及对结构的两次遍历,因此我们必须让类型检查器知道在第二次遍历时,类型参数与在第一次遍历时相同。
coerceIndex :: forall x. Some (Index xs) -> Index xs x
coerceIndex (Some i) = unsafeCoerce i
in
TupleDump indexedRes tup
【问题讨论】:
-
有什么阻止您在代码中使用类型级别的
Nats 吗?我不明白,例如nextItem完全依赖于一些运行时值。 -
类型级别的
Nats 可能在哪里有用? -
能够在类型级别定义索引,因此您不必强制它们,因为类型级别索引总是会在
Foldable上产生相同的类型 - 尽管这可能需要Foldable长度的类型级概念,用于实现res的遍历和构造。这更多的是推测而不是具体的设计。 -
对不起。这对我来说是不够的。
标签: haskell traversal existential-type data-kinds