【问题标题】:Parallel functions composition in Functional Programming函数式编程中的并行函数组合
【发布时间】:2019-08-30 18:15:26
【问题描述】:

这个问题是一个更大问题的子问题,这是最后一个难题:

我有两个功能:

getUserFromDB :: Token -> Either String User
getNewUser :: Token -> Either String User

如何将它们结合起来得到这样的功能:

getUser :: Token -> Either String User

以便getUserFromDBgetNewUser 依次尝试,输入相同的输入(Token 是 OAuth api 的 api 令牌)并返回第一个成功函数的结果,或者一些如果没有成功则默认值?

换句话说:如果 FP 中有一个函数,其签名如下:

tryUntilGoodOrFail :: failValue -> (result -> Boolean) -> [fn] -> input -> result
OR
tryUntilGoodOrFail :: a -> (a -> Boolean) -> [(b -> a)] -> b -> a

编辑

这只是一个关于函数式编程的新手问题。我只是不知道如何将两个(或更多)函数并行排列,这与连续函数管道(FP 非常擅长)相反。但在现实世界的情况下,这种并行序列函数模式是经常需要的。我相信 FP 对此有自己的解决方案。我只是在任何地方都找不到它。

【问题讨论】:

  • 为什么这个标签是“理论”?这听起来很像一个特定的 Haskell 问题。
  • 它与语言无关,通常与 FP 有关。
  • 我使用 Scala,但听起来这可以使用模式匹配轻松完成。 Scala 中的最终错误类型将被编写为两种错误类型的超类。我不知道如何在 Haskell 中编写它。
  • @StasShepelev 在 Haskell 中,EitherSemigroup 的一个实例,并且有一个default value function,所以你只需写fromRight failValue $ getUserFromDB token <> getNewUser token
  • @StasShepelev 对于更通用的版本,您也可以使用let (Right result) = sconcat $ map ($ token) [getUserFromDB, getNewUser] |: Right failvalue

标签: functional-programming theory


【解决方案1】:

部分问题是Eithers 是右偏的,这意味着它们被设计为在第一个Left 上短路,但您希望它在第一个Right 上短路。如果您将所有内容都设为Either User String,那么您的getUser 将是:

getUser token = getUserFromDb token >> getNewUser token

如果你想保持它为Either String User,你必须交换它然后绑定,或者使用if表达式或其他东西自己短路。

【讨论】:

  • 哇!这是我对这个问题的第一个有意义和有教育意义的答案!谢谢!你知道这种模式在 FP 中是否相当不受欢迎吗?我找不到任何“教科书”解决方案。在 Either 中左右切换或为此用例创建一些特殊容器似乎有些过火了。
  • 顺便说一下getUserFromDb token 得到了调用(执行)的函数,如果我做对了——在那之后你不能>> 他们。或者你可以吗?在 FP 中,值是不需要参数的函数!对吗?
  • getUserFromDb tokengetNewUser token 中的 getUserFromDb token >> getNewUser token 是否意味着这两个函数都被执行了?仅当 getUserFromDb 失败时,我才需要执行 getNewUser
  • Haskell 默认是惰性的。除非需要,否则不会执行第二个函数。
【解决方案2】:

这种听起来像Applicative Functors(也叫here)。如果用户已经在系统中,假设getNewUser 将返回Left(或Failure,具体取决于语言),那么您可以使用函数列表并按顺序应用它们并获得Result 列表背部。然后你可以找到第一个成功的结果,而不用关心它是新用户还是现有用户。

这不会短路,但它会做次好的事情。

【讨论】:

  • Result的列表是否意味着输入列表中的所有函数都会被执行?这意味着所有功能都可以成功,并且即使getUserFromDB 会找到搜索到的用户并且我们实际上不需要新用户,也会在 DB 中创建新用户。我认为如果语言是惰性的(例如 Haskell)就不会发生这种情况,但不适合非惰性语言。
  • 是的。这就是应用程序的本质。这就是为什么我说如果用户已经存在,getNewUser 应该返回一个Left。或者,您可以只使用普通的bind,并在找到/创建用户时从每个函数返回Left<Pair<Token, User>>,在未找到/创建用户时返回Right<Token>。这应该会使第一个 Left 上的链短路,从而允许 Token 传递到下一个阶段以供尝试。
【解决方案3】:

我找到的最简单的解决方案(适用于任何语言是否懒惰):fold(更准确地说是foldl)。不知道如何用 Haskell 或 PureScript 来表达。这是我的 Python 版本:

getUser = lambda token: fold(
  lambda e,f:e if e.isRight else f(token),
  left('None Worked'),
  [getUserFromDB, getNewUser, someOtherAttempt, ... ]
)

JS 也一样:

const getUser = token => fold(
  (e,f)=>e.isRight?e:f(token),
  left('None Worked'),
  [getUserFromDB, getNewUser, someOtherAttempt, ... ]
)

PureScript 有一个有趣的函数,叫做until,它也可以提供帮助。但它会返回一个结果列表,直到第一个成功的结果(这是我正在寻找的),我可以将它提供给 lastmaybe 并获得第一个好的结果或默认值。但是fold 一口气完成了所有操作,并且更加通用、通用和标准。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-24
    • 1970-01-01
    • 1970-01-01
    • 2015-10-03
    • 2010-09-06
    • 2020-06-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多