"target" 是做什么的?

What does "target" do?

在唤醒代码中,我知道 def 表示某物是一个函数。但是,看起来还有另一个关键字 target 也定义了函数。例如 (taken from here):

global target makeBitstream plan =
  ...

global def makeMCS plan =
  ...

这两个都可以从命令行调用。 deftarget 有什么区别?

我刚刚写了一篇关于这个主题的文章:

虽然唤醒语言主要是函数式的,但它并不完全纯粹。 Wake 有副作用,例如 运行ning 作业和打印。它还具有记忆功能,一种存储计算的包含方式,如下所述。

考虑斐波那契函数:

def fib n = if n < 2 then 1 else fib (n-1) + fib (n-2)

在 20 上评估此功能相对较快地完成,但 运行在 40 上对其进行评估完全是另一回事。唤醒似乎 运行 永远(真的,只是非常非常长的时间)。

问题是每次调用 fib 都会导致另外两次调用 fib。这会导致连锁反应,其中 fib 被调用的次数根据其输入呈指数增长。

让我们试试特殊的目标关键字:

target fib n = if n < 2 then 1 else fib (n-1) + fib (n-2)

现在 fib 40 很快就完成了!其实fib 40000也是returns一个结果。

这里发生的事情是目标 fib 现在记住并重新使用以前调用的结果。 fib 4 将调用 fib 3 和 fib 2。fib 3 将调用 fib 2 和 fib 1。但是,fib 2 的常见调用现在只发生一次。目标会记住结果;这叫做记忆。

虽然目标对于加速玩具功能很有用,但它的真正用途是节省构建系统中的工作。唤醒构建系统通常包括构建规则,这些构建规则调用它们所依赖的进一步规则。假设作业C依赖于作业B和A,但是作业B也依赖于作业A。我们不希望A被执行两次!

幸运的是,wake 的计划 API 默认包括一次 运行 作业的选项。在内部,此 API 使用目标来防止重新执行作业。但是,这种使用 target 在大型构建中是不够的。

在大型构建中,为其他函数生成路径的顶级函数通常应使用 target 定义。这样,即使该函数被依赖项调用两次,也只需要计算一次。在一个涉及多个依赖于多个目标的目标的构建中,结果可能是指数级的加速,就像我们在 fib 示例中看到的那样。

目标也可以在函数内部定义。这些目标只保留它们保存的值,而封闭函数可以访问它们。

def wrappedFib n = target fib n = if n < 2 then 1 else fib (n-1) + fib (n-2) fib n

在此示例中,wrappedFib 使用内部目标 fib 来计算斐波那契结果。但是,在两次调用 wrappedFib 之间,不会保留部分结果。嵌套目标可能很有用,因为它们不会在整个 wake 执行过程中消耗内存。例如,一个函数可能需要计算大量无趣的中间值,以便计算出感兴趣的值(可能会被保存)。

考虑目标的一种方式是它定义了一个 table,就像在数据库或键值映射中一样。例如,target foo xy = z 定义了一个 table 键为 Pair xy,值为 z。从这个角度来看,有时用一些不属于密钥的输入来计算 z 很有用,这也许不足为奇。

target myWrite filename\contents=写入文件名内容 在上面的示例中,myWrite "file" "content" 将创建一个名为 file 的文件并用字符串内容填充它,返回创建文件的路径。如果有人试图再次写入具有相同内容的相同文件,则将返回相同的路径。

但是,如果有人试图写入同一个文件,但内容不同怎么办?如果我们允许这样做,构建系统中就会出现竞争条件!让我们看看会发生什么:

$ wake -x '("bar", "bar", Nil) | map (myWrite "foo")' Path "foo", Path "foo", Nil $ wake -x '("bar", "baz", Nil) | map (myWrite "foo")' ERROR: Target subkey mismatch for 'myWrite filename \ contents' (demo.wake:1:[8-34])

第一次调用,两次调用都成功,foo只创建了一次。在第二次调用中,其中一个调用因目标子项不匹配而失败。这些失败在 wake 中是致命的,因为由于 wake 使用的无序并行评估策略,永远不清楚哪个调用失败了。尽管如此,如果有错误的构建失败,可能比偶尔成功要好。

最后,请注意这个常见的目标陷阱:

target foo x = match _ None = None Some y = x + y

通常在wake中,对于define(def),上面的函数是这样的:

target foo x y = match y None = None Some y = x + y

但是,在目标情况下,这些就大不一样了。第一个目标 foo 正在记忆一个函数结果,而第二个目标 foo 正在记忆一个整数结果(可能是预期的结果)。