为什么`functionArgs` 实现了两次? 即,作为 primop 和在 `lib` 中

问题描述

试图理解 callPackage,所以查找了 its implementation 使用 lib.functionArgs (source) 的地方,但是已经有一个 builtins.functionArgs primop,一个别名__functionArgs (implemented in C)。

lib.functionArgs 定义为

   /* Extract the expected function arguments from a function.
      This works both with nix-native { a,b ? foo,... }: style
      functions and functions with args set with 'setFunctionArgs'. It
      has the same return type and semantics as builtins.functionArgs.
      setFunctionArgs : (a → b) → Map String Bool.
   */

    functionArgs = f: f.__functionArgs or (builtins.functionArgs f);

上面的 __functionArgs 属性来自 setFunctionArgs (source):

  /* Add Metadata about expected function arguments to a function.
     The Metadata should match the format given by
     builtins.functionArgs,i.e. a set from expected argument to a bool
     representing whether that argument has a default or not.
     setFunctionArgs : (a → b) → Map String Bool → (a → b)

     This function is necessary because you can't dynamically create a
     function of the { a,... }: format,but some facilities
     like callPackage expect to be able to query expected arguments.
  */

  setFunctionArgs = f: args:
    {
      __functor = self: f;
      __functionArgs = args;
    };

我理解 setFunctionArgs 的作用,其声明上方的注释说明了为什么它是必要的,但我无法理解;该句子的两个子句都很清楚,但不确定第一个语句如何阻止实现第二个语句(没有 setFunctionArgs,即)。

danbst also tried to elucidate this further,

lib.nix 添加 __functionArgs attr 来模仿 __functionArgs 内置。它 用于将实际 __functionArgs 结果“传递”给消费者,因为 内置 __functionArgs 仅适用于最顶层的函数参数

但不确定“消费者”是什么,并且无法解压最后一个子句(即“builtin __functionArgs 仅适用于最顶层的函数 args”)。这是对 Nix 函数被柯里化这一事实的引用,以及

nix-repl> g = a: { b,c }: "lofa"

nix-repl> builtins.functionArgs g
{ }

lib.functionArgs 也没有解决这个问题,但我现在可能跑偏了。


给自己的笔记

__functor 记录在 Nix 手册的 Sets 下。

$ nix repl '<nixpkgs>'
Welcome to Nix version 2.3.6. Type :? for help.

Loading '<nixpkgs>'...
Added 11530 variables.

nix-repl> f = { a ? 7,b }: a + b

nix-repl> set_f = lib.setFunctionArgs f { b = 9; }

nix-repl> set_f
{ __functionArgs = { ... }; __functor = «lambda @ /nix/store/16blhmppp9k6apz41gjlgr0arp88awyb-nixos-20.03.3258.86fa45b0ff1/nixos/lib/trivial.nix:318:19»; }

nix-repl> set_f.__functionArgs
{ b = 9; }

nix-repl> set_f set_f.__functionArgs
16

nix-repl> set_f { a = 27; b = 9; }
36

解决方法

lib.functionArgs 包装 builtins.functionArgs 以提供对泛型函数的反射访问。

这支持使用 builtins.functionArgs 进行反射:

f = { a,b,c }: #...

现在考虑相同函数的eta abstraction

f' = attrs: f attrs

这不支持使用 builtins.functionArgs 进行反射。使用 setFunctionArgs,您可以恢复该信息,只要您还使用 lib.functionArgs

我建议避免反射,因为我见过的所有用它实现的东西都可以不用它来实现。它扩展了函数的定义,以包括通常应视为实现细节的内容。 无论如何,主要动机似乎是 callPackage,如果您像在 ... 中那样更改所有包以添加 { lib,stdenv,... }:,则可以通过正常的 attrset 操作实现。我确实对函数反射这个错误特性有病态的兴趣,所以如果有人发现另一个用例,请发表评论。