创建一个完全依赖的串联


10

关于串联的一个很好的真实事实是,如果我知道方程式中的任何两个变量:

a ++ b = c

那我知道第三个。

我想在我自己的concat中抓住这个想法,所以我使用了功能依赖。

{-# Language DataKinds, GADTs, FlexibleContexts, FlexibleInstances, FunctionalDependencies, KindSignatures, PolyKinds, TypeOperators, UndecidableInstances #-}
import Data.Kind (Type)

class Concatable
   (m  :: k -> Type)
   (as :: k)
   (bs :: k)
   (cs :: k)
   | as bs -> cs
   , as cs -> bs
   , bs cs -> as
   where
     concat' :: m as -> m bs -> m cs

现在,我想到了类似的异类列表:

data HList ( as :: [ Type ] ) where
  HEmpty :: HList '[]
  HCons  :: a -> HList as -> HList (a ': as)

但是当我尝试声明这些时,Concatable我遇到了问题

instance Concatable HList '[] bs bs where
  concat' HEmpty bs = bs
instance
  ( Concatable HList as bs cs
  )
    => Concatable HList (a ': as) bs (a ': cs)
  where
    concat' (HCons head tail) bs = HCons head (concat' tail bs)

我不满足我的第三个功能依赖性。或者更确切地说,编译器认为我们不这样做。这是因为编译器认为,在第二种情况下,可能是这样bs ~ (a ': cs)。如果是的话Concatable as (a ': cs) cs

如何调整实例,以便满足所有三个依赖关系?


1
关键问题似乎是bs cs -> as,因为我们需要有关非本地信息,bscs确定as该值是弊还是零。我们需要找出如何表示这些信息;当无法直接推导类型签名时,我们将添加什么上下文来保证它呢?
luqui

3
扩展luqui所说的话:假设我们知道bscs,并且我们想利用Fundep,即我们要重构as。为了以确定性的方式进行操作,我们希望能够提交到单个实例并遵循该配方。具体而言,假设bs = (Int ': bs2)cs = (Int ': cs2)。我们选择哪个实例?此类Int输入可能cs来自bs(并且as为空)。也有可能来自(非空)as,而稍后Int将再次出现cs。我们需要更深入cs地了解,GHC不会这样做。

1
粗略地说,GHC会接受脚底肌,这可以通过实例中的简单诱导来证明。在这里,其中一个需要一种双重归纳的证明(或者看起来),并且编译器不会走那么远。

Answers:


10

因此,正如评论所暗示的那样,GHC不会自己解决它,但是我们可以通过一些类型级别的编程来帮助它。让我们介绍一些TypeFamilies。所有这些功能都是将列表操作提升到类型级别的非常简单的转换:

-- | This will produce the suffix of `cs` without `as`
type family DropPrefix (as :: [Type]) (cs :: [Type]) where
  DropPrefix '[] cs = cs
  DropPrefix (a ': as) (a ': cs) = DropPrefix as cs

-- Similar to the logic in the question itself: list concatenation. 
type family Concat (as :: [Type]) (bs :: [Type]) where
  Concat '[] bs = bs
  Concat (head ': tail) bs = head ': Concat tail bs

-- | Naive list reversal with help of concatenation.
type family Reverse (xs :: [Type]) where
  Reverse '[] = '[]
  Reverse (x ': xs) = Concat (Reverse xs) '[x]

-- | This will produce the prefix of `cs` without `bs`
type family DropSuffix (bs :: [Type]) (cs :: [Type]) where
  DropSuffix bs cs = Reverse (DropPrefix (Reverse bs) (Reverse cs))

-- | Same definition of `HList` as in the question
data HList (as :: [Type]) where
  HEmpty :: HList '[]
  HCons :: a -> HList as -> HList (a ': as)

-- | Definition of concatenation at the value level
concatHList :: (cs ~ Concat as bs) => HList as -> HList bs -> HList cs
concatHList HEmpty bs = bs
concatHList (HCons head tail) bs = HCons head (concatHList tail bs)

使用此工具,我们实际上可以达到小时目标,但首先让我们定义一个具有所需属性的函数:

  • csas和推论的能力bs
  • asbs和推论的能力cs
  • bsas和推论的能力cs

瞧:

concatH ::
     (cs ~ Concat as bs, bs ~ DropPrefix as cs, as ~ DropSuffix bs cs)
  => HList as
  -> HList bs
  -> HList cs
concatH = concatHList

让我们测试一下:

foo :: HList '[Char, Bool]
foo = HCons 'a' (HCons True HEmpty)

bar :: HList '[Int]
bar = HCons (1 :: Int) HEmpty
λ> :t concatH foo bar
concatH foo bar :: HList '[Char, Bool, Int]
λ> :t concatH bar foo
concatH bar foo :: HList '[Int, Char, Bool]

最后是最终目标:

class Concatable (m :: k -> Type) (as :: k) (bs :: k) (cs :: k)
  | as bs -> cs
  , as cs -> bs
  , bs cs -> as
  where
  concat' :: m as -> m bs -> m cs

instance (cs ~ Concat as bs, bs ~ DropPrefix as cs, as ~ DropSuffix bs cs) =>
         Concatable HList as bs cs where
  concat' = concatH
λ> :t concat' HEmpty bar
concat' HEmpty bar :: HList '[Int]
λ> :t concat' foo bar
concat' foo bar :: HList '[Char, Bool, Int]
λ> :t concat' bar foo
concat' bar foo :: HList '[Int, Char, Bool]

3
做得好!我什至怀疑这可能是不可能的,但是您可以透明而优雅地解决它。
luqui

谢谢@luqui
lehins
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.