Menciptakan gabungan yang sepenuhnya tergantung

10

Fakta benar yang bagus tentang penggabungan adalah bahwa jika saya tahu ada dua variabel dalam persamaan:

a ++ b = c

Lalu aku tahu yang ketiga.

Saya ingin menangkap ide ini di konser saya sendiri jadi saya menggunakan dependensi fungsional.

{-# 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

Sekarang saya membuat daftar heterogen seperti:

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

Tetapi ketika saya mencoba untuk menyatakan ini karena Concatablesaya memiliki masalah

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)

Saya tidak memenuhi ketergantungan fungsional ketiga saya. Atau lebih tepatnya kompiler percaya kita tidak. Ini karena kompiler percaya bahwa pada instance kedua kita mungkin demikian bs ~ (a ': cs). Dan itu bisa terjadi jika Concatable as (a ': cs) cs.

Bagaimana saya bisa menyesuaikan instance saya sehingga ketiga dependensi puas?

Sriotchilism O'Zaic
sumber
1
Masalah utama tampaknya adalah bs cs -> as, karena kita memerlukan informasi non-lokal tentang bsdan csuntuk memutuskan apakah asharus kontra atau nol. Kita perlu mengetahui bagaimana cara merepresentasikan informasi ini; konteks apa yang akan kita tambahkan ke tanda tangan jenis untuk menjaminnya ketika tidak dapat disimpulkan secara langsung?
luqui
3
Untuk memperluas apa yang dikatakan luqui: bayangkan kita tahu bsdan cs, dan kita ingin mengeksploitasi fundep, yaitu kita ingin merekonstruksi as. Untuk melakukannya dengan cara yang deterministik, kami berharap dapat berkomitmen pada satu instance dan mengikuti resep itu. Konkret, asumsikan bs = (Int ': bs2)dan cs = (Int ': cs2). Contoh mana yang kita pilih? Ada kemungkinan bahwa Intin tersebut csberasal dari bs(dan askosong). Mungkin juga yang berasal dari ( asbukan kosong) sebagai gantinya, dan itu Intakan muncul lagi csnanti. Kita perlu menggali lebih dalam csuntuk mengetahui dan GHC tidak akan melakukan itu.
chi
1
Secara kasar, GHC akan menerima fundeps yang dapat dibuktikan menggunakan bentuk induksi sederhana dari instance. Di sini, salah satunya membutuhkan semacam induksi ganda untuk dibuktikan (atau begitulah tampaknya), dan kompiler tidak akan sejauh itu.
chi

Jawaban:

10

Jadi, seperti komentar yang disarankan, GHC tidak akan mengetahuinya sendiri, tetapi kami dapat membantu dengan sedikit pemrograman tingkat tipe. Mari kita perkenalkan beberapa TypeFamilies. Semua fungsi ini adalah terjemahan yang cukup mudah dari manipulasi daftar yang diangkat ke tingkat tipe:

-- | 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)

Dengan alat ini yang kami miliki, kami benar-benar dapat mencapai sasaran jam, tetapi pertama-tama mari kita mendefinisikan fungsi dengan properti yang diinginkan:

  • Kemampuan untuk menyimpulkan csdari asdanbs
  • Kemampuan untuk menyimpulkan asdari bsdancs
  • Kemampuan untuk menyimpulkan bsdari asdancs

Voila:

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

Mari kita mengujinya:

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]

Dan akhirnya tujuan akhir:

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]
lehins
sumber
3
Sudah selesai dilakukan dengan baik! Saya bahkan curiga ini tidak mungkin tetapi Anda menyelesaikannya secara transparan dan elegan.
luqui
Terima kasih, @luqui
lehins