"target" 是做什么的?
What does "target" do?
在唤醒代码中,我知道 def
表示某物是一个函数。但是,看起来还有另一个关键字 target
也定义了函数。例如 (taken from here):
global target makeBitstream plan =
...
global def makeMCS plan =
...
这两个都可以从命令行调用。 def
和 target
有什么区别?
我刚刚写了一篇关于这个主题的文章:
虽然唤醒语言主要是函数式的,但它并不完全纯粹。 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 正在记忆一个整数结果(可能是预期的结果)。
在唤醒代码中,我知道 def
表示某物是一个函数。但是,看起来还有另一个关键字 target
也定义了函数。例如 (taken from here):
global target makeBitstream plan =
...
global def makeMCS plan =
...
这两个都可以从命令行调用。 def
和 target
有什么区别?
我刚刚写了一篇关于这个主题的文章:
虽然唤醒语言主要是函数式的,但它并不完全纯粹。 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 正在记忆一个整数结果(可能是预期的结果)。