作为理解singletons
之旅的一部分,我试图弥合编译时安全性与将运行时值提升到相关类型安全性之间的差距。
我认为,“运行时”值的最小示例是一个采用无界列表的函数,并将其转换为大小索引的向量。以下骨架提供长度索引向量,但我无法确定如何编写fromList
。
我已经考虑过让函数采用size参数,但我怀疑它可以保持隐含。
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE UndecidableInstances #-}
import Data.Singletons
import Data.Singletons.TH
$(singletons
[d|
data Nat = Z | S Nat deriving (Show)
|])
data Vec a n where
Nil :: Vec a Z
Cons :: a -> Vec a n -> Vec a (S n)
instance Show a => Show (Vec a n) where
show Nil = "Nil"
show (Cons x xs) = show x ++ " :< " ++ show xs
fromListExplicit :: forall (n :: Nat) a. SNat n -> [a] -> Vec a n
fromListExplicit SZ _ = Nil
fromListExplicit (SS n) (x : xs) = Cons x (fromListExplicit n xs)
ex1 = fromListExplicit (SS (SS (SS SZ))) [1..99]
-- 1 :< 2 :< 3 :< Nil
fromListImplicit :: (?????) => [a] -> Vec a n
fromListImplicit = ?????
main :: IO ()
main = do
xs <- readLn :: IO [Int]
print $ fromListImplicit xs
答案 0 :(得分:5)
使用Haskell可能 ,因为Haskell还没有完全依赖类型(尽管GHC might in the future)。注意
fromList :: [a] -> Vec a n
a
和n
都是普遍量化的,这意味着用户应该能够选择他们的n
并获得正确大小的Vec
。这是没有意义的!诀窍是n
并不是供用户选择 - 它必须是输入列表的长度。 (出于同样的原因,fromList :: Integer -> [a] -> Vec a n
将不再有用 - 大小提示必须是类型级别的。)
期待像Idris这样依赖类型的语言,你可以定义
fromList : (l : List elem) -> Vec (length l) elem
事实上他们是define this in the standard library。
那么,可以你做什么?没有说Vec
的长度等于输入列表的大小(需要将“输入列表的长度”提升到类型级别),你可以说它有一些长度。
data SomeVec a where { SomeVec :: Vec a n -> SomeVec a }
list2SomeVec :: [a] -> SomeVec a
list2SomeVec [] = SomeVec Nil
list2SomeVec (x:xs) = case list2SomeVec xs of
SomeVec ys -> SomeVec (x `Cons` ys)
这并不是非常有用,但总比没有好。
答案 1 :(得分:2)
您可以使用存在量化的类型变量来执行此操作,如@ Alec的答案,或等效地通过重写继续传递样式。诀窍是给fromList
一个延续(函数),它是Vec
大小的多态;然后,在延续中,您可以访问表示大小的类型变量:
data Vec n a where
Nil :: Vec Z a
Cons :: a -> Vec n a -> Vec (S n) a
deriving instance (Show a) => Show (Vec n a)
fromList :: [a] -> (forall n. Vec n a -> r) -> r
fromList [] k = k Nil
fromList (x : xs) k = fromList xs $ \ xs' -> k (Cons x xs')
-- fromList [1, 2, 3] show == "Cons 1 (Cons 2 (Cons 3 Nil))"
您无法知道n
的实际值,因为它在编译时不可用。
如果您将Nat
替换为GHC.TypeLits
中的KnownNat
,我认为您可以通过从n
创建SomeNat
来fromJust (someNatVal (fromIntegral (length xs)))
约束natVal
使用var min = d3.min(data, function(d){return d.value});
var max = d3.max(data, function(d){return d.value});
var absMax = Math.max(Math.abs(min), Math.abs(max));
x.domain([-absMax, absMax]);
运行时长度,然后在运行时使用{{1}}获取实际长度值。我不是很熟悉如何做到这一点,它可能需要ghc-typelits-natnormalise
插件,但它可能是一个起点。