Tạo một kết nối hoàn toàn phụ thuộc


10

Một sự thật đúng đắn về phép nối là nếu tôi biết bất kỳ hai biến nào trong phương trình:

a ++ b = c

Sau đó tôi biết thứ ba.

Tôi muốn nắm bắt ý tưởng này trong concat của riêng tôi để tôi sử dụng một phụ thuộc chức năng.

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

Bây giờ tôi gợi ý danh sách không đồng nhất như vậy:

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

Nhưng khi tôi cố gắng khai báo những điều này vì Concatabletôi có một vấn đề

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)

Tôi không đáp ứng sự phụ thuộc chức năng thứ ba của tôi. Hay đúng hơn là trình biên dịch tin rằng chúng tôi không. Điều này là do trình biên dịch tin rằng trong trường hợp thứ hai của chúng tôi, nó có thể là trường hợp đó bs ~ (a ': cs). Và nó có thể là trường hợp nếu Concatable as (a ': cs) cs.

Làm cách nào tôi có thể điều chỉnh các phiên bản của mình để cả ba phụ thuộc đều hài lòng?


1
Vấn đề chính dường như là bs cs -> asbởi vì chúng ta cần thông tin không phải địa phương về bscsquyết định xem asnên là khuyết điểm hay không. Chúng ta cần tìm hiểu làm thế nào để thể hiện thông tin này; bối cảnh nào chúng ta sẽ thêm vào một chữ ký loại để đảm bảo nó khi nó không thể được suy luận trực tiếp?
luqui

3
Để mở rộng những gì luqui đã nói: hãy tưởng tượng chúng tôi biết bscs, và chúng tôi muốn khai thác quỹ, tức là chúng tôi muốn xây dựng lại as. Để làm điều đó theo một cách xác định, chúng tôi hy vọng có thể cam kết với một trường hợp duy nhất và làm theo công thức đó. Cụ thể, giả định bs = (Int ': bs2)cs = (Int ': cs2). Trường hợp nào chúng ta chọn? Có thể là như vậy Inttrong csxuất phát từ bs(và aslà trống). asThay vào đó, cũng có thể xuất phát từ (không trống) và điều đó Intsẽ xuất hiện lại cssau này. Chúng ta cần đào sâu hơn csđể biết và GHC sẽ không làm điều đó.
chi

1
Rất đại khái, GHC sẽ chấp nhận các quỹ có thể được chứng minh bằng cách sử dụng một hình thức cảm ứng đơn giản từ các thể hiện. Ở đây, một trong số chúng đòi hỏi một loại cảm ứng kép phải được chứng minh (hoặc có vẻ như vậy), và trình biên dịch sẽ không đi xa đến thế.
chi

Câu trả lời:


10

Vì vậy, như các ý kiến ​​cho thấy, GHC sẽ không tự tìm ra nó, nhưng chúng tôi có thể giúp nó với một chút về lập trình cấp độ loại. Hãy giới thiệu một số TypeFamilies. Tất cả các chức năng này là các bản dịch khá đơn giản về thao tác danh sách được nâng lên mức loại:

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

Với công cụ này theo ý của chúng tôi, chúng tôi thực sự có thể đạt được mục tiêu hàng giờ, nhưng trước tiên hãy xác định một chức năng với các thuộc tính mong muốn:

  • Khả năng suy luận cstừ asbs
  • Khả năng suy luận astừ bscs
  • Khả năng suy luận bstừ ascs

Voila:

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

Hãy kiểm tra nó:

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]

Và cuối cùng là mục tiêu cuối cùng:

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
Làm tốt! Tôi thậm chí nghi ngờ điều này có thể là không thể nhưng bạn đã giải quyết nó một cách minh bạch và thanh lịch.
luqui

Cảm ơn bạn, @luqui
lehins
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.