简体   繁体   中英

Polymorphic return type

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM