如何在 monad 转换器堆栈中建模可变键/值映射?

问题描述 投票:0回答:0

有点设计题

我目前有一个“App”类,它是一个 monad 转换器堆栈,我想添加到该堆栈的是一个可变的键/值存储。

我希望能够轻松更改该商店的确切实施方式。最初我希望它只是一个内存

Map
,但后来我可能会将它更改为一些基于数据库的东西。

首先想到的就是在我的 monad 转换器堆栈中添加一个

StateT
。这适用于内存中的
Map
,但我认为这不适用于数据库实现,因为我不想每次都获取/更新entire状态,而只是一个键/值组合.

第二个想法是编写我自己的 monad 转换器。但是当我查看现有的转换器时,它们都有包含所有其他 monad 转换器实例的类,因此它们可以与其他转换器一起使用。我 认为 添加我自己的 monad 转换器并允许它与所有其他转换器很好地交互将涉及编写

n
(或者实际上可能是
2 * n
实例)以便它可以穿透其他转换器,其他转换器可以穿透它。从技术上讲,我只需要用我正在使用的变压器来做这件事,但这似乎有点老套。关于 monad 转换器堆栈是否是一个好主意,这里可能还有另一个争论,但我不想在这一点上完全改变我的 App 架构。

所以我的第三个想法是有一个

ReaderT
和我可以替换的函数记录(或者可能是带有类型类约束的记录,如
data Blah where Blah :: c => Blah
)。我认为我可以开始工作,但我不确定这是最好的方法,我必须在
ReaderT
之上为我认为的内存方法分层
StateT

作为我猜的第四种方法,我确实写了一堆这样的类型类:

data HadKey = KeyFound | KeyNotFound

class Monad m => LookupMapM key value m where
  lookupM :: key -> m (Maybe value)

class LookupMapM key value m => InsertMapM key value m where
  insertM :: key -> value -> m HadKey

class InsertMapM key value m => UpdateMapM key value m where
  updateM :: key -> value -> m HadKey

然后我开始实现接口,像这样:

instance LookupMapM key value (StateT (Map key value) m) where ...

instance (...) => LookupMapM key value IO where ...

这看起来很有希望,但是第二个实例似乎有问题......就像,我只能有一个

IO
实现(大概不同的数据库应该能够有自己的实现)。

我想我可以使用包 tagged identity 解决上述问题,这样我就可以标记不同的实现。

我注意到如果我的“地图”转换器不在堆栈的顶部,我仍然需要提升它,但这不是什么大问题,我可以只维护一个

liftKV
功能,只需要一个额外的
lift
每次将一层添加到 monad 转换器堆栈时,我认为这没什么大不了的。

但也许有比我建议的方法更好的方法?或者也许我建议的方法之一是最好的方法,我只是不确定是哪一种。

这里的其他想法不是:

class Monad m => LookupMapM key value m where
  lookupM :: key -> m (Maybe value)

采用这种风格:

class Monad m => LookupMapM t m where
  type Key t
  type Value t
  lookupM :: Key t -> m (Maybe (Value t))

但我不确定这是否解决了任何问题,或者只是增加了额外的并发症而没有任何好处。

有什么想法吗?

haskell monad-transformers
© www.soinside.com 2019 - 2024. All rights reserved.