Wie erstelle ich eine Liste mit einer abhängig eingegebenen Länge?

ch tauchte meinen Zeh in die Gewässer abhängiger Typen und hatte einen Riss in der kanonischen "Liste mit statisch typisierter Länge"

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

Dies scheint zu funktionieren:

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

Damit dieser Datentyp jedoch wirklich nützlich ist, sollte er aus Laufzeitdaten erstellt werden können, deren Länge Sie zur Kompilierungszeit nicht kennen. Mein naiver Versuch:

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

Dies kann nicht kompiliert werden, mit dem Typ Fehler:

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

Ich verstehe, warum das passiert: der Rückgabetyp vonCons ist für jede Iteration der Falte anders - das ist der springende Punkt! Aber ich sehe keinen Weg, wahrscheinlich, weil ich nicht tief genug in das Thema hineingelesen habe. (Ich kann mir nicht vorstellen, dass all dieser Aufwand in ein Typensystem gesteckt wird, das in der Praxis nicht anwendbar ist!)

So: Wie kann ich diese Art von abhängig typisierten Daten aus 'normalen' einfach typisierten Daten erstellen?

Folgen @ luquis Rat, den ich machen konntefromList compile:

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)

Hier ist mein Versuch, das @ auszupackASafeList und benutze es:

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

Dies verursacht einen anderen Typfehler:

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

Again, intuitiv macht es Sinn, dass dies nicht zu kompilieren wäre. Ich kann anrufenfromList mit einer leeren Liste, daher kann der Compiler nicht garantieren, dass ich @ anrufen kansafeHead auf dem resultierendenSafeList. Dieser Mangel an Wissen ist ungefähr das, was das existenzielleASafeList erfasst.

Kann dieses Problem gelöst werden? Ich habe das Gefühl, ich wäre in eine logische Sackgasse geraten.

Antworten auf die Frage(6)

Ihre Antwort auf die Frage