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

Why is `functionArgs` implemented twice? (i.e, as a primop and in `lib`)

试图理解 callPackage,所以查找 its implementation where it uses lib.functionArgs (source), but there is already a builtins.functionArgs primop, an alias of __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, b ? foo, ... }: format, but some facilities
     like callPackage expect to be able to query expected arguments.
  */

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

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

,

lib.nix adds __functionArgs attr to mimic __functionArgs builtin. It used to "pass" actual __functionArgs result down to consumers, because builtin __functionArgs only works on top-most function args

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

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

nix-repl> builtins.functionArgs g
{ }

?

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


自述

__functor 记录在 Sets 下的 Nix 手册中。

$ 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,如果您更改所有包以添加 ...,则 可以 通过正常的 attrset 操作来实现,如 { lib, stdenv, ... }: ].我确实对函数反射这个错误特性抱有病态的兴趣,所以如果有人发现另一个用例,请发表评论。