问题描述
这是Associate a function with a type in Haskell的后续问题。
同样,假设您有一个序列化器/反序列化器类型类
class SerDes a where
ser :: a -> ByteString
des :: ByteString -> a
,而您想提供一些类型a
有所不同的健全性检查或测试用例,
check :: ByteString -> Bool
当然,des
在check
的深处使用,但是无法推导类型a
。去做
对于a
有用的东西,它可能需要是某个类型类的成员,比方说它是Show a
。
像往常一样,Proxy可以完成这项工作:
data Proxy a = Proxy -- or import Data.Proxy
check :: Proxy a -> ByteString -> Bool
check (Proxy :: Proxy MyType) input = ...
check (Proxy :: Proxy MyOtherType) input = ...
(使用扩展名TypeApplications
,可以使其更加简洁:check (Proxy @MyType) ...
。)
但是可以避免Proxy
吗? (假设您不能将check
移到SerDes
类型类中。)
解决方法
我必须首先提到,您在类型类的设计中遇到了问题。 des
函数的签名表明,对于每个输入ByteString
,都有一个有效的输出a
。这可能不是事实。
例如,假设SerDes
有一个Int
的实例。根据您的定义,即使对于6GB Int
的随机数据,您也将具有有效的ByteString
表示形式。听起来不对。
因此,您需要在des
的签名中指定反序列化失败的可能性。一种典型的方法是将a
包装在Maybe
或Either YourDetailedRepresentationOfFailure
中。例如,
class SerDes a where
ser :: a -> ByteString
des :: ByteString -> Either Text a
实际上,这是所有Haskell序列化和解析库基本上采用的方法。他们可能会在此处引入一些抽象,但是从本质上讲,它们都找到了表示反序列化失败的方法。
现在是您的实际问题。 Typeclass实例由它们的类型标识,因此您必须以某种方式为a
提供特定的类型。 Proxy
是一种选择。另一种方法是直接引用该类型,而无需在函数中使用该类型并为其传递undefined
值。第三(也是最干净的IMO)是将结果包装在Tagged
中。
undefined
选项
{-# LANGUAGE ScopedTypeVariables,TypeApplications #-}
check :: forall a. SerDes a => a -> ByteString -> Bool
check _ bytes =
isRight (des @a bytes)
请注意,必须使用forall
和ScopedTypeVariables
扩展名才能引用函数def中的type参数。
然后您将像这样调用此函数:
check (undefined :: a) bytes
或者这个:
check @a undefined bytes
Tagged
选项
check :: SerDes a => ByteString -> Tagged a Bool
check bytes =
fmap isRight (Tagged (des bytes))
然后您将像这样调用此函数:
unTagged (check @a bytes)
最后的笔记
实际上,您可能永远不需要check
函数,因为des
已经携带了所有需要的信息以及更多信息。仅在需要isRight (des @a bytes)
的地方放置check
会更容易,也更容易理解。实际上,您在定义check
时必须经过复杂的处理,这实际上是设计错误的信号。在实用的Haskell代码中,您很少遇到这种复杂情况。
是的,您可以通过a
指定类型TypeApplications
,方法与您链接的答案完全相同:
{-# LANGUAGE ScopedTypeVariables,AllowAmbiguousTypes,TypeApplications #-}
check :: forall a. SerDes a => ByteString -> Bool
check bytes = ... des @a bytes ...
请注意,您需要ScopedTypeVariables
和forall a.
才能为类型变量a
创建范围,以便在调用des
时可以引用它。如果没有显式的forall
,则类型变量的范围将仅限于类型签名,并且您不能在正文中提及它。
要调用check
函数,请像以前一样使用application类型:
check @Int bytes