Haskell:具有幻像变量的数据的异构列表
Haskell: Heterogeneous list for data with phantom variable
我目前正在学习存在量化、幻像类型和 GADT。如何着手创建具有幻像变量的数据类型的异构列表?例如:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ExistentialQuantification #-}
data Toy a where
TBool :: Bool -> Toy Bool
TInt :: Int -> Toy Int
instance Show (Toy a) where
show (TBool b) = "TBool " ++ show b
show (TInt i) = "TInt " ++ show i
bools :: [Toy Bool]
bools = [TBool False, TBool True]
ints :: [Toy Int]
ints = map TInt [0..9]
有如下功能即可:
isBool :: Toy a -> Bool
isBool (TBool _) = True
isBool (TInt _) = False
addOne :: Toy Int -> Toy Int
addOne (TInt a) = TInt $ a + 1
但是,我希望能够像这样声明一个异构列表:
zeros :: [Toy a]
zeros = [TBool False, TInt 0]
我尝试使用空类型 class 通过以下方式限制 a
上的类型:
class Unify a
instance Unify Bool
instance Unify Int
zeros :: Unify a => [Toy a]
zeros = [TBool False, TInt 0]
但是上面的代码会编译失败。我能够使用存在量化来获得以下内容:
data T = forall a. (Forget a, Show a) => T a
instance Show T where
show (T a) = show a
class (Show a) => Forget a
instance Forget (Toy a)
instance Forget T
zeros :: [T]
zeros = [T (TBool False), T (TInt 0)]
但是这样一来,我就无法将基于 Toy a
中 a
的特定类型的函数应用到 T
,例如addOne
以上。
总而言之,有哪些方法可以在没有 forgetting/losing 虚拟变量的情况下创建异构列表?
由其元素类型列表索引的普通异构列表是
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
data HList l where
HNil :: HList '[]
HCons :: a -> HList l -> HList (a ': l)
我们可以修改它以在某些 f :: * -> *
.
中保存值
data HList1 f l where
HNil1 :: HList1 f '[]
HCons1 :: f a -> HList1 f l -> HList1 f (a ': l)
您可以使用它来编写 zeros
而不会忘记类型变量。
zeros :: HList1 Toy [Bool, Int]
zeros = HCons1 (TBool False) $ HCons1 (TInt 0) $ HNil1
从 Toy
类型开始:
data Toy a where
TBool :: Bool -> Toy Bool
TInt :: Int -> Toy Int
现在您可以将其包装在一个存在的 中,而不会 过度概括 class 系统:
data WrappedToy where
Wrap :: Toy a -> WrappedToy
因为包装器只包含 Toy
s,我们可以 unwrap 它们并取回 Toy
s:
incIfInt :: WrappedToy -> WrappedToy
incIfInt (Wrap (TInt n)) = Wrap (TInt (n+1))
incIfInt w = w
现在您可以区分列表中的内容了:
incIntToys :: [WrappedToy] -> [WrappedToy]
incIntToys = map incIfInt
编辑
正如 Cirdec 指出的那样,不同的部分可以分开梳理一下:
onInt :: (Toy Int -> WrappedToy) -> WrappedToy -> WrappedToy
onInt f (Wrap t@(TInt _)) = f t
onInt _ w = w
mapInt :: (Int -> Int) -> Toy Int -> Toy Int
mapInt f (TInt x) = TInt (f x)
incIntToys :: [WrappedToy] -> [WrappedToy]
incIntToys = map $ onInt (Wrap . mapInt (+1))
我还应该指出,到目前为止这里没有任何东西可以真正证明 Toy
GADT 的合理性。 bheklilr 使用普通代数数据类型的更简单方法应该可以正常工作。
几天前有一个非常相似
你的情况是
{-# LANGUAGE GADTs, PolyKinds, Rank2Types #-}
data Exists :: (k -> *) -> * where
This :: p x -> Exists p
type Toys = [Exists Toy]
zeros :: Toys
zeros = [This (TBool False), This (TInt 0)]
消除存在主义很容易:
recEx :: (forall x. p x -> c) -> Exists p -> c
recEx f (This x) = f x
那么如果你有 Toy
数据类型的递归
recToy :: (Toy Bool -> c) -> (Toy Int -> c) -> Toy a -> c
recToy f g x@(TBool _) = f x
recToy f g x@(TInt _) = g x
你可以映射一个包裹 toy
:
mapToyEx :: (Toy Bool -> p x) -> (Toy Int -> p y) -> Exists Toy -> Exists p
mapToyEx f g = recEx (recToy (This . f) (This . g))
例如
non_zeros :: Toys
non_zeros = map (mapToyEx (const (TBool True)) addOne) zeros
这种方法类似于@dfeuer 的回答中的一种方法,但它不是特别的。
你玩过Data.Typeable
吗? Typeable
约束允许您猜测存在性隐藏的类型,并在猜对时转换为该类型。
不是你的例子,而是我身边的一些示例代码:
{-# LANGUAGE GADTs, ScopedTypeVariables, TypeOperators #-}
import Data.Typeable
data Showable where
-- Note that this is an existential defined in GADT form
Showable :: (Typeable a, Show a) => a -> Showable
instance Show Showable where
show (Showable value) = "Showable " ++ show value
-- Example of casting Showable to Integer
castToInteger :: Showable -> Maybe Integer
castToInteger (Showable (value :: a)) =
case eqT :: Maybe (a :~: Integer) of
Just Refl -> Just value
Nothing -> Nothing
example1 = [Showable "foo", Showable 5]
example2 = map castToInteger example1
我目前正在学习存在量化、幻像类型和 GADT。如何着手创建具有幻像变量的数据类型的异构列表?例如:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ExistentialQuantification #-}
data Toy a where
TBool :: Bool -> Toy Bool
TInt :: Int -> Toy Int
instance Show (Toy a) where
show (TBool b) = "TBool " ++ show b
show (TInt i) = "TInt " ++ show i
bools :: [Toy Bool]
bools = [TBool False, TBool True]
ints :: [Toy Int]
ints = map TInt [0..9]
有如下功能即可:
isBool :: Toy a -> Bool
isBool (TBool _) = True
isBool (TInt _) = False
addOne :: Toy Int -> Toy Int
addOne (TInt a) = TInt $ a + 1
但是,我希望能够像这样声明一个异构列表:
zeros :: [Toy a]
zeros = [TBool False, TInt 0]
我尝试使用空类型 class 通过以下方式限制 a
上的类型:
class Unify a
instance Unify Bool
instance Unify Int
zeros :: Unify a => [Toy a]
zeros = [TBool False, TInt 0]
但是上面的代码会编译失败。我能够使用存在量化来获得以下内容:
data T = forall a. (Forget a, Show a) => T a
instance Show T where
show (T a) = show a
class (Show a) => Forget a
instance Forget (Toy a)
instance Forget T
zeros :: [T]
zeros = [T (TBool False), T (TInt 0)]
但是这样一来,我就无法将基于 Toy a
中 a
的特定类型的函数应用到 T
,例如addOne
以上。
总而言之,有哪些方法可以在没有 forgetting/losing 虚拟变量的情况下创建异构列表?
由其元素类型列表索引的普通异构列表是
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
data HList l where
HNil :: HList '[]
HCons :: a -> HList l -> HList (a ': l)
我们可以修改它以在某些 f :: * -> *
.
data HList1 f l where
HNil1 :: HList1 f '[]
HCons1 :: f a -> HList1 f l -> HList1 f (a ': l)
您可以使用它来编写 zeros
而不会忘记类型变量。
zeros :: HList1 Toy [Bool, Int]
zeros = HCons1 (TBool False) $ HCons1 (TInt 0) $ HNil1
从 Toy
类型开始:
data Toy a where
TBool :: Bool -> Toy Bool
TInt :: Int -> Toy Int
现在您可以将其包装在一个存在的 中,而不会 过度概括 class 系统:
data WrappedToy where
Wrap :: Toy a -> WrappedToy
因为包装器只包含 Toy
s,我们可以 unwrap 它们并取回 Toy
s:
incIfInt :: WrappedToy -> WrappedToy
incIfInt (Wrap (TInt n)) = Wrap (TInt (n+1))
incIfInt w = w
现在您可以区分列表中的内容了:
incIntToys :: [WrappedToy] -> [WrappedToy]
incIntToys = map incIfInt
编辑
正如 Cirdec 指出的那样,不同的部分可以分开梳理一下:
onInt :: (Toy Int -> WrappedToy) -> WrappedToy -> WrappedToy
onInt f (Wrap t@(TInt _)) = f t
onInt _ w = w
mapInt :: (Int -> Int) -> Toy Int -> Toy Int
mapInt f (TInt x) = TInt (f x)
incIntToys :: [WrappedToy] -> [WrappedToy]
incIntToys = map $ onInt (Wrap . mapInt (+1))
我还应该指出,到目前为止这里没有任何东西可以真正证明 Toy
GADT 的合理性。 bheklilr 使用普通代数数据类型的更简单方法应该可以正常工作。
几天前有一个非常相似
你的情况是
{-# LANGUAGE GADTs, PolyKinds, Rank2Types #-}
data Exists :: (k -> *) -> * where
This :: p x -> Exists p
type Toys = [Exists Toy]
zeros :: Toys
zeros = [This (TBool False), This (TInt 0)]
消除存在主义很容易:
recEx :: (forall x. p x -> c) -> Exists p -> c
recEx f (This x) = f x
那么如果你有 Toy
数据类型的递归
recToy :: (Toy Bool -> c) -> (Toy Int -> c) -> Toy a -> c
recToy f g x@(TBool _) = f x
recToy f g x@(TInt _) = g x
你可以映射一个包裹 toy
:
mapToyEx :: (Toy Bool -> p x) -> (Toy Int -> p y) -> Exists Toy -> Exists p
mapToyEx f g = recEx (recToy (This . f) (This . g))
例如
non_zeros :: Toys
non_zeros = map (mapToyEx (const (TBool True)) addOne) zeros
这种方法类似于@dfeuer 的回答中的一种方法,但它不是特别的。
你玩过Data.Typeable
吗? Typeable
约束允许您猜测存在性隐藏的类型,并在猜对时转换为该类型。
不是你的例子,而是我身边的一些示例代码:
{-# LANGUAGE GADTs, ScopedTypeVariables, TypeOperators #-}
import Data.Typeable
data Showable where
-- Note that this is an existential defined in GADT form
Showable :: (Typeable a, Show a) => a -> Showable
instance Show Showable where
show (Showable value) = "Showable " ++ show value
-- Example of casting Showable to Integer
castToInteger :: Showable -> Maybe Integer
castToInteger (Showable (value :: a)) =
case eqT :: Maybe (a :~: Integer) of
Just Refl -> Just value
Nothing -> Nothing
example1 = [Showable "foo", Showable 5]
example2 = map castToInteger example1