Haskell 哈斯克尔:为什么要使用代理?

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的工作方式。 我最初认为,在使用代理时,获取某

在Haskell中,a是一个类型见证值,可以轻松地传递某些类型

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'