如何使用 Monadic Bind 简化此 Apache Tomcat 代码?

How could this Apache Tomcat code be simplified with a Monadic Bind?

commentator writes:

Some nice "greater-than sign" code in Tomcat. Needs a healthy dose of (>>=).

查看the AuthenticatorBase.java class from Apache Tomcat时:

/**
 * Enforce the security restrictions in the web application deployment
 * descriptor of our associated Context.
 *
 * @param request Request to be processed
 * @param response Response to be processed
 *
 * @exception IOException if an input/output error occurs
 * @exception ServletException if thrown by a processing element
 */
@Override
public void invoke(Request request, Response response)
    throws IOException, ServletException {

    if (log.isDebugEnabled())
        log.debug("Security checking request " +
            request.getMethod() + " " + request.getRequestURI());
    LoginConfig config = this.context.getLoginConfig();

    // Have we got a cached authenticated Principal to record?
    if (cache) {
        Principal principal = request.getUserPrincipal();
        if (principal == null) {
            Session session = request.getSessionInternal(false);
            if (session != null) {
                principal = session.getPrincipal();
                if (principal != null) {
                    if (log.isDebugEnabled())
                        log.debug("We have cached auth type " +
                            session.getAuthType() +
                            " for principal " +
                            session.getPrincipal());
                    request.setAuthType(session.getAuthType());
                    request.setUserPrincipal(principal);
                }
            }
        }
    }

我不得不承认,我不知道如何应用它。我知道有一种方法可以将 if-tree 重构为 monadic 绑定,但我不知道该怎么做。

假设:

我的问题是:如何使用 Monadic Bind 简化此 Apache Tomcat 代码?

好吧,这里有副作用 (request.set...),当没有副作用时 bind 更有用。使用 ifPresent 就足够了:

Optional.ofNullable(principal).ifPresent(principal ->
  Optional.ofNullable(request.getSessionInternal(false)).ifPresent(session ->
    Optional.ofNullable(session.getPrincipal).ifPresent(principal -> {
      if (log.isDebugEnabled())
        log.debug(...);
      request.setAuthType(session.getAuthType());
      request.setUserPrincipal(principal);
    })));

这看起来不像是胜利;重复,但如果 request.getSessionInternalsession.getPrincipal 已经返回 Optional,则 Optional.ofNullable(...) 不是必需的。

你可以编写一个方法,其工作方式类似于 Optional.ofNullable(...).ifPresent:

public static <T> void ifNotNull(T value, Consumer<? super T> consumer) {
  if (value != null) { consumer.accept(value); }
}

ifNotNull(principal, principal ->
  ifNotNull(request.getSessionInternal(false), session ->
    ifNotNull(session.getPrincipal, principal -> {
      if (log.isDebugEnabled())
        log.debug(...);
      request.setAuthType(session.getAuthType());
      request.setUserPrincipal(principal);
    })));

(注意:不确定我是否记得 Java 语法,我已经有一段时间没有使用它了。)

我是 Twitter 评论的作者。在此示例中,"low-hanging fruit" 用于 简化,假设存在一个单子选项类型,是在foo上预测的几层嵌套代码不是null” =68=].

让我们关注以下片段:

Principal principal = request.getUserPrincipal();
if (principal == null) {
    Session session = request.getSessionInternal(false);
    if (session != null) {
        principal = session.getPrincipal();
        if (principal != null) {
            if (log.isDebugEnabled())
                log.debug("We have cached auth type " +
                    session.getAuthType() +
                    " for principal " +
                    session.getPrincipal());
            request.setAuthType(session.getAuthType());
            request.setUserPrincipal(principal);
        }
    }
}

由于我们讨论的是一般结构而不是具体 Java 或任何特定语言,让我们引入一些新的假设(这些假设可能很容易也可能不容易体现在 Java 代码中),并且我们将重构代码以利用它们。

假设 1:所有可以 return null return 和 Optional<T> 的方法,并且 从不 return null 因为 absense/failure 情况是在 Optional 值本身中处理的。在其他语言中,Optional 被称为 OptionMaybe,可能还有其他名称。

假设 2Optional 有一个 monadic bind 方法。 Java 8 的 Optional 调用此函数 flatMap,因此我们也将调用它。在 Haskell 中,它被称为 >>=,但名称并不重要 - 重要的是类型:

<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)

为了比较,在 Haskell 中,>>= 是为所有具有 Monad 类型 class 实例的类型定义的;类型签名是:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

当专用于Maybe并使用不同的类型变量名时,与Java的Optional的对应关系变得更加清晰。

(>>=) :: Maybe t -> (t -> Maybe u) -> Maybe u

假设 3:该语言具有 first-class lambda。我知道 Java 8 有 lambda,但我不知道我头脑中的语法,所以我只是想弥补 :)

应用这些假设,简化代码看起来像:

Optional<Principal> principal = request.getUserPrincipal();
if (!principal.isPresent()) {
    Optional<Session> session = request.getSessionInternal(false);
    principal = session.flatMap((sess) { sess.getPrincipal() });
    principal.flatMap((principal) {
        if (log.isDebugEnabled()) ... // as before
        request.setAuthType(session.getAuthType());
        request.setUserPrincipal(principal);
    })
}

请注意,每次连续调用 flatMap 都会从程序中删除一个 if 级别。对 flatMap 的调用也可以链接起来以避免中间赋值。

在 Java 中使用单子绑定模式的好处是显而易见的,但不幸的是它们是有限的。因为 flatMap 是为 Optional 具体定义的(也许类似的方法也为其他类型具体定义),而不是抽象地定义在类型 class 或接口上,程序员无权访问免费的许多派生操作。必须为每个实例手动编写此类派生操作。