Haskell 多态返回类型

Haskell 多态返回类型,haskell,parametric-polymorphism,Haskell,Parametric Polymorphism,函数从字节流中读取ID。它知道id的大小-可以是4或8字节。如何使返回类型多态 (伪代码:) 此readId需要调用方对实例进行IdSize,但调用方不知道其大小。类似地,readData返回的映射需要是多态的,但调用方不知道实际的类型。使用映射的函数将知道该类型。啊,好的,我们可以使用额外的包装器类型解决其中一些问题: {-# LANGUAGE RankNTypes, ConstraintKinds, ExistentialQuantification #-}

函数从字节流中读取ID。它知道id的大小-可以是4或8字节。如何使返回类型多态

(伪代码:)


此readId需要调用方对实例进行IdSize,但调用方不知道其大小。类似地,readData返回的映射需要是多态的,但调用方不知道实际的类型。使用映射的函数将知道该类型。

啊,好的,我们可以使用额外的包装器类型解决其中一些问题:

        {-# 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
然后,数据可能会包含一个映射Id字符串。

如果它只能有两种类型,那么它就不是“多态的”。这只是两种类型的不相交并集,或“和”类型

当您确实需要在数据类型的类型签名方面具有更大的灵活性时,例如,当您正在构建一个抽象语法树以约束到类型良好的构造时,GADT是一种很好的方法:

以下是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
s与
Idx Int64
s混合使用,但您仍然可以编写多态
Idx a
函数,如
idSize
以下功能满足了我的要求:

    {-# 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实现的设计意图。

作为一个稻草人,在你声称需要“多态性”的地方使用
Int32 Int64
怎么样?当然,它是多态的。在Java中,我可以声明一个返回类型对象并返回任何子类。在这里,我想表达一个类似的想法。调用方不关心返回的内容,直到它将返回的值传递给一个能够确定该做什么的函数。存在量化可以让我走上这条道路,但我需要进一步研究和实验,看看下一个问题是什么。:)请注意,这在语义上与从
readId
直接返回大小为
Int
的大小相同,因为这是您在
Idx
上可以执行的唯一操作@is7s当然,我在尝试了所有种类后忘记了它们。@shang这对调用方是正确的-调用方只能做一件事。诀窍在于能够使用Idx特定实例的其他函数中。请参阅我稍后的答案,以了解示例模式。mkH的调用者不能对IdSize a做任何事情-从这个意义上说,调用者在函数mkH的结果类型上是多态的-但是调用者可以构造一个函数,该函数可以使用IdSize a的实例在获取IdSize a的同一个H中访问映射。而且,readId仅在本例中是如此愚蠢;在real app中,对于特定的monad M,它是readId::MA。是的,使用union是解决当前问题的简单方法。我希望找到一个能更好地约束地图的表达式。Map(Int32或Int64)字符串允许混合使用Int32和Int64作为键。最好以这样一种方式对其进行约束,即始终只使用其中一种类型作为键。(这并不是唯一取决于id大小的东西,所以现在我将使用这两种方法,最好将其约束得更紧)我更新了GADT版本,该版本不允许混合使用Idx Int32和Idx Int64。感谢您的建议。看来我还是需要存在主义的量化。有没有一种方法可以像您的案例那样使用Idx来编写mkH?模式是这样的:调用者使用mkH::Int->H构造一个H;然后可以使用它::(a->b)->H->b。请看下面我的最新答案(不知道如何在评论中粘贴代码)@SassaNF我仍然认为你拔出的枪太大了,无法射杀这只鸟。如果您希望每种类型有一个映射,只需使用
(map Int32字符串,map Int64字符串)
;或者,如果您的解码函数肯定要解码所有大ID或所有小ID,只需使用
或(Map Int32 String)(Map Int64 String)
。我认为,当只涉及两种类型时,使用非常简单、老式的类型会有很长的路要走。@DanielWagner我试图想象使用带有任意类型键的映射的函数的用户代码是什么样子。你是说老式的模式匹配f(左m)=查找读数32m;f(右m)=查找读数64 m?我认为readInt*和lookup之间的路径越长,用户代码就越难组织和维护两段完全相同的代码。
{-# 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 ()
    {-# 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