Створення повністю залежної конкатенації


10

Приємний справжній факт конкатенації полягає в тому, що якщо я знаю будь-які дві змінні рівняння:

a ++ b = c

Тоді я знаю третє.

Я хотів би зафіксувати цю ідею у власному стислі, тому я використовую функціональну залежність.

{-# 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тому, що нам потрібна не місцева інформація про bsта csвирішити, чи asмає бути мінус чи нуль. Нам потрібно з'ясувати, як представити цю інформацію; який контекст ми б додали до підпису типу, щоб гарантувати його, коли його неможливо безпосередньо вивести?
luqui

3
Щоб розширити те, що сказав luqui: уявіть, що ми знаємо bsі cs, і ми хочемо використовувати фундамент, тобто ми хочемо реконструювати as. Щоб зробити це детермінованим способом, ми очікуємо, що зможемо взяти на себе окремий екземпляр і дотримуватись цього рецепта. Конкретно, припустимо bs = (Int ': bs2)і cs = (Int ': cs2). Який екземпляр ми обираємо? Можливо, що таке Intв csприходить від bsasпорожнє). Можливо також, що asнатомість походить із (непустого) , а потім Intз’явиться знову cs. Нам потрібно пірнути глибше, csщоб знати, і GHC цього не зробить.
чі

1
Дуже грубо кажучи, GHC прийме фундаменти, які можна довести, використовуючи просту форму індукції від інстанцій. Тут одна з них вимагає довести певну подвійну індукцію (або так здається), і компілятор не піде так далеко.
чі

Відповіді:


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)

За допомогою цих інструментів ми можемо реально дійти до цілі години, але спочатку давайте визначимо функцію з потрібними властивостями:

  • Можливість вивести csз asіbs
  • Можливість вивести asз bsіcs
  • Можливість вивести bsз asі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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.