Haskell 哈斯克尔:为什么要使用代理?
在Haskell中,a是一个类型见证值,可以轻松地传递某些类型Haskell 哈斯克尔:为什么要使用代理?,haskell,Haskell,在Haskell中,a是一个类型见证值,可以轻松地传递某些类型 data Proxy a = Proxy 使用示例如下所示: 因此,您可以使用schema(Proxy::Proxy(Int,Char))来获得Int-Char元组的JSON表示形式(可能是一个数组) 为什么人们使用代理?在我看来,同样的事情也可以通过 class JSONSchema a where schema :: Schema a 类似于Boundedtypeclass的工作方式。 我最初认为,在使用代理时,获取某
data Proxy a = Proxy
使用示例如下所示:
因此,您可以使用schema(Proxy::Proxy(Int,Char))
来获得Int-Char元组的JSON表示形式(可能是一个数组)
为什么人们使用代理?在我看来,同样的事情也可以通过
class JSONSchema a where
schema :: Schema a
类似于Bounded
typeclass的工作方式。
我最初认为,在使用代理时,获取某个给定值的模式可能更容易,但这似乎不是真的:
{-# LANGUAGE ScopedTypeVariables #-}
schemaOf :: JSONSchema a => a -> Schema a
schemaOf (v :: x) = schema (Proxy :: Proxy x) -- With proxy
schemaOf (v :: x) = schema :: Schema x -- With `:: a`
schemaOf _ = schema -- Even simpler with `:: a`
此外,人们可能会担心代理
值是否在运行时被实际删除,这是使用::a
方法时不存在的优化问题
如果::Bounded
所采用的方法能够以更短的代码和更少的优化顾虑获得相同的结果,那么人们为什么使用代理呢代理的好处是什么?
EDIT:一些答案和评论者正确地指出,::a
方法用一个“无用”的类型参数污染了数据模式=…
类型-至少从普通数据结构本身的角度来看,它从不使用a
()
建议改为使用幻影类型,它允许分离两个关注点(标记模式
将非参数模式类型与类型变量a
),这严格优于::a
方法
因此,我的问题最好是代理与标记方法相比有什么好处?最终,它们将执行相同的功能,并且您可以在两种方式中看到它们。有时用幻影标记你的值是合适的,有时你会认为它们是非类型化的
另一种选择是使用Data.taged
class JSONSchema a where
schema :: Tagged a Schema
这里我们有两个方面的优点,因为标记的模式具有解析实例所需的幻影类型信息,但我们可以使用未标记::标记的sb->b
忽略该信息
我想说,这个例子中的驱动问题应该是“我想考虑<代码> schema < /代码> s的类型化操作吗?”如果答案是“否”,那么您将偏向于代理
或标记
方法。如果答案是“是”,那么Schema a
是一个很好的解决方案
最后,您可以使用代理
方法(有些粗俗),而无需任何导入。你有时会看到这种风格
class JSONSchema a where
schema :: proxy a -> Schema
现在,Proxy
已成为一个具有提示性名称的类型变量,只有我们可以执行以下操作
foo :: Schema
foo = schema ([] :: [X])
而且根本不必导入Proxy
。我个人认为这是一项彻底的黑客工作,但最终可能会让读者感到困惑。两个例子,一个是必须使用代理,另一个是代理不能从根本上改变类型,但我还是倾向于使用它
代理
必要
代理
或一些等效技巧是必要的,当您希望消费者能够指定一些中间类型,而这些中间类型在普通类型签名中未公开时。可能中间类型会更改语义,例如read。show::String->String
。启用ScopedTypeVariables
后,我会编写
f :: forall proxy a. (Read a, Show a) => proxy a -> String -> String
f _ = (show :: a -> String) . read
proxy参数允许我将a
作为类型参数公开<代码>显示。read
是一个愚蠢的例子。更好的情况可能是某些算法在内部使用泛型集合,所选集合类型具有一些性能特征,您希望使用者能够控制这些性能特征,而无需(或允许)他们提供或接收中间值
类似于这样,使用类型,我们不想公开内部数据
类型。(也许有人能为这个例子提出一个合适的算法?)
公开代理参数将允许用户在Patricia树或普通树图实现之间进行选择
代理
作为API或实现方便
我有时使用代理作为选择typeclass实例的工具,尤其是在递归或归纳类实例中。考虑< <代码> MyBea I类:
其思想是从任意一个字符串(任意一个Bool Int)
中提取一个可能是Int
。isA
的类型基本上是a->可能是t
。在这里使用代理有两个原因:
首先,它消除了消费者的类型签名。您可以调用isA
作为isA(Proxy::Proxy Int)
而不是isA::MightBeA Int a=>a->Maybe Int
第二,通过传递代理,我更容易思考归纳案例。使用ScopedTypeVariables
,可以在不使用代理参数的情况下重写类;归纳案例将按如下方式实施:
instance MightBeA' t b => MightBeA' t (Either a b) where
-- no proxy argument
isA' (Right xs) = (isA' :: b -> Maybe t) xs
isA' _ = Nothing
fromA' = Right . fromA'
在这种情况下,这并不是一个很大的变化;如果isA
的类型签名要复杂得多,那么使用代理将是一个很大的改进
如果只是为了实现方便,我通常会导出一个包装函数,这样用户就不需要提供代理
代理
与标记的
在我的所有示例中,类型参数a
没有向输出类型本身添加任何有用的内容。(在前两个示例中,它与输出类型无关;在最后一个示例中,它与输出类型无关。)如果我返回一个标记为x的,消费者总是会立即取消标记。此外,用户还必须写出<
> f (Proxy :: Proxy Int) "3"
"3"
> f (Proxy :: Proxy Bool) "3"
"*** Exception: Prelude.read: no parse
f :: Input -> Output
f = g . h
where
h :: Gr graph Data => Input -> graph Data
g :: Gr graph Data => graph Data -> Output
class MightBeA t a where
isA :: proxy t -> a -> Maybe t
fromA :: t -> a
instance MightBeA t t where
isA _ = Just
fromA = id
instance MightBeA t (Either t b) where
isA _ (Left i) = Just i
isA _ _ = Nothing
fromA = Left
instance MightBeA t b => MightBeA t (Either a b) where
isA p (Right xs) = isA p xs
isA _ _ = Nothing
fromA = Right . fromA
instance MightBeA' t b => MightBeA' t (Either a b) where
-- no proxy argument
isA' (Right xs) = (isA' :: b -> Maybe t) xs
isA' _ = Nothing
fromA' = Right . fromA'