什么是 `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
参数。
作为下一步,我想为这个棱镜定义一个函子,以便它可以转换所有三种类型:ctx
、s
、a
。我目前有一个像这样的 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 路由的值。我希望能够将这些路由编码器转换为 r
、ctx
和 URL 字符串(实际上是内部 FilePath
),它编码为/解码为.
备注:
- 我使用
optics-core
而不是 lens
。
编辑:这是我目前的做法:
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
和encodeRoute
和decodeRoute
定义为:
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)
要实际应用棱镜,您需要使用 reify
将 Config
抛向空中让它们捕捉。
如果其他一些程序或程序组件根据不同需求使用不同的棱镜组合,它可以使用自己的主配置类型(如 Config
)和自己的标签类型(如 UsingConfig
) 使必要的上下文可用。
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
参数。
作为下一步,我想为这个棱镜定义一个函子,以便它可以转换所有三种类型:ctx
、s
、a
。我目前有一个像这样的 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 路由的值。我希望能够将这些路由编码器转换为 r
、ctx
和 URL 字符串(实际上是内部 FilePath
),它编码为/解码为.
备注:
- 我使用
optics-core
而不是lens
。
编辑:这是我目前的做法:
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
和encodeRoute
和decodeRoute
定义为:
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)
要实际应用棱镜,您需要使用 reify
将 Config
抛向空中让它们捕捉。
如果其他一些程序或程序组件根据不同需求使用不同的棱镜组合,它可以使用自己的主配置类型(如 Config
)和自己的标签类型(如 UsingConfig
) 使必要的上下文可用。