Как мне построить список с зависимой типизированной длиной?

Окунув носок в воды зависимых типов, я обнаружил трещину в каноническом примере со списком статически типизированной длины.

{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}

-- a kind declaration
data Nat = Z | S Nat

data SafeList :: (Nat -> * -> *) where
    Nil :: SafeList Z a
    Cons :: a -> SafeList n a -> SafeList (S n) a

-- the type signature ensures that the input list has at least one element
safeHead :: SafeList (S n) a -> a
safeHead (Cons x xs) = x

Это похоже на работу:

ghci> :t Cons 5 (Cons 3 Nil)
Cons 5 (Cons 3 Nil) :: Num a => SafeList ('S ('S 'Z)) a

ghci> safeHead (Cons 'x' (Cons 'c' Nil))
'x'

ghci> safeHead Nil
Couldn't match type 'Z with 'S n0
Expected type: SafeList ('S n0) a0
  Actual type: SafeList 'Z a0
In the first argument of `safeHead', namely `Nil'
In the expression: safeHead Nil
In an equation for `it': it = safeHead Nil

Однако для того, чтобы этот тип данных был действительно полезным, я должен иметь возможность построить его из данных времени выполнения, для которых вы не знаете длину во время компиляции. Моя наивная попытка:

fromList :: [a] -> SafeList n a
fromList = foldr Cons Nil

Это не скомпилируется, с ошибкой типа:

Couldn't match type 'Z with 'S n
Expected type: a -> SafeList n a -> SafeList n a
  Actual type: a -> SafeList n a -> SafeList ('S n) a
In the first argument of `foldr', namely `Cons'
In the expression: foldr Cons Nil
In an equation for `fromList': fromList = foldr Cons Nil

Я понимаю, почему это происходит: тип возвратаCons отличается для каждой итерации сгиба - вот и весь смысл! Но я не могу найти способ обойти это, вероятно, потому что я недостаточно глубоко вник в предмет. (Я не могу себе представить, что все эти усилия вкладываются в систему типов, которую невозможно использовать на практике!)

Итак: Как я могу построить этот тип зависимых данных из «обычных» данных простого типа?

Следуя совету @ Luqui, я смог сделатьfromList компиляции:

data ASafeList a where
    ASafeList :: SafeList n a -> ASafeList a

fromList :: [a] -> ASafeList a
fromList = foldr f (ASafeList Nil)
    where f x (ASafeList xs) = ASafeList (Cons x xs)

Вот моя попытка распаковатьASafeList и использовать это:

getSafeHead :: [a] -> a
getSafeHead xs = case fromList xs of ASafeList ys -> safeHead ys

Это вызывает другую ошибку типа:

Couldn't match type `n' with 'S n0
  `n' is a rigid type variable bound by
      a pattern with constructor
        ASafeList :: forall a (n :: Nat). SafeList n a -> ASafeList a,
      in a case alternative
      at SafeList.hs:33:22
Expected type: SafeList ('S n0) a
  Actual type: SafeList n a
In the first argument of `safeHead', namely `ys'
In the expression: safeHead ys
In a case alternative: ASafeList ys -> safeHead ys

Опять же, интуитивно понятно, что это не скомпилируется. я могу позвонитьfromList с пустым списком, поэтому у компилятора нет гарантии, что я смогу вызватьsafeHead в результатеSafeList, Этот недостаток знаний примерно то, что экзистенциальныйASafeList захватывает.

Можно ли решить эту проблему? Я чувствую, что, возможно, ушел в логический тупик.

Ответы на вопрос(3)

Ваш ответ на вопрос