Bagaimana saya bisa memberitahu GHC untuk memenuhi batasan tipe-level <= jika saya tahu itu benar saat runtime?
Saya memiliki tipe parametrized dengan bilangan asli n
:
data MyType (n :: Nat) = MyType
Operasi pada tipe ini hanya masuk akal jika n > 0
, jadi saya menetapkannya sebagai batasan:
myFunction :: (KnownNat n, 1 <= n) => MyType n -> MyType n
myFunction = id
(Perhatikan bahwa versi sebenarnya dari fungsi ini memang digunakan n
dengan mengubahnya menjadi nilai dengan natVal
.)
Saya ingin membuat tipe eksistensial ( SomeMyType
) yang memungkinkan kita memilih n
saat runtime:
data SomeMyType where
SomeMyType :: forall n. (KnownNat n, 1 <= n) => MyType n -> SomeMyType
Untuk menghasilkan nilai SomeMyType
, saya menulis someMyTypeVal
fungsi yang berfungsi seperti someNatVal
:
someMyTypeVal :: Integer -> Maybe SomeMyType
someMyTypeVal n
| n > 0 = case someNatVal n of
Just (SomeNat (_ :: p n)) -> Just (SomeMyType (MyType @n))
Nothing -> Nothing
| otherwise = Nothing
Ini akan bekerja dengan baik tanpa 1 <= n
kendala, tetapi dengan kendala saya mendapatkan jenis kesalahan berikut:
• Couldn't match type ‘1 <=? n’ with ‘'True’
arising from a use of ‘SomeMyType’
Bagaimana cara mengatasi batasan ini? Karena saya telah memeriksanya n > 0
pada waktu proses, saya tidak keberatan menggunakan operasi seperti di unsafeCoerce
sini untuk meyakinkan GHC bahwa 1 <= n
itu benar — tetapi saya tidak bisa menggunakannya begitu sajaunsafeCoerce
karena itu akan kehilangan nilai level-tipe n
.
Apa cara terbaik untuk mengatasi ini?
Jawaban
Setelah mencari-cari lebih banyak, saya menemukan jawaban Justin L. untuk pertanyaan serupa . Dia membungkus solusinya ke dalam typelits-witnessespaket yang dapat saya gunakan untuk menyelesaikan masalah ini dengan cukup bersih:
someMyTypeVal :: Integer -> Maybe SomeMyType
someMyTypeVal n = someNatVal n >>= check
where check :: SomeNat -> Maybe SomeMyType
check (SomeNat (_ :: p n)) = case (SNat @1) %<=? (SNat @n) of
LE Refl -> Just (SomeMyType (MyType @n))
NLE _ _ -> Nothing
a %<=? b
mari kita bandingkan dua bilangan asli tingkat tipe dan memberi kita kesaksian apakah a
kurang dari b
( LE
) atau tidak ( NLE
). Ini memberi kita batasan ekstra LE
untuk mengembalikan a SomeMyType
, tetapi mencoba melakukan itu dalam NLE
kasus masih akan memberi kita kesalahan "tidak bisa mencocokkan '1 <=? N' dengan '' Benar '".
Perhatikan tanda tangan tipe eksplisit untuk check
—tanpa itu, tipe check
disimpulkan sebagai check :: SomeNat -> Maybe a
dan saya akan mendapatkan kesalahan tipe berikut:
• Couldn't match type ‘a’ with ‘SomeMyType’
‘a’ is untouchable
inside the constraints: 'True ~ (1 <=? n)
Dengan tanda tangan tipe eksplisit semuanya bekerja dan kodenya bahkan (cukup) dapat dibaca.
Sebagai jawaban yang lebih langsung, batasan 1 <= n
hanyalah alias tipe untuk 1 <=? n ~ 'True
. Anda dapat membuat batasan kesetaraan jenis dengan tidak aman dengan:
{-# LANGUAGE DataKinds, TypeOperators #-}
import Data.Type.Equality
import GHC.TypeLits
import Unsafe.Coerce
unsafeDeclarePositive :: p n -> (1 <=? n) :~: 'True
unsafeDeclarePositive _ = unsafeCoerce Refl
yang kurang lebih apa typelits-witnesses
yang dilakukan di bawah tenda.
Dengan definisi itu di tempat, yang berikut harus mengetik-periksa:
someMyTypeVal :: Integer -> Maybe SomeMyType
someMyTypeVal n
| n > 0 = case someNatVal n of
Just (SomeNat (pxy :: p n)) ->
case unsafeDeclarePositive pxy of
Refl -> Just (SomeMyType (MyType @n))
Nothing -> Nothing
| otherwise = Nothing