什么是 `Prism' s a` 但带有上下文 `ctx`?

What is a `Prism' s a` but with a context `ctx`?

A type Prism' s a = Prism s s a a (hackage) 可以被认为是一些结构 s 和它的成员 a 之间的关系,这样你总是可以产生结构来自成员 (a -> s),但只能选择性地从结构 (s -> Maybe a).

中检索成员

此模型有助于将总和类型与其构造函数之一相关联......以及(此处更相关)路由编码和解码。如果 s 是编码路由 URL,并且 a 是路由类型,那么我们有 a -> s 表示编码函数(总是成功),并且 s -> Maybe a表示解码函数(可能会失败)。

现在,在这些函数对上,我想添加一个将在编码和解码过程中使用的“上下文”参数(假设解码过程需要在成功之前“查找”一些数据库产生相关路线)。基本上:

encode :: ctx -> a -> s 
decode :: ctx -> s -> Maybe a

是否有对这些转化进行建模的类型?它看起来很像 Prism' 但多了一个 ctx 参数。


作为下一步,我想为这个棱镜定义一个函子,以便它可以转换所有三种类型:ctxsa。我目前有一个像这样的 class,但看起来我可能缺少一个现有的库,我可以用它来简化所有这些:

class PartialIsoFunctor (f :: Type -> Type -> Type -> Type) where
  -- x, y are the context
  -- a, b are the structure `s`
  -- c, d are the route types `a`
  pimap ::
    Prism' b a -> -- Note: contravariant prism
    Prism' c d ->
    (y -> x) -> -- Note: this is contravariant
    f x a c ->
    f y b d

这里的想法是有一个 RouteEncoder ctx r 类型 (see also) 代表一个知道如何 encode/decode 路由的值。我希望能够将这些路由编码器转换为 rctx 和 URL 字符串(实际上是内部 FilePath),它编码为/解码为.

备注:


编辑:这是我目前的做法:

type RouteEncoder ctx s route = Prism' (ctx, s) (ctx, route)

以及转换它的函子:

mapRouteEncoder ::
  Prism' b a ->
  Prism' c d ->
  (y -> x) ->
  RouteEncoder x a c ->
  RouteEncoder y b d
mapRouteEncoder = undefined

encoding/decoding函数:

-- The use of `snd` here suggests that the use of tuples in 
-- RouteEncoder is a bit of a hack

encodeRoute :: RouteEncoder ctx r -> ctx -> r -> FilePath
encodeRoute enc ctx r = snd $ review enc (ctx, r)

decodeRoute :: RouteEncoder ctx r -> ctx -> FilePath -> Maybe r
decodeRoute enc m s = snd <$> preview enc (ctx, s)

如何简化?请注意,RouteEncoder 是提前创建和组合的。但实际的 encoding/decoding 发生在稍后,传递了随时间变化的 'ctx'。

我和@HTNW 在一起。您应该能够定义:

type RouteEncoder ctx s route = ctx -> Prism' s route

那么,pimap定义为:

pimap :: Prism' b a -> Prism' c d -> (y -> x)
  -> RouteEncoder x a c -> RouteEncoder y b d
pimap p q f r ctx = p . r (f ctx) . q

encodeRoutedecodeRoute定义为:

encodeRoute :: RouteEncoder ctx s r -> ctx -> r -> s
encodeRoute enc ctx r = review (enc ctx) r

decodeRoute :: RouteEncoder ctx s r -> ctx -> s -> Maybe r
decodeRoute enc ctx s = preview (enc ctx) s

唯一的困难是,具有延迟上下文的路由编码器的组合需要一些额外的语法。您可能需要写:

\ctx -> otherLens . myRouteEncoder ctx . otherPrism

或类似而不是简单的构图。但是,您现有的解决方案也不能与其他光学器件很好地组合,除非它们“意识到”上下文。

当您询问棱镜内部的 getter 功能如何访问棱镜外部的上下文时,我不是 100% 确定您的意思。如果您的意思是在定义时,那么答案是您只需使用类似的东西:

makeRouteEncoder a b c ctx = prism (f a b ctx) (g c ctx)

myRouteEncoder = makeRouteEncoder myA myB myC

如果您的上下文是一种适用于程序同一区域中发生的所有事情的“配置”,那么您可以考虑使用 reflection。您可以使用

type RouteEncoder ctx s r = ctx => Prism' s r

上下文变得隐含。你会得到类似

的东西
ctx ~ Zippity x
s ~ Whatever x

只有当您拥有一种贯穿始终的上下文时,这才真正符合人体工程学。但是,您可以使用本机 class 关系来增加一些灵活性,允许您使用需要更大上下文“片段”的光学。

例子

假设每个配置方面都表示为 class:

class FooC x where
  getFoo :: Foo

class BarC x where
  getBar :: Bar

class (FooC x, BarC x) => ConfigC x where
  getBaz :: Baz

现在你将拥有各种看起来大致像

的棱镜
whatever :: forall x. FooC x => Prism' (Thing x) (Thingum x)
yeah :: forall x. BarC x => Prism' (Thingum x) (Yak x)
uhHuh :: forall x. ConfigC x => Prism' (Yak x) Hum

(抱歉,不是最好的例子。)

您会注意到 none 个棱镜需要知道它们的配置信息来自何处;他们只需要他们需要的东西。

当你编写这些内容时,你会得到带有上下文的棱镜,这些棱镜会累积必要的上下文。要真正使用 这些棱镜,您需要一种或多种辅助类型。这里最明显的是

data Config = Config
  { _fooConfig :: Foo
  , _barConfig :: Bar
  , _bazConfig :: Baz }

data UsingConfig x = UsingConfig

instance Reifies x Config => FooC (UsingConfig x) where
  getFoo = _fooConfig $ reflect (Proxy @x)
instance Reifies x Config => BarC (UsingConfig x) where
  getBar = _barConfig $ reflect (Proxy @x)
instance Reifies x Config => ConfigC (UsingConfig x) where
  getBaz = _bazConfig $ reflect (Proxy @x)

要实际应用棱镜,您需要使用 reifyConfig 抛向空中让它们捕捉。

如果其他一些程序或程序组件根据不同需求使用不同的棱镜组合,它可以使用自己的主配置类型(如 Config)和自己的标签类型(如 UsingConfig) 使必要的上下文可用。