Adding dollar sign (apply) to nix

Table of Contents

Reading time: 1 minute

Back Home.

beebasse.webp

This post is very low quality for comedic effect.

1. A detailed list of instructions

Take one nix, apply this patch1:

What does this do? It adds a new token $ to the nix languages parser and exposes it as __dollar, essentially by copying the way this works for __lessThan.

Hack on your nix:

nix develop
./bootstrap.sh
./configure $configureFlags --prefix=$(pwd)/outputs/out
make -j $NIX_BUILD_CORES
outputs/out/bin/nix repl

Now in the nix repl that just opened:

nix-repl> __dollar = x: y: builtins.foldl' (a: b: a b) x y

This makes $ an infix operator that’s essentially just a generic apply.

Now just create a toy example to demonstrate:

nix-repl> concat = x: y: z: (x + y + z)
nix-repl> concat $ ["literally" " " "dollar sign"]

And your repl will say:

"literally dollar sign"

Tadaa! That’s all it takes to add the $ operator to nix, just like Haskell.

The end \(\text{\\(^ヮ^)/}\)

Back Home.

Footnotes:

1
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 201370b90..4b26af623 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -353,6 +353,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
 %left AND
 %nonassoc EQ NEQ
 %nonassoc '<' '>' LEQ GEQ
+%nonassoc '$' DOL
 %right UPDATE
 %left NOT
 %left '+' '-'
@@ -411,6 +412,8 @@ expr_op
   | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
   | expr_op '>' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
   | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
+  | expr_op '$' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__dollar")), {$1, $3}); }
+  | expr_op DOL expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__dollar")), {$3, $1})); }
   | expr_op AND expr_op { $$ = new ExprOpAnd(makeCurPos(@2, data), $1, $3); }
   | expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); }
   | expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); }
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index ddf529b9e..00622a142 100644
--- a/src/libexpr/primops.cc
...skipping...
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 201370b90..4b26af623 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -353,6 +353,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
 %left AND
 %nonassoc EQ NEQ
 %nonassoc '<' '>' LEQ GEQ
+%nonassoc '$' DOL
 %right UPDATE
 %left NOT
 %left '+' '-'
@@ -411,6 +412,8 @@ expr_op
   | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
   | expr_op '>' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
   | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
+  | expr_op '$' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__dollar")), {$1, $3}); }
+  | expr_op DOL expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__dollar")), {$3, $1})); }
   | expr_op AND expr_op { $$ = new ExprOpAnd(makeCurPos(@2, data), $1, $3); }
   | expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); }
   | expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); }
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index ddf529b9e..00622a142 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -3583,6 +3583,24 @@ static RegisterPrimOp primop_lessThan({
     .fun = prim_lessThan,
 });

+static void prim_dollar(EvalState & state, const PosIdx pos, Value * * args, Value & v)
+{
+    state.forceValue(*args[0], pos);
+    state.forceValue(*args[1], pos);
+    // pos is exact here, no need for a message.
+    CompareValues comp(state, noPos, "");
+    v.mkBool(comp(args[0], args[1]));
+}
+
+static RegisterPrimOp primop_dollar({
+    .name = "__dollar",
+    .args = {"e1", "e2"},
+    .doc = R"(
+      Just like haskell :)
+    )",
+    .fun = prim_dollar,
+});
+

 /*************************************************************
  * String manipulation

Author: Christina Sørensen

Created: 2024-04-14 Sun 10:06