在Haskell中将非类型类函数与类型相关联

问题描述

这是Associate a function with a type in Haskell的后续问题。

同样,假设您有一个序列化器/反序列化器类型类

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

,而您想提供一些类型a有所不同的健全性检查或测试用例,

check :: ByteString -> Bool

当然,descheck的深处使用,但是无法推导类型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包装在MaybeEither 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)

请注意,必须使用forallScopedTypeVariables扩展名才能引用函数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 ...

请注意,您需要ScopedTypeVariablesforall a.才能为类型变量a创建范围,以便在调用des时可以引用它。如果没有显式的forall,则类型变量的范围将仅限于类型签名,并且您不能在正文中提及它。

要调用check函数,请像以前一样使用application类型:

check @Int bytes