将函数与 Haskell 中的类型相关联

Associate a function with a type in Haskell

假设您有一个 serializer/deserializer 类型 class

class SerDes a where
    ser :: a -> ByteString
    des :: ByteString -> a

事实证明,为每种类型设置一个特殊的辅助函数至关重要 a,例如

compress :: ByteString -> ByteString     -- actually varies with the original type

我将 compress 视为一个函数,我想将其与每个 a(即 SerDes)相关联。 (“同事”这个词可能是一个错误的选择,也是互联网搜索一无所获的原因。)

这个例子并不像看起来那么做作,例如当 decompress 是可选的 serializer/deserializer 的特征。 (是的,可以通过增加 ser 带有控制压缩的开关,ser:: a -> Bool -> ByteString,或者更好地使用 Config 记录。但让我们坚持这个例子。)

一种方法是 'dummy' class,单例:

data For a = For

那么这将起作用:

class SerDes a where
    ser      :: a -> ByteString
    des      :: ByteString -> a
    compress :: For a -> ByteString -> ByteString

acompress 将被实例化为

compress (For :: For MyType) input = ...

另一种有点不寻常的方法是将所有函数粘贴在一条记录中。

data SerDes a = SerDes { ser      :: a -> ByteString
                       , des      :: ByteString -> a
                       , compress :: ByteString -> ByteString 
                       }

是否有任何其他方法可以将 compress 函数与 a 类型“关联”?

您的 For a 类型在库中称为 Proxy a

import Data.Proxy

class SerDes a where
    ser      :: a -> ByteString
    des      :: ByteString -> a
    compress :: Proxy a -> ByteString -> ByteString

有时这会泛化为通用 proxy 类型变量。

class SerDes a where
    ser      :: a -> ByteString
    des      :: ByteString -> a
    compress :: proxy a -> ByteString -> ByteString

还有一个选项,类似于代理。可以使用 Tagged:

a 添加到结果类型,而不是强行将 a 添加到参数中
import Data.Tagged

class SerDes a where
    ser      :: a -> ByteString
    des      :: ByteString -> a
    compress :: ByteString -> Tagged a ByteString

这需要用作 unTagged (compress someByteString :: Tagged T ByteString) 来告诉编译器我们想要 Tcompress 函数。


就个人而言,我不喜欢代理和标签。过去当 GHC 不允许另一个更简单的解决方案时需要它们,但现在不应再使用它们。

现代方法是打开无害的扩展 AllowAmbiguousTypesTypeApplications 并简单地写下你想要的 class

class SerDes a where
    ser      :: a -> ByteString
    des      :: ByteString -> a
    compress :: ByteString -> ByteString

在这种方法中,我们需要使用更短的 compress @T someByteString 而不是调用 compress (Proxy :: Proxy T) someByteString,我们明确地“传递我们想要的类型 a”(T在这种情况下),所以 select 想要的 compress.

完整示例:

{-# LANGUAGE AllowAmbiguousTypes, TypeApplications, OverloadedStrings #-}

import Data.ByteString as BS

class SerDes a where
    ser      :: a -> ByteString
    des      :: ByteString -> a
    compress :: ByteString -> ByteString

-- bogus implementation to show everything type checks
instance SerDes Int where
   ser _ = "int"
   des _ = 42
   compress bs = BS.tail bs

-- bogus implementation to show everything type checks
instance SerDes Bool where
   ser _ = "bool"
   des _ = True
   compress bs = bs <> bs

main :: IO ()
main = BS.putStrLn (compress @Int "hello" <> compress @Bool "world")
-- output: elloworldworld