为条件分支组合这 3 种类型的函数的通用方法是什么?

What is an generalized way to compose these 3 types of functions for conditional branching?

我有三个单子函数,我想将它们组合在一起并在谓词上有条件地分支。我正在寻找可能具有权衡的多个 general 解决方案。 Arrows(ArrowChoice?)和 Monads 看起来很有前途。

人为的问题是这样的:

我 运行 一项跟踪许多客户 "personal number" 的服务。如果他们是第一次登录,他们的号码设置为 0。如果他们之前登录过,并且可能更改了他们的号码,那么这就是将为他们获取的号码。

所以这个程序的通用类型可以是program :: Name -> Int

我有 3 个函数,它们都可能具有查询持久数据存储(例如 DB、State)的单子效应:

new?     :: Name -> Bool
fetch    :: Name -> Int
generate :: Name -> Int

我希望能够将这些功能换成其他功能。例如,将 fetch 换成 fetchFromDB,或 fetchFromStateMonad.

"composing" 他们的一个解决方案可能如下所示:

ex1 :: (a -> Bool) -> (a -> b) -> (a -> b) -> (a -> b)
-- or --
ex1 :: (Name -> Bool) -> (Name -> Int) -> (Name -> Int) -> (Name -> Int)
--     predicate         fetch            generate         result

这看起来有点像 ifM,这让我更接近通用解决方案。

但是当我调用 ex1 "John" 时,它会调用一个数据库来确定谓词,另一个调用 fetch John 的号码。 (当然,在这种狭窄的情况下这很容易解决。我正在寻找更通用的)。

ex2 :: Name -> (Name -> Bool) -> (Name -> Int) -> (Name -> Int) -> Int
--     name    predicate         fetch            generate      result         

我仍然不清楚如何通过 predicatefetch 将一个数据库查询结果串联起来,但至少这种类型签名可以允许它。现在有一个类型签名的问题,它不太像组合,更像是临时的。

有没有办法概括这个问题?其中一些 composition 函数会做所有必要的事情来允许任意谓词和函数以我想要的方式连接起来。

如果new?fetch是不同的函数,无论如何你都必须进行两次查询。 (一个检查名称是否在数据库中,检查结果后另一个。)

不过,好像new?是多余的; fetch 可以 return 一个 Maybe 值来检索所需号码或表明名称是新的。还假设您的 monad 是 MonadIO 的一个实例,您的签名和函数将类似于

program :: MonadIO m => Name                  -- name to query
                     -> (Name -> m Maybe Int) -- fetch
                     -> (Name -> m Int)       -- generate
                     -> m Int                 -- number for name
program name fetch generate = fetch name >>= maybe (generate name) return