1
+ # https://discourse.nixos.org/t/tail-call-optimization-in-nix-today/17763
2
+ f :
3
+ let
4
+ lib = import <nixpkgs/lib> ;
5
+ # we'll use a fixed point and tail recursion
6
+ fac = self : acc : n : if n == 0 then acc else self ( n * acc ) ( n - 1 ) ;
7
+ apply = f : args : builtins . foldl' ( f : x : f x ) f args ;
8
+
9
+ unapply =
10
+ let
11
+ unapply' = acc : n : f : x :
12
+ if n == 1
13
+ then f ( acc ++ [ x ] )
14
+ else unapply' ( acc ++ [ x ] ) ( n - 1 ) f ;
15
+ in
16
+ unapply' [ ] ;
17
+ argCount = f :
18
+ let
19
+ # N.B. since we are only interested if the result of calling is a function
20
+ # as opposed to a normal value or evaluation failure, we never need to
21
+ # check success, as value will be false (i.e. not a function) in the
22
+ # failure case.
23
+ called = builtins . tryEval (
24
+ f ( builtins . throw "You should never see this error message" )
25
+ ) ;
26
+ in
27
+ if ! ( builtins . isFunction f || builtins . isFunction ( f . __functor or null ) )
28
+ then 0
29
+ else 1 + argCount called . value ;
30
+ tailCallOpt = f :
31
+ let
32
+ argc = argCount ( lib . fix f ) ;
33
+
34
+ # This function simulates being f for f's self reference. Instead of
35
+ # recursing, it will just return the arguments received as a specially
36
+ # tagged set, so the recursion step can be performed later.
37
+ fakef = unapply argc ( args : {
38
+ __tailCall = true ;
39
+ inherit args ;
40
+ } ) ;
41
+ # Pass fakef to f so that it'll be called instead of recursing, ensuring
42
+ # only one recursion step is performed at a time.
43
+ encodedf = f fakef ;
44
+
45
+ # This is the main function, implementing the “optimized” recursion
46
+ opt = args :
47
+ let
48
+ steps = builtins . genericClosure {
49
+ # This is how we encode a (tail) call: A set with final == false
50
+ # and the list of arguments to pass to be found in args.
51
+ startSet = [
52
+ {
53
+ key = "0" ;
54
+ id = 0 ;
55
+ final = false ;
56
+ inherit args ;
57
+ }
58
+ ] ;
59
+
60
+ operator =
61
+ { id , final , ... } @state :
62
+ let
63
+ # Generate a new, unique key to make genericClosure happy
64
+ newIds = {
65
+ key = toString ( id + 1 ) ;
66
+ id = id + 1 ;
67
+ } ;
68
+
69
+ # Perform recursion step
70
+ call = apply encodedf state . args ;
71
+
72
+ # If call encodes a new call, return the new encoded call,
73
+ # otherwise signal that we're done.
74
+ newState =
75
+ if builtins . isAttrs call && call . __tailCall or false
76
+ then newIds // {
77
+ final = false ;
78
+ inherit ( call ) args ;
79
+ } else newIds // {
80
+ final = true ;
81
+ value = call ;
82
+ } ;
83
+ in
84
+
85
+ if final
86
+ then [ ] # end condition for genericClosure
87
+ else [ newState ] ;
88
+ } ;
89
+ in
90
+ # The returned list contains intermediate steps we need to ignore
91
+ ( builtins . head ( builtins . filter ( x : x . final ) steps ) ) . value ;
92
+ in
93
+ # make it look like a normal function again
94
+ unapply argc opt ;
95
+ in
96
+ tailCallOpt f
97
+ # => 1307674368000
0 commit comments