函数从字节流中读取ID。它知道id的大小 - 可以是4或8个字节。如何使返回类型多态?
(伪代码:)
class (Integral a) => IdSize a where
size :: a -> Int
instance IdSize Int32 ...
instance IdSize Int64 ...
data Data = Data (Map (IdSize a) String)
readData :: Data (Map (IdSize a) String)
readId :: (forall a. IdSize a) => a -- kind of this, but not right
此readId需要IdSize来自调用者的实例,但调用者不知道大小。类似地,readData返回的Map需要是多态的,但调用者不知道实际的类型。使用Map的函数将知道该类型。
答案 0 :(得分:5)
如果它只有两种类型,那么它就不是“多态的”。这只是两种类型的不相交联合,或者是“和”类型。
import Data.Int
data Idx = I32 Int32
| I64 Int64
deriving (Show)
readId 4 _ = I32 0x12345678
readId _ _ = I64 0x1234567812345678
idSize (I32 _) = 4
idSize _ = 8
main :: IO ()
main = do
let input = () -- this would be your input stream
let idx1 = readId 4 input
let idx2 = readId 8 input
putStrLn $ "idx 1: size " ++ (show $ idSize idx1) ++ " value: " ++ (show idx1)
putStrLn $ "idx 2: size " ++ (show $ idSize idx2) ++ " value: " ++ (show idx2)
return ()
当您确实需要更灵活的数据类型的类型签名时,例如当您构建一个想要约束为类型良好的构造的抽象语法树时,GADT是一种很好的方法:{{3} }
以下是GADT的示例:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
import Data.Int
data Idx a where
I32 :: Int32 -> Idx Int32
I64 :: Int64 -> Idx Int64
deriving instance Show (Idx a)
readId32 :: t -> Idx Int32
readId32 _ = I32 0x12345678
readId64 :: t -> Idx Int64
readId64 _ = I64 0x1234567812345678
idSize :: Num a => Idx t -> a
idSize (I32 _) = 4
idSize _ = 8
main :: IO ()
main = do
let idx1 = readId32 ()
let idx2 = readId64 ()
putStrLn $ "idx 1: size " ++ (show $ idSize idx1) ++ " value: " ++ (show idx1)
putStrLn $ "idx 2: size " ++ (show $ idSize idx2) ++ " value: " ++ (show idx2)
return ()
我不确定这是否与你所追求的完全相同,但它确实让你专注于这种类型,这样你就不能将Idx Int32
与Idx Int64
混合,但你仍然可以写多态Idx a
函数,如idSize
。
答案 1 :(得分:1)
啊,好的,我们可以使用额外的包装类型解决其中的一些问题:
{-# LANGUAGE RankNTypes, ConstraintKinds, ExistentialQuantification #-}
import Data.Int
data Idx = forall a. IdSize a => Idx a
instance Show Idx where
show (Idx a) = show a
class (Integral a, Show a) => IdSize a where
size :: a -> Int
instance IdSize Int32 where
size _ = 4
instance IdSize Int64 where
size _ = 8
readId :: Int -> Idx
readId 4 = Idx (4 :: Int32)
readId _ = Idx (8 :: Int64)
main = print $ readId 8
然后,数据可能会保存一个Map Id字符串。
答案 2 :(得分:1)
以下工作符合我的要求:
{-# LANGUAGE RankNTypes, ExistentialQuantification #-}
import Data.Int
import Data.Typeable
import qualified Data.Map as M
data H = forall a. IdSize a => H a (M.Map a String)
class (Integral a, Show a, Typeable a) => IdSize a where
size :: a -> Int
readId :: a
instance IdSize Int32 where
size _ = 4
readId = 4
instance IdSize Int64 where
size _ = 8
readId = 8
use :: (forall a. IdSize a => a -> M.Map a String -> b) -> H -> b
use f (H i m) = f i m
idSize :: H -> Int
idSize (H i _) = size i
mkH :: Int -> H
mkH 4 = H (4 :: Int32) (M.singleton (4 :: Int32) "int32")
mkH _ = H (8 :: Int64) (M.singleton (8 :: Int64) "int64")
main = print $ use (M.lookup . const readId) $ mkH 4
mkH可用于构造H,它对调用者是不透明的。然后调用者可以传递一个要使用的函数,它将解构H并调用给定的函数。该函数必须是RankN多态的 - 它应该适用于任何IdSize实例。这是隐藏IdSize实现的设计意图。