2

我是 Scala 的新手,并试图理解模式匹配构造的语法,特别是来自 Unfiltered ( http://unfiltered.databinder.net/Try+Unfiltered.html ) 中的示例。

这是一个简单的 HTTP 服务器,它回显 Hello World!如果路径有 2 部分长,则为路径的 2 部分:

package com.hello

import unfiltered.request.GET
import unfiltered.request.Path
import unfiltered.request.Seg
import unfiltered.response.ResponseString

object HelloWorld {
  val sayhello = unfiltered.netty.cycle.Planify {
    case GET(Path(Seg(p :: q :: Nil))) => {
      ResponseString("Hello World! " + p + " " + q);
    }
  };

  def main(args: Array[String]) {
    unfiltered.netty.Http(10000).plan(sayhello).run();
  }
}

还可以参考 Path、Seg 和 GET/Method 对象的源代码:

package unfiltered.request

object Path {
  def unapply[T](req: HttpRequest[T]) = Some(req.uri.split('?')(0))
  def apply[T](req: HttpRequest[T]) = req.uri.split('?')(0)
}

object Seg {
  def unapply(path: String): Option[List[String]] = path.split("/").toList match {
    case "" :: rest => Some(rest) // skip a leading slash
    case all => Some(all)
  }
}

class Method(method: String) {
  def unapply[T](req: HttpRequest[T]) = 
    if (req.method.equalsIgnoreCase(method)) Some(req)
    else None
}

object GET extends Method("GET")

我能够分解它的大部分工作原理,但这条线让我感到困惑:

case GET(Path(Seg(p :: q :: Nil))) => {

我了解代码的用途,但不了解它是如何应用的。我对学习 Scala 的细节非常感兴趣,而不是简单地用它实现 HTTP 服务器,所以我已经研究了几个小时。我知道它与提取器和, 和对象上的unapply方法有关GET,我也知道当我调试它时会遇到before和before 。PathSegunapplyGETPathPathSeg

我不明白以下几点:

  1. 为什么我不能写GET.unapply(req),但我可以写GET(req)或者GET()它会匹配任何HTTP GET?

  2. 编译器为什么或如何知道将哪些值传递给每个提取器的unapply方法?似乎它只会将它们链接在一起,除非其中一个返回 aNone而不是Some?

  3. 它如何绑定变量 p 和 q?它知道它们是字符串,它必须从 的返回类型推断出来Seg.unapply,但我不明白分配 p 列表第一部分的值和 q 列表第二部分的值的机制。

  4. 有没有办法重写它以使其更清楚发生了什么?当我第一次看这个例子的时候,我被这行弄糊涂了 val sayhello = unfiltered.netty.cycle.Planify {,我翻来覆去重写了它,发现它隐式地创建了一个 PartialFunction 并将它传递给 Planify.apply。

4

1 回答 1

2

理解它的一种方法是重写这个表达式,就像它被 Scala 编译器重写一样。

unfiltered.netty.cycle.Planify期望 a PartialFunction[HttpRequest[ReceivedMessage], ResponseFunction[NHttpResponse]],即可能与参数匹配或不匹配的函数。如果两个case语句中的任何一个都不匹配,则该请求将被忽略。如果有一个匹配——它也必须通过所有的提取器——将返回响应。

每个case语句都有一个HttpRequest[ReceivedMessage]. 然后,它通过一系列unapply方法为每个匹配器应用左关联性:

// The request passed to us is HttpRequest[ReceivedMessage]
// GET.unapply only returns Some if the method is GET
GET.unapply(request) flatMap { getRequest =>
    // this separates the path from the query
    Path.unapply(getRequest) flatMap { path =>
        // splits the path by "/"
        Seg.unapply(path) flatMap { listOfParams =>
            // Calls to unapply don't end here - now we build an
            // instance of :: class, which 
            // since a :: b is the same as ::(a, b)
            ::.unapply(::(listOfParams.head, listOfParams.tail)) flatMap { case (p, restOfP) =>
                ::.unapply(::(restOfP.head, Nil)) map { case (q, _) =>
                    ResponseString("Hello World! " + p + " " + q)
                }
            }
        }
    }
}

希望这能让您了解匹配在幕后是如何工作的。我不完全确定我是否::正确 - 欢迎评论。

于 2013-09-29T03:54:16.083 回答