A function reads ID from a stream of bytes. It knows the size of id - can be 4 or 8 bytes. How to make the return type polymorphic?
(Pseudocode:)
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
This readId would require IdSize a instance from the caller, but the caller doesn't know the size. Similarly, Map returned by readData needs to be polymorphic, but the caller doesn't know the actual type. The type will be known to the functions using the Map.
Something is not "polymorphic" if it can have only two types. That's just a disjoint union of two types, or a "sum" type.
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 ()
When you really do need more flexibility in the type signature of your data type, such as when you are building an abstract syntax tree that you want to constrain to well-typed constructions, GADTs are a nice approach: http://en.wikibooks.org/wiki/Haskell/GADT
Here's the example with GADTs:
{-# 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 ()
I'm not sure if this is quite what you were after, but it does let you specialize the type so that you can't mix Idx Int32
s with Idx Int64
s, but you can still write polymorphic Idx a
functions like idSize
.
Ah, ok, we can solve some of this using additional wrapper type:
{-# 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
Then perhaps Data will hold a Map Id String.
The following worked to meet my requirements:
{-# 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 can be used to construct a H, which is opaque to the caller. The caller then can pass a function to use, which will deconstruct H and invoke the given function. The function must be RankN polymorphic - it should work with any IdSize instance. That's the design intention to hide the implementation of IdSize.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.