Repository: tweag/nickel
Pull request #1937 (2024-06-03 17:19:43)

Rename and re-organize primitive operators
Primitive operators haven't got much love, as opposed to user-facing interfaces like the stdlib, and they have grown organically since the beginning of the very first Nickel prototype. As a result, the naming is inconsistent, with several generations, both in the surface syntax of Nickel and internally in the Rust codebase.

This PR makes a cleaning pass on primitive operators, by:

1. Use full worlds in the style of the current stdlib: `str` ->
   `string`, `num` -> `number`, etc.
2. Introduce pseudo-namespaces: instead of `str_foo` and `record_bar`, we use `/` as a separator for categories. The previous examples become `string/foo` and `record/bar`. We don't use `.`, to make it clear that it's not a normal record access, but just a nice way to dinstinguish the different categories
3. Align old operators on the current naming in the standard library.

This PR is mostly read for review, but in order to appease the CI, I need to update the tree-sitter grammar first to accommodate for the `/` character in primops.

yannham: Although this PR is also blocked on the `topiary-queries` update (https://github.com/tweag/topiary/pull/708), it's in reviewable state.

Patch:
diff --git a/core/src/eval/merge.rs b/core/src/eval/merge.rs
index c73306da..052bf194 100644
--- a/core/src/eval/merge.rs
+++ b/core/src/eval/merge.rs
@@ -203,7 +203,7 @@ pub fn merge<C: Cache>(
             // printed out when reporting the error.
             let contract_for_display = mk_app!(
                 mk_term::op1(
-                    UnaryOp::StaticAccess("Equal".into()),
+                    UnaryOp::RecordAccess("Equal".into()),
                     Term::Var("contract".into()),
                 ),
                 // We would need to substitute variables inside `t1` to make it useful to print,
@@ -233,7 +233,7 @@ pub fn merge<C: Cache>(
             // exactly the same, but can't be shadowed.
             let eq_contract = mk_app!(stdlib::internals::stdlib_contract_equal(), t1);
             let result = mk_app!(
-                mk_term::op2(BinaryOp::ApplyContract(), eq_contract, Term::Lbl(label)),
+                mk_term::op2(BinaryOp::ContractApply, eq_contract, Term::Lbl(label)),
                 t2
             )
             .with_pos(pos_op);
diff --git a/core/src/eval/mod.rs b/core/src/eval/mod.rs
index f1f3017d..43efb2e6 100644
--- a/core/src/eval/mod.rs
+++ b/core/src/eval/mod.rs
@@ -530,10 +530,10 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     //   corresponding polymorphic contract has been violated: a function tried to
                     //   use a polymorphic sealed value.
                     match stack_item {
-                        Some(OperationCont::Op2Second(BinaryOp::Unseal(), _, _, _)) => {
+                        Some(OperationCont::Op2Second(BinaryOp::Unseal, _, _, _)) => {
                             self.continuate_operation(closure)?
                         }
-                        Some(OperationCont::Op1(UnaryOp::Seq(), _)) => {
+                        Some(OperationCont::Op1(UnaryOp::Seq, _)) => {
                             // Then, evaluate / `Seq` the inner value.
                             Closure { body: inner, env }
                         }
@@ -662,7 +662,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                             });
 
                             Closure {
-                                body: RichTerm::new(Term::Op1(UnaryOp::ChunksConcat(), arg), pos),
+                                body: RichTerm::new(Term::Op1(UnaryOp::ChunksConcat, arg), pos),
                                 env,
                             }
                         }
@@ -721,9 +721,9 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     // extensions
                     //
                     // ```
-                    // %record_insert% exp1
+                    // %record/insert% exp1
                     //   (...
-                    //     (%record_insert% expn {stat1 = val1, ..., statn = valn} dyn_valn)
+                    //     (%record/insert% expn {stat1 = val1, ..., statn = valn} dyn_valn)
                     //   ...)
                     //   dyn_val1
                     //
@@ -751,7 +751,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                             } = field;
 
                             let extend = mk_term::op2(
-                                BinaryOp::DynExtend {
+                                BinaryOp::RecordInsert {
                                     metadata,
                                     pending_contracts,
                                     ext_kind,
diff --git a/core/src/eval/operation.rs b/core/src/eval/operation.rs
index ee435cab..3c7b1d13 100644
--- a/core/src/eval/operation.rs
+++ b/core/src/eval/operation.rs
@@ -199,7 +199,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
         }
 
         match u_op {
-            UnaryOp::Ite() => {
+            UnaryOp::IfThenElse => {
                 if let Term::Bool(b) = *t {
                     if self.stack.count_args() >= 2 {
                         let (fst, ..) = self
@@ -225,7 +225,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     ))
                 }
             }
-            UnaryOp::Typeof() => {
+            UnaryOp::Typeof => {
                 let result = match *t {
                     Term::Num(_) => "Number",
                     Term::Bool(_) => "Bool",
@@ -243,7 +243,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     pos_op_inh,
                 )))
             }
-            UnaryOp::BoolAnd() =>
+            UnaryOp::BoolAnd =>
             // The syntax should not allow partially applied boolean operators.
             {
                 if let Some((next, ..)) = self.stack.pop_arg(&self.cache) {
@@ -264,7 +264,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(EvalError::NotEnoughArgs(2, String::from("&&"), pos_op))
                 }
             }
-            UnaryOp::BoolOr() => {
+            UnaryOp::BoolOr => {
                 if let Some((next, ..)) = self.stack.pop_arg(&self.cache) {
                     match &*t {
                         Term::Bool(true) => Ok(Closure::atomic_closure(RichTerm {
@@ -283,7 +283,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(EvalError::NotEnoughArgs(2, String::from("||"), pos_op))
                 }
             }
-            UnaryOp::BoolNot() => {
+            UnaryOp::BoolNot => {
                 if let Term::Bool(b) = *t {
                     Ok(Closure::atomic_closure(RichTerm::new(
                         Term::Bool(!b),
@@ -293,7 +293,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("unary negation", "Bool"))
                 }
             }
-            UnaryOp::Blame() => match_sharedterm!(match (t) {
+            UnaryOp::Blame => match_sharedterm!(match (t) {
                 Term::Lbl(label) => Err(EvalError::BlameError {
                     evaluated_arg: label.get_evaluated_arg(&self.cache),
                     label,
@@ -301,7 +301,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }),
                 _ => Err(mk_type_error!("blame", "Label")),
             }),
-            UnaryOp::Embed(_id) => {
+            UnaryOp::EnumEmbed(_id) => {
                 if let Term::Enum(_) = &*t {
                     Ok(Closure::atomic_closure(RichTerm {
                         term: t,
@@ -364,7 +364,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("match", "Enum", 2))
                 }
             }
-            UnaryOp::ChangePolarity() => match_sharedterm!(match (t) {
+            UnaryOp::LabelFlipPol => match_sharedterm!(match (t) {
                 Term::Lbl(l) => {
                     let mut l = l;
                     l.polarity = l.polarity.flip();
@@ -375,7 +375,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("chng_pol", "Label")),
             }),
-            UnaryOp::Pol() => {
+            UnaryOp::LabelPol => {
                 if let Term::Lbl(l) = &*t {
                     Ok(Closure::atomic_closure(RichTerm::new(
                         l.polarity.into(),
@@ -385,7 +385,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("polarity", "Label"))
                 }
             }
-            UnaryOp::GoDom() => match_sharedterm!(match (t) {
+            UnaryOp::LabelGoDom => match_sharedterm!(match (t) {
                 Term::Lbl(l) => {
                     let mut l = l;
                     l.path.push(ty_path::Elem::Domain);
@@ -396,7 +396,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("go_dom", "Label")),
             }),
-            UnaryOp::GoCodom() => match_sharedterm!(match (t) {
+            UnaryOp::LabelGoCodom => match_sharedterm!(match (t) {
                 Term::Lbl(l) => {
                     let mut l = l;
                     l.path.push(ty_path::Elem::Codomain);
@@ -407,7 +407,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("go_codom", "Label")),
             }),
-            UnaryOp::GoArray() => match_sharedterm!(match (t) {
+            UnaryOp::LabelGoArray => match_sharedterm!(match (t) {
                 Term::Lbl(l) => {
                     let mut l = l;
                     l.path.push(ty_path::Elem::Array);
@@ -418,7 +418,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("go_array", "Label")),
             }),
-            UnaryOp::GoDict() => match_sharedterm!(match (t) {
+            UnaryOp::LabelGoDict => match_sharedterm!(match (t) {
                 Term::Lbl(l) => {
                     let mut l = l;
                     l.path.push(ty_path::Elem::Dict);
@@ -429,7 +429,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("go_dict", "Label")),
             }),
-            UnaryOp::StaticAccess(id) => {
+            UnaryOp::RecordAccess(id) => {
                 if let Term::Record(record) = &*t {
                     // We have to apply potentially pending contracts. Right now, this
                     // means that repeated field access will re-apply the contract again
@@ -474,7 +474,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     ))
                 }
             }
-            UnaryOp::FieldsOf(op_kind) => match_sharedterm!(match (t) {
+            UnaryOp::RecordFields(op_kind) => match_sharedterm!(match (t) {
                 Term::Record(record) => {
                     let fields_as_terms: Array = record
                         .field_names(op_kind)
@@ -489,7 +489,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("fields", "Record")),
             }),
-            UnaryOp::ValuesOf() => match_sharedterm!(match (t) {
+            UnaryOp::RecordValues => match_sharedterm!(match (t) {
                 Term::Record(record) => {
                     let mut values = record
                         .into_iter_without_opts()
@@ -508,7 +508,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("values", "Record")),
             }),
-            UnaryOp::ArrayMap() => {
+            UnaryOp::ArrayMap => {
                 let (f, ..) = self
                     .stack
                     .pop_arg(&self.cache)
@@ -545,7 +545,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     _ => Err(mk_type_error!("map", "Array")),
                 })
             }
-            UnaryOp::ArrayGen() => {
+            UnaryOp::ArrayGen => {
                 let (f, _) = self
                     .stack
                     .pop_arg(&self.cache)
@@ -595,7 +595,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     env: Environment::new(),
                 })
             }
-            UnaryOp::RecordMap() => {
+            UnaryOp::RecordMap => {
                 let (f, ..) = self.stack.pop_arg(&self.cache).ok_or_else(|| {
                     EvalError::NotEnoughArgs(2, String::from("record_map"), pos_op)
                 })?;
@@ -646,12 +646,12 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     _ => Err(mk_type_error!("record_map", "Record", 1)),
                 })
             }
-            UnaryOp::Seq() => self
+            UnaryOp::Seq => self
                 .stack
                 .pop_arg(&self.cache)
                 .map(|(next, ..)| next)
                 .ok_or_else(|| EvalError::NotEnoughArgs(2, String::from("seq"), pos_op)),
-            UnaryOp::DeepSeq() => {
+            UnaryOp::DeepSeq => {
                 // Build a `RichTerm` that forces a given list of terms, and at the end resumes the
                 // evaluation of the argument on the top of the stack.
                 //
@@ -665,9 +665,9 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                         .expect("expected the argument to be a non-empty iterator");
 
                     it.fold(
-                        mk_term::op1(UnaryOp::DeepSeq(), first).with_pos(pos_op_inh),
+                        mk_term::op1(UnaryOp::DeepSeq, first).with_pos(pos_op_inh),
                         |acc, t| {
-                            mk_app!(mk_term::op1(UnaryOp::DeepSeq(), t), acc).with_pos(pos_op_inh)
+                            mk_app!(mk_term::op1(UnaryOp::DeepSeq, t), acc).with_pos(pos_op_inh)
                         },
                     )
                 }
@@ -725,7 +725,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     }
                 }
             }
-            UnaryOp::ArrayLength() => {
+            UnaryOp::ArrayLength => {
                 if let Term::Array(ts, _) = &*t {
                     // A num does not have any free variable so we can drop the environment
                     Ok(Closure {
@@ -736,7 +736,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("length", "Array"))
                 }
             }
-            UnaryOp::ChunksConcat() => {
+            UnaryOp::ChunksConcat => {
                 let StrAccData {
                     mut acc,
                     curr_indent: indent,
@@ -773,7 +773,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                         });
 
                         Ok(Closure {
-                            body: RichTerm::new(Term::Op1(UnaryOp::ChunksConcat(), e), pos_op_inh),
+                            body: RichTerm::new(Term::Op1(UnaryOp::ChunksConcat, e), pos_op_inh),
                             env: env_chunks,
                         })
                     } else {
@@ -795,7 +795,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     ))
                 }
             }
-            UnaryOp::StrTrim() => {
+            UnaryOp::StringTrim => {
                 if let Term::Str(s) = &*t {
                     Ok(Closure::atomic_closure(RichTerm::new(
                         Term::Str(s.trim().into()),
@@ -805,7 +805,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("str_trim", "String"))
                 }
             }
-            UnaryOp::StrChars() => {
+            UnaryOp::StringChars => {
                 if let Term::Str(s) = &*t {
                     let ts = s.characters();
                     Ok(Closure::atomic_closure(RichTerm::new(
@@ -816,7 +816,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("str_chars", "String"))
                 }
             }
-            UnaryOp::StrUppercase() => {
+            UnaryOp::StringUppercase => {
                 if let Term::Str(s) = &*t {
                     Ok(Closure::atomic_closure(RichTerm::new(
                         Term::Str(s.to_uppercase().into()),
@@ -826,7 +826,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("str_uppercase", "String"))
                 }
             }
-            UnaryOp::StrLowercase() => {
+            UnaryOp::StringLowercase => {
                 if let Term::Str(s) = &*t {
                     Ok(Closure::atomic_closure(RichTerm::new(
                         Term::Str(s.to_lowercase().into()),
@@ -836,7 +836,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("string_lowercase", "String"))
                 }
             }
-            UnaryOp::StrLength() => {
+            UnaryOp::StringLength => {
                 if let Term::Str(s) = &*t {
                     let length = s.graphemes(true).count();
                     Ok(Closure::atomic_closure(RichTerm::new(
@@ -847,7 +847,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("str_length", "String"))
                 }
             }
-            UnaryOp::ToStr() => {
+            UnaryOp::ToString => {
                 let result = match_sharedterm!(match (t) {
                     Term::Num(n) => Ok(Term::Str(format!("{}", n.to_sci()).into())),
                     Term::Str(s) => Ok(Term::Str(s)),
@@ -865,7 +865,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
 
                 Ok(Closure::atomic_closure(RichTerm::new(result, pos_op_inh)))
             }
-            UnaryOp::NumFromStr() => {
+            UnaryOp::NumberFromString => {
                 if let Term::Str(s) = &*t {
                     let n = parse_number_sci(s).map_err(|_| {
                         EvalError::Other(
@@ -881,7 +881,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("num_from_str", "String"))
                 }
             }
-            UnaryOp::EnumFromStr() => {
+            UnaryOp::EnumFromString => {
                 if let Term::Str(s) = &*t {
                     Ok(Closure::atomic_closure(RichTerm::new(
                         Term::Enum(LocIdent::from(s)),
@@ -891,7 +891,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("enum_from_str", "String"))
                 }
             }
-            UnaryOp::StrIsMatch() => {
+            UnaryOp::StringIsMatch => {
                 if let Term::Str(s) = &*t {
                     let re = regex::Regex::new(s)
                         .map_err(|err| EvalError::Other(err.to_string(), pos_op))?;
@@ -901,7 +901,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                         param,
                         RichTerm::new(
                             Term::Op1(
-                                UnaryOp::StrIsMatchCompiled(re.into()),
+                                UnaryOp::StringIsMatchCompiled(re.into()),
                                 RichTerm::new(Term::Var(param), pos_op_inh),
                             ),
                             pos_op_inh,
@@ -913,7 +913,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("str_is_match", "String", 1))
                 }
             }
-            UnaryOp::StrFind() => {
+            UnaryOp::StringFind => {
                 if let Term::Str(s) = &*t {
                     let re = regex::Regex::new(s)
                         .map_err(|err| EvalError::Other(err.to_string(), pos_op))?;
@@ -923,7 +923,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                         param,
                         RichTerm::new(
                             Term::Op1(
-                                UnaryOp::StrFindCompiled(re.into()),
+                                UnaryOp::StringFindCompiled(re.into()),
                                 RichTerm::new(Term::Var(param), pos_op_inh),
                             ),
                             pos_op_inh,
@@ -935,7 +935,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("str_find", "String", 1))
                 }
             }
-            UnaryOp::StrFindAll() => {
+            UnaryOp::StringFindAll => {
                 if let Term::Str(s) = &*t {
                     let re = regex::Regex::new(s)
                         .map_err(|err| EvalError::Other(err.to_string(), pos_op))?;
@@ -945,7 +945,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                         param,
                         RichTerm::new(
                             Term::Op1(
-                                UnaryOp::StrFindAllCompiled(re.into()),
+                                UnaryOp::StringFindAllCompiled(re.into()),
                                 RichTerm::new(Term::Var(param), pos_op_inh),
                             ),
                             pos_op_inh,
@@ -957,7 +957,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("str_find_all", "String", 1))
                 }
             }
-            UnaryOp::StrIsMatchCompiled(regex) => {
+            UnaryOp::StringIsMatchCompiled(regex) => {
                 if let Term::Str(s) = &*t {
                     Ok(Closure::atomic_closure(RichTerm::new(
                         s.matches_regex(&regex),
@@ -970,7 +970,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     ))
                 }
             }
-            UnaryOp::StrFindCompiled(regex) => {
+            UnaryOp::StringFindCompiled(regex) => {
                 if let Term::Str(s) = &*t {
                     use crate::term::string::RegexFindResult;
                     let result = match s.find_regex(&regex) {
@@ -1011,7 +1011,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     ))
                 }
             }
-            UnaryOp::StrFindAllCompiled(regex) => {
+            UnaryOp::StringFindAllCompiled(regex) => {
                 if let Term::Str(s) = &*t {
                     let result = Term::Array(
                         Array::from_iter(s.find_all_regex(&regex).map(|found| {
@@ -1049,7 +1049,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     I: Iterator<Item = RichTerm>,
                 {
                     terms
-                        .fold(cont, |acc, t| mk_app!(mk_term::op1(UnaryOp::Seq(), t), acc))
+                        .fold(cont, |acc, t| mk_app!(mk_term::op1(UnaryOp::Seq, t), acc))
                         .with_pos(pos)
                 }
 
@@ -1149,13 +1149,13 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     }),
                 })
             }
-            UnaryOp::RecDefault() => {
+            UnaryOp::RecDefault => {
                 Ok(RecPriority::Bottom.propagate_in_term(&mut self.cache, t, env, pos))
             }
-            UnaryOp::RecForce() => {
+            UnaryOp::RecForce => {
                 Ok(RecPriority::Top.propagate_in_term(&mut self.cache, t, env, pos))
             }
-            UnaryOp::RecordEmptyWithTail() => match_sharedterm!(match (t) {
+            UnaryOp::RecordEmptyWithTail => match_sharedterm!(match (t) {
                 Term::Record(r) => {
                     let mut empty = RecordData::empty();
                     empty.sealed_tail = r.sealed_tail;
@@ -1166,7 +1166,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("record_empty_with_tail", "Record")),
             }),
-            UnaryOp::Trace() => {
+            UnaryOp::Trace => {
                 if let Term::Str(s) = &*t {
                     let _ = writeln!(self.trace, "std.trace: {s}");
                     Ok(())
@@ -1179,7 +1179,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     .map(|(next, ..)| next)
                     .ok_or_else(|| EvalError::NotEnoughArgs(2, String::from("trace"), pos_op))
             }
-            UnaryOp::LabelPushDiag() => {
+            UnaryOp::LabelPushDiag => {
                 match_sharedterm!(match (t) {
                     Term::Lbl(label) => {
                         let mut label = label;
@@ -1193,7 +1193,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 })
             }
             #[cfg(feature = "nix-experimental")]
-            UnaryOp::EvalNix() => {
+            UnaryOp::EvalNix => {
                 if let Term::Str(s) = &*t {
                     let json = nix_ffi::eval_to_json(&String::from(s)).map_err(|e| {
                         EvalError::Other(
@@ -1216,7 +1216,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     ))
                 }
             }
-            UnaryOp::EnumUnwrapVariant() => {
+            UnaryOp::EnumUnwrapVariant => {
                 if let Term::EnumVariant { arg, .. } = &*t {
                     Ok(Closure {
                         body: arg.clone(),
@@ -1226,20 +1226,20 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("enum_unwrap_variant", "Enum variant"))
                 }
             }
-            UnaryOp::EnumGetTag() => match &*t {
+            UnaryOp::EnumGetTag => match &*t {
                 Term::EnumVariant { tag, .. } | Term::Enum(tag) => Ok(Closure::atomic_closure(
                     RichTerm::new(Term::Enum(*tag), pos_op_inh),
                 )),
                 _ => Err(mk_type_error!("enum_get_tag", "Enum")),
             },
-            UnaryOp::EnumIsVariant() => {
+            UnaryOp::EnumIsVariant => {
                 let result = matches!(&*t, Term::EnumVariant { .. });
                 Ok(Closure::atomic_closure(RichTerm::new(
                     Term::Bool(result),
                     pos_op_inh,
                 )))
             }
-            UnaryOp::PatternBranch() => {
+            UnaryOp::PatternBranch => {
                 // The continuation, that we must evaluate in the augmented environment.
                 let (mut cont, _) = self
                     .stack
@@ -1336,7 +1336,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
         }
 
         match b_op {
-            BinaryOp::Seal() => {
+            BinaryOp::Seal => {
                 if let Term::SealingKey(s) = &*t1 {
                     if let Term::Lbl(lbl) = &*t2 {
                         Ok(Closure::atomic_closure(
@@ -1350,7 +1350,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("seal", "SealingKey", 1, t1, pos1))
                 }
             }
-            BinaryOp::Plus() => {
+            BinaryOp::Plus => {
                 if let Term::Num(ref n1) = *t1 {
                     if let Term::Num(ref n2) = *t2 {
                         Ok(Closure::atomic_closure(RichTerm::new(
@@ -1364,7 +1364,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(+)", "Number", 1, t1, pos1))
                 }
             }
-            BinaryOp::Sub() => {
+            BinaryOp::Sub => {
                 if let Term::Num(ref n1) = *t1 {
                     if let Term::Num(ref n2) = *t2 {
                         Ok(Closure::atomic_closure(RichTerm::new(
@@ -1378,7 +1378,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(-)", "Number", 1, t1, pos1))
                 }
             }
-            BinaryOp::Mult() => {
+            BinaryOp::Mult => {
                 if let Term::Num(ref n1) = *t1 {
                     if let Term::Num(ref n2) = *t2 {
                         Ok(Closure::atomic_closure(RichTerm::new(
@@ -1392,7 +1392,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(*)", "Number", 1, t1, pos1))
                 }
             }
-            BinaryOp::Div() => {
+            BinaryOp::Div => {
                 if let Term::Num(ref n1) = *t1 {
                     if let Term::Num(ref n2) = *t2 {
                         if n2 == &Number::ZERO {
@@ -1410,7 +1410,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(/)", "Number", 1, t1, pos1))
                 }
             }
-            BinaryOp::Modulo() => {
+            BinaryOp::Modulo => {
                 let Term::Num(ref n1) = *t1 else {
                     return Err(mk_type_error!("(%)", "Number", 1, t1, pos1));
                 };
@@ -1431,7 +1431,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     pos_op_inh,
                 )))
             }
-            BinaryOp::Pow() => {
+            BinaryOp::Pow => {
                 if let Term::Num(ref n1) = *t1 {
                     if let Term::Num(ref n2) = *t2 {
                         // Malachite's Rationals don't support exponents larger than `u64`. Anyway,
@@ -1474,7 +1474,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("pow", "Number", 1, t1, pos1))
                 }
             }
-            BinaryOp::StrConcat() => {
+            BinaryOp::StringConcat => {
                 if let Term::Str(s1) = &*t1 {
                     if let Term::Str(s2) = &*t2 {
                         let ss: [&str; 2] = [s1, s2];
@@ -1489,7 +1489,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(++)", "String", 1, t1, pos1))
                 }
             }
-            BinaryOp::ApplyContract() => {
+            BinaryOp::ContractApply => {
                 if let Term::Lbl(l) = &*t2 {
                     // Track the contract argument for better error reporting, and push back the
                     // label on the stack, so that it becomes the first argument of the contract.
@@ -1526,7 +1526,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                                 "l",
                                 "x",
                                 mk_opn!(
-                                    NAryOp::MergeContract(),
+                                    NAryOp::MergeContract,
                                     mk_term::var("l"),
                                     mk_term::var("x"),
                                     closurized
@@ -1545,7 +1545,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("apply_contract", "Label", 2, t2, pos2))
                 }
             }
-            BinaryOp::Unseal() => {
+            BinaryOp::Unseal => {
                 if let Term::SealingKey(s1) = &*t1 {
                     // Return a function that either behaves like the identity or
                     // const unwrapped_term
@@ -1566,7 +1566,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("unseal", "SealingKey", 1, t1, pos1))
                 }
             }
-            BinaryOp::Eq() => {
+            BinaryOp::Eq => {
                 let c1 = Closure {
                     body: RichTerm {
                         term: t1,
@@ -1600,7 +1600,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                             let t2 = c2.body.closurize(&mut self.cache, c2.env);
 
                             Ok(Closure {
-                                body: RichTerm::new(Term::Op2(BinaryOp::Eq(), t1, t2), pos_op),
+                                body: RichTerm::new(Term::Op2(BinaryOp::Eq, t1, t2), pos_op),
                                 env: Environment::new(),
                             })
                         }
@@ -1609,13 +1609,13 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                         self.stack.push_eqs(subeqs.into_iter());
 
                         Ok(Closure {
-                            body: RichTerm::new(Term::Op2(BinaryOp::Eq(), t1, t2), pos_op),
+                            body: RichTerm::new(Term::Op2(BinaryOp::Eq, t1, t2), pos_op),
                             env: Environment::new(),
                         })
                     }
                 }
             }
-            BinaryOp::LessThan() => {
+            BinaryOp::LessThan => {
                 if let Term::Num(ref n1) = *t1 {
                     if let Term::Num(ref n2) = *t2 {
                         Ok(Closure::atomic_closure(RichTerm::new(
@@ -1629,7 +1629,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(<)", "Number", 1, t1, pos1))
                 }
             }
-            BinaryOp::LessOrEq() => {
+            BinaryOp::LessOrEq => {
                 if let Term::Num(ref n1) = *t1 {
                     if let Term::Num(ref n2) = *t2 {
                         Ok(Closure::atomic_closure(RichTerm::new(
@@ -1643,7 +1643,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(<=)", "Number", 1, t1, pos1))
                 }
             }
-            BinaryOp::GreaterThan() => {
+            BinaryOp::GreaterThan => {
                 if let Term::Num(ref n1) = *t1 {
                     if let Term::Num(ref n2) = *t2 {
                         Ok(Closure::atomic_closure(RichTerm::new(
@@ -1657,7 +1657,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(>)", "Number", 1, t1, pos1))
                 }
             }
-            BinaryOp::GreaterOrEq() => {
+            BinaryOp::GreaterOrEq => {
                 if let Term::Num(ref n1) = *t1 {
                     if let Term::Num(ref n2) = *t2 {
                         Ok(Closure::atomic_closure(RichTerm::new(
@@ -1671,7 +1671,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(>=)", "Number", 1, t1, pos1))
                 }
             }
-            BinaryOp::GoField() => match_sharedterm!(match (t1) {
+            BinaryOp::LabelGoField => match_sharedterm!(match (t1) {
                 Term::Str(field) => match_sharedterm!(match (t2) {
                     Term::Lbl(l) => {
                         let mut l = l;
@@ -1685,7 +1685,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }),
                 _ => Err(mk_type_error!("go_field", "String", 1, t1, pos1)),
             }),
-            BinaryOp::DynAccess() => {
+            BinaryOp::RecordGet => {
                 match_sharedterm!(match (t1) {
                     Term::Str(id) => {
                         if let Term::Record(record) = &*t2 {
@@ -1745,7 +1745,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     _ => Err(mk_type_error!(".$", "String", 1, t1, pos1)),
                 })
             }
-            BinaryOp::DynExtend {
+            BinaryOp::RecordInsert {
                 metadata,
                 pending_contracts,
                 ext_kind,
@@ -1805,7 +1805,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("record_insert", "String", 1, t1, pos1))
                 }
             }
-            BinaryOp::DynRemove(op_kind) => match_sharedterm!(match (t1) {
+            BinaryOp::RecordRemove(op_kind) => match_sharedterm!(match (t1) {
                 Term::Str(id) => match_sharedterm!(match (t2) {
                     Term::Record(record) => {
                         let mut fields = record.fields;
@@ -1862,7 +1862,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }),
                 _ => Err(mk_type_error!("record_remove", "String", 1, t1, pos1)),
             }),
-            BinaryOp::HasField(op_kind) => match_sharedterm!(match (t1) {
+            BinaryOp::RecordHasField(op_kind) => match_sharedterm!(match (t1) {
                 Term::Str(id) => {
                     if let Term::Record(record) = &*t2 {
                         Ok(Closure::atomic_closure(RichTerm::new(
@@ -1878,7 +1878,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("has_field", "String", 1, t1, pos1)),
             }),
-            BinaryOp::FieldIsDefined(op_kind) => match_sharedterm!(match (t1) {
+            BinaryOp::RecordFieldIsDefined(op_kind) => match_sharedterm!(match (t1) {
                 Term::Str(id) => {
                     if let Term::Record(record) = &*t2 {
                         Ok(Closure::atomic_closure(RichTerm::new(
@@ -1894,7 +1894,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 }
                 _ => Err(mk_type_error!("field_is_defined", "String", 1, t1, pos1)),
             }),
-            BinaryOp::ArrayConcat() => match_sharedterm!(match (t1) {
+            BinaryOp::ArrayConcat => match_sharedterm!(match (t1) {
                 Term::Array(ts1, attrs1) => match_sharedterm!(match (t2) {
                     Term::Array(ts2, attrs2) => {
                         // NOTE: the [eval_closure] function in [eval] should've made sure
@@ -2016,7 +2016,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     Err(mk_type_error!("(@)", "Array", 1, t1, pos1))
                 }
             }),
-            BinaryOp::ArrayElemAt() => match (&*t1, &*t2) {
+            BinaryOp::ArrayAt => match (&*t1, &*t2) {
                 (Term::Array(ts, attrs), Term::Num(n)) => {
                     let Ok(n_as_usize) = usize::try_from(n) else {
                         return Err(EvalError::Other(
@@ -2071,7 +2071,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 MergeMode::Standard(merge_label),
                 &mut self.call_stack,
             ),
-            BinaryOp::Hash() => {
+            BinaryOp::Hash => {
                 let mk_err_fst = |t1| {
                     Err(mk_type_error!(
                         "hash",
@@ -2119,7 +2119,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     mk_err_fst(t1)
                 }
             }
-            BinaryOp::Serialize() => {
+            BinaryOp::Serialize => {
                 let mk_err_fst = |t1| {
                     Err(mk_type_error!(
                         "serialize",
@@ -2159,7 +2159,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     mk_err_fst(t1)
                 }
             }
-            BinaryOp::Deserialize() => {
+            BinaryOp::Deserialize => {
                 let mk_err_fst = |t1| {
                     Err(mk_type_error!(
                         "deserialize",
@@ -2205,7 +2205,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     mk_err_fst(t1)
                 }
             }
-            BinaryOp::StrSplit() => match (&*t1, &*t2) {
+            BinaryOp::StringSplit => match (&*t1, &*t2) {
                 (Term::Str(input), Term::Str(separator)) => {
                     let result = input.split(separator);
                     Ok(Closure::atomic_closure(RichTerm::new(
@@ -2216,7 +2216,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 (Term::Str(_), _) => Err(mk_type_error!("str_split", "String", 2, t2, pos2)),
                 (_, _) => Err(mk_type_error!("str_split", "String", 1, t1, pos1)),
             },
-            BinaryOp::StrContains() => match (&*t1, &*t2) {
+            BinaryOp::StringContains => match (&*t1, &*t2) {
                 (Term::Str(s1), Term::Str(s2)) => {
                     let result = s1.contains(s2.as_str());
                     Ok(Closure::atomic_closure(RichTerm::new(
@@ -2227,7 +2227,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                 (Term::Str(_), _) => Err(mk_type_error!("str_contains", "String", 2, t2, pos2)),
                 (_, _) => Err(mk_type_error!("str_contains", "String", 1, t1, pos1)),
             },
-            BinaryOp::ArrayLazyAppCtr() => {
+            BinaryOp::ContractArrayLazyApp => {
                 let (ctr, _) = self.stack.pop_arg(&self.cache).ok_or_else(|| {
                     EvalError::NotEnoughArgs(3, String::from("array_lazy_app_ctr"), pos_op)
                 })?;
@@ -2267,7 +2267,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     _ => Err(mk_type_error!("array_lazy_app_ctr", "Array", 2, t2, pos2)),
                 })
             }
-            BinaryOp::RecordLazyAppCtr() => {
+            BinaryOp::ContractRecordLazyApp => {
                 // The contract is expected to be of type `String -> Contract`: it takes the name
                 // of the field as a parameter, and returns a contract.
                 let (
@@ -2333,7 +2333,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     _ => Err(mk_type_error!("record_lazy_app_ctr", "Record", 2, t2, pos2)),
                 })
             }
-            BinaryOp::LabelWithMessage() => {
+            BinaryOp::LabelWithMessage => {
                 let t1 = t1.into_owned();
                 let t2 = t2.into_owned();
 
@@ -2362,7 +2362,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     pos_op_inh,
                 )))
             }
-            BinaryOp::LabelWithNotes() => {
+            BinaryOp::LabelWithNotes => {
                 let t2 = t2.into_owned();
 
                 // We need to extract plain strings from a Nickel array, which most likely
@@ -2423,7 +2423,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     pos_op_inh,
                 )))
             }
-            BinaryOp::LabelAppendNote() => {
+            BinaryOp::LabelAppendNote => {
                 let t1 = t1.into_owned();
                 let t2 = t2.into_owned();
 
@@ -2452,7 +2452,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     pos2.into_inherited(),
                 )))
             }
-            BinaryOp::LookupTypeVar() => {
+            BinaryOp::LabelLookupTypeVar => {
                 let t1 = t1.into_owned();
                 let t2 = t2.into_owned();
 
@@ -2499,7 +2499,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
         // Currently, for fixed arity primitive operators, the parser must ensure that they get
         // exactly the right number of argument: if it is not the case, this is a bug, and we panic.
         match n_op {
-            NAryOp::StrReplace() | NAryOp::StrReplaceRegex() => {
+            NAryOp::StringReplace | NAryOp::StringReplaceRegex => {
                 let mut args_wo_env = args
                     .into_iter()
                     .map(|(clos, pos)| (clos.body.term, clos.body.pos, pos));
@@ -2510,7 +2510,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
 
                 match (&*fst, &*snd, &*thd) {
                     (Term::Str(s), Term::Str(from), Term::Str(to)) => {
-                        let result = if let NAryOp::StrReplace() = n_op {
+                        let result = if let NAryOp::StringReplace = n_op {
                             s.replace(from.as_str(), to.as_str())
                         } else {
                             let re = regex::Regex::new(from)
@@ -2556,7 +2556,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     }),
                 }
             }
-            NAryOp::StrSubstr() => {
+            NAryOp::StringSubstr => {
                 let mut args_wo_env = args
                     .into_iter()
                     .map(|(clos, pos)| (clos.body.term, clos.body.pos, pos));
@@ -2604,7 +2604,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     }),
                 }
             }
-            NAryOp::MergeContract() => {
+            NAryOp::MergeContract => {
                 let mut args_iter = args.into_iter();
                 let (
                     Closure {
@@ -2667,7 +2667,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     )),
                 })
             }
-            NAryOp::RecordSealTail() => {
+            NAryOp::RecordSealTail => {
                 let mut args = args.into_iter();
                 let (
                     Closure {
@@ -2783,7 +2783,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     }),
                 }
             }
-            NAryOp::RecordUnsealTail() => {
+            NAryOp::RecordUnsealTail => {
                 let mut args = args.into_iter();
                 let (
                     Closure {
@@ -2863,7 +2863,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     }),
                 }
             }
-            NAryOp::InsertTypeVar() => {
+            NAryOp::LabelInsertTypeVar => {
                 let mut args = args.into_iter();
 
                 let (
@@ -2952,7 +2952,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
                     pos2.into_inherited(),
                 )))
             }
-            NAryOp::ArraySlice() => {
+            NAryOp::ArraySlice => {
                 let mut args = args.into_iter();
 
                 let (
@@ -3524,7 +3524,7 @@ mod tests {
 
     #[test]
     fn ite_operation() {
-        let cont: OperationCont = OperationCont::Op1(UnaryOp::Ite(), TermPos::None);
+        let cont: OperationCont = OperationCont::Op1(UnaryOp::IfThenElse, TermPos::None);
         let mut vm: VirtualMachine<DummyResolver, CacheImpl> =
             VirtualMachine::new(DummyResolver {}, std::io::sink());
 
@@ -3555,7 +3555,7 @@ mod tests {
     #[test]
     fn plus_first_term_operation() {
         let cont = OperationCont::Op2First(
-            BinaryOp::Plus(),
+            BinaryOp::Plus,
             Closure {
                 body: mk_term::integer(6),
                 env: Environment::new(),
@@ -3584,7 +3584,7 @@ mod tests {
         assert_eq!(
             (
                 OperationCont::Op2Second(
-                    BinaryOp::Plus(),
+                    BinaryOp::Plus,
                     Closure {
                         body: mk_term::integer(7),
                         env: Environment::new(),
@@ -3602,7 +3602,7 @@ mod tests {
     #[test]
     fn plus_second_term_operation() {
         let cont: OperationCont = OperationCont::Op2Second(
-            BinaryOp::Plus(),
+            BinaryOp::Plus,
             Closure {
                 body: mk_term::integer(7),
                 env: Environment::new(),
diff --git a/core/src/eval/stack.rs b/core/src/eval/stack.rs
index 4239322c..73e50662 100644
--- a/core/src/eval/stack.rs
+++ b/core/src/eval/stack.rs
@@ -365,7 +365,7 @@ mod tests {
     }
 
     fn some_cont() -> OperationCont {
-        OperationCont::Op1(UnaryOp::Typeof(), TermPos::None)
+        OperationCont::Op1(UnaryOp::Typeof, TermPos::None)
     }
 
     fn some_arg_marker() -> Marker<CacheImpl> {
diff --git a/core/src/parser/grammar.lalrpop b/core/src/parser/grammar.lalrpop
index 706eabf6..0f1cb401 100644
--- a/core/src/parser/grammar.lalrpop
+++ b/core/src/parser/grammar.lalrpop
@@ -245,7 +245,7 @@ UniTerm: UniTerm = {
         UniTerm::from(rt)
     },
     "if" <cond: Term> "then" <t1: Term> "else" <t2: Term> =>
-        UniTerm::from(mk_app!(Term::Op1(UnaryOp::Ite(), cond), t1, t2)),
+        UniTerm::from(mk_app!(Term::Op1(UnaryOp::IfThenElse, cond), t1, t2)),
     <err: Error> => {
         UniTerm::from(err)
     },
@@ -327,7 +327,7 @@ TypeArray: Type = "Array" <t: AsType<Atom>> =>
 
 // A record operation chain, such as `{foo = data}.bar.baz`.
 RecordOperationChain: RichTerm = {
-    <t: AsTerm<Atom>> "." <id: ExtendedIdent> => mk_term::op1(UnaryOp::StaticAccess(id), t).with_pos(id.pos),
+    <t: AsTerm<Atom>> "." <id: ExtendedIdent> => mk_term::op1(UnaryOp::RecordAccess(id), t).with_pos(id.pos),
     <t: AsTerm<Atom>> "." <t_id: WithPos<StrChunks>> => mk_access(t_id, t),
 };
 
@@ -1073,45 +1073,45 @@ ChunkLiteralPart: ChunkLiteralPart = {
 };
 
 UOp: UnaryOp = {
-    "typeof" => UnaryOp::Typeof(),
-    "blame" => UnaryOp::Blame(),
-    "chng_pol" => UnaryOp::ChangePolarity(),
-    "polarity" => UnaryOp::Pol(),
-    "go_dom" => UnaryOp::GoDom(),
-    "go_codom" => UnaryOp::GoCodom(),
-    "go_array" => UnaryOp::GoArray(),
-    "go_dict" => UnaryOp::GoDict(),
-    "embed" <Ident> => UnaryOp::Embed(<>),
-    "map"  => UnaryOp::ArrayMap(),
-    "generate" => UnaryOp::ArrayGen(),
-    "record_map" => UnaryOp::RecordMap(),
-    "seq" => UnaryOp::Seq(),
-    "deep_seq" => UnaryOp::DeepSeq(),
+    "typeof" => UnaryOp::Typeof,
+    "blame" => UnaryOp::Blame,
+    "label/flip_polarity" => UnaryOp::LabelFlipPol,
+    "label/polarity" => UnaryOp::LabelPol,
+    "label/go_dom" => UnaryOp::LabelGoDom,
+    "label/go_codom" => UnaryOp::LabelGoCodom,
+    "label/go_array" => UnaryOp::LabelGoArray,
+    "label/go_dict" => UnaryOp::LabelGoDict,
+    "enum/embed" <Ident> => UnaryOp::EnumEmbed(<>),
+    "array/map"  => UnaryOp::ArrayMap,
+    "array/generate" => UnaryOp::ArrayGen,
+    "record/map" => UnaryOp::RecordMap,
+    "seq" => UnaryOp::Seq,
+    "deep_seq" => UnaryOp::DeepSeq,
     "op force" => UnaryOp::Force{ ignore_not_exported: false },
-    "length" => UnaryOp::ArrayLength(),
-    "fields" => UnaryOp::FieldsOf(RecordOpKind::IgnoreEmptyOpt),
-    "fields_with_opts" => UnaryOp::FieldsOf(RecordOpKind::ConsiderAllFields),
-    "values" => UnaryOp::ValuesOf(),
-    "str_trim" => UnaryOp::StrTrim(),
-    "str_chars" => UnaryOp::StrChars(),
-    "str_uppercase" => UnaryOp::StrUppercase(),
-    "str_lowercase" => UnaryOp::StrLowercase(),
-    "str_length" => UnaryOp::StrLength(),
-    "str_from" => UnaryOp::ToStr(),
-    "num_from" => UnaryOp::NumFromStr(),
-    "enum_from" => UnaryOp::EnumFromStr(),
-    "str_is_match" => UnaryOp::StrIsMatch(),
-    "str_find" => UnaryOp::StrFind(),
-    "str_find_all" => UnaryOp::StrFindAll(),
-    "rec_force_op" => UnaryOp::RecForce(),
-    "rec_default_op" => UnaryOp::RecDefault(),
-    "record_empty_with_tail" => UnaryOp::RecordEmptyWithTail(),
-    "trace" => UnaryOp::Trace(),
-    "label_push_diag" => UnaryOp::LabelPushDiag(),
+    "array/length" => UnaryOp::ArrayLength,
+    "record/fields" => UnaryOp::RecordFields(RecordOpKind::IgnoreEmptyOpt),
+    "record/fields_with_opts" => UnaryOp::RecordFields(RecordOpKind::ConsiderAllFields),
+    "record/values" => UnaryOp::RecordValues,
+    "string/trim" => UnaryOp::StringTrim,
+    "string/chars" => UnaryOp::StringChars,
+    "string/uppercase" => UnaryOp::StringUppercase,
+    "string/lowercase" => UnaryOp::StringLowercase,
+    "string/length" => UnaryOp::StringLength,
+    "to_string" => UnaryOp::ToString,
+    "number/from_string" => UnaryOp::NumberFromString,
+    "enum/from_string" => UnaryOp::EnumFromString,
+    "string/is_match" => UnaryOp::StringIsMatch,
+    "string/find" => UnaryOp::StringFind,
+    "string/find_all" => UnaryOp::StringFindAll,
+    "op rec_force" => UnaryOp::RecForce,
+    "op rec_default" => UnaryOp::RecDefault,
+    "record/empty_with_tail" => UnaryOp::RecordEmptyWithTail,
+    "trace" => UnaryOp::Trace,
+    "label/push_diag" => UnaryOp::LabelPushDiag,
     <l: @L> "eval_nix" <r: @R> =>? {
         #[cfg(feature = "nix-experimental")]
         {
-            Ok(UnaryOp::EvalNix())
+            Ok(UnaryOp::EvalNix)
         }
         #[cfg(not(feature = "nix-experimental"))]
         {
@@ -1123,9 +1123,9 @@ UOp: UnaryOp = {
             })
         }
     },
-    "enum_unwrap_variant" => UnaryOp::EnumUnwrapVariant(),
-    "enum_is_variant" => UnaryOp::EnumIsVariant(),
-    "enum_get_tag" => UnaryOp::EnumGetTag(),
+    "enum/unwrap_variant" => UnaryOp::EnumUnwrapVariant,
+    "enum/is_variant" => UnaryOp::EnumIsVariant,
+    "enum/get_tag" => UnaryOp::EnumGetTag,
 }
 
 PatternGuard: RichTerm = "if" <Term> => <>;
@@ -1138,42 +1138,42 @@ MatchBranch: MatchBranch =
 // highest ones.
 
 InfixBOp2: BinaryOp = {
-    "++" => BinaryOp::StrConcat(),
-    "@" => BinaryOp::ArrayConcat(),
+    "++" => BinaryOp::StringConcat,
+    "@" => BinaryOp::ArrayConcat,
 }
 
 InfixBOp3: BinaryOp = {
-    "*" => BinaryOp::Mult(),
-    "/" => BinaryOp::Div(),
-    "%" => BinaryOp::Modulo(),
+    "*" => BinaryOp::Mult,
+    "/" => BinaryOp::Div,
+    "%" => BinaryOp::Modulo,
 }
 
 InfixBOp4: BinaryOp = {
-    "+" => BinaryOp::Plus(),
-    "-" => BinaryOp::Sub(),
+    "+" => BinaryOp::Plus,
+    "-" => BinaryOp::Sub,
 }
 
 InfixUOp5: UnaryOp = {
-    "!" => UnaryOp::BoolNot(),
+    "!" => UnaryOp::BoolNot,
 }
 
 InfixBOp7: BinaryOp = {
-    "<" => BinaryOp::LessThan(),
-    "<=" => BinaryOp::LessOrEq(),
-    ">" => BinaryOp::GreaterThan(),
-    ">=" => BinaryOp::GreaterOrEq(),
+    "<" => BinaryOp::LessThan,
+    "<=" => BinaryOp::LessOrEq,
+    ">" => BinaryOp::GreaterThan,
+    ">=" => BinaryOp::GreaterOrEq,
 }
 
 InfixBOp8: BinaryOp = {
-    "==" => BinaryOp::Eq(),
+    "==" => BinaryOp::Eq,
 }
 
 InfixLazyBOp9: UnaryOp = {
-    "&&" => UnaryOp::BoolAnd(),
+    "&&" => UnaryOp::BoolAnd,
 }
 
 InfixLazyBOp10: UnaryOp = {
-    "||" => UnaryOp::BoolOr(),
+    "||" => UnaryOp::BoolOr,
 }
 
 InfixBOp: BinaryOp = {
@@ -1209,8 +1209,8 @@ CurriedOp: RichTerm = {
     <l: @L> "!=" <r: @R> =>
         mk_fun!("x1", "x2",
             mk_term::op1(
-                UnaryOp::BoolNot(),
-                Term::Op2(BinaryOp::Eq(),
+                UnaryOp::BoolNot,
+                Term::Op2(BinaryOp::Eq,
                     mk_term::var("x1"),
                     mk_term::var("x2")
                 )
@@ -1228,24 +1228,11 @@ CurriedOp: RichTerm = {
             "x1",
             "x2",
             mk_term::op2(
-                BinaryOp::DynAccess(),
+                BinaryOp::RecordGet,
                 mk_term::var("x2"),
                 mk_term::var("x1"),
             ).with_pos(mk_pos(src_id, l, r))
         ),
-    //<l: @L> "->" <r: @R> =>?
-    //    UniTerm::from(
-    //        mk_fun!("x1", "x2",
-    //            mk_term::op1(
-    //                UnaryOp::BoolNot(),
-    //                Term::Op2(BinaryOp::Eq(),
-    //                    mk_term::var("x2"),
-    //                    mk_term::var("x1")
-    //                )
-    //            )
-    //            .with_pos(mk_pos(src_id, l, r))
-    //        )
-    //    ),
 }
 
 InfixUOpApp<UOp, Expr>: UniTerm =
@@ -1265,7 +1252,7 @@ InfixExpr: UniTerm = {
 
     #[precedence(level="1")]
     "-" <AsTerm<InfixExpr>> =>
-        UniTerm::from(mk_term::op2(BinaryOp::Sub(), Term::Num(Number::ZERO), <>)),
+        UniTerm::from(mk_term::op2(BinaryOp::Sub, Term::Num(Number::ZERO), <>)),
 
     #[precedence(level="2")] #[assoc(side="left")]
     InfixBOpApp<InfixBOp2, InfixExpr, InfixExpr>,
@@ -1293,7 +1280,7 @@ InfixExpr: UniTerm = {
     InfixBOpApp<InfixBOp8, InfixExpr, InfixExpr>,
     <t1: AsTerm<InfixExpr>> "!=" <t2: AsTerm<InfixExpr>> =>
         UniTerm::from(
-            mk_term::op1(UnaryOp::BoolNot(), Term::Op2(BinaryOp::Eq(), t1, t2))
+            mk_term::op1(UnaryOp::BoolNot, Term::Op2(BinaryOp::Eq, t1, t2))
         ),
 
     #[precedence(level="9")] #[assoc(side="left")]
@@ -1308,58 +1295,58 @@ InfixExpr: UniTerm = {
 }
 
 BOpPre: BinaryOp = {
-    "apply_contract" => BinaryOp::ApplyContract(),
-    "array_lazy_app_ctr" => BinaryOp::ArrayLazyAppCtr(),
-    "record_lazy_app_ctr" => BinaryOp::RecordLazyAppCtr(),
-    "unseal" => BinaryOp::Unseal(),
-    "seal" => BinaryOp::Seal(),
-    "go_field" => BinaryOp::GoField(),
-    "has_field" => BinaryOp::HasField(RecordOpKind::IgnoreEmptyOpt),
-    "has_field_with_opts" => BinaryOp::HasField(RecordOpKind::ConsiderAllFields),
-    "field_is_defined" => BinaryOp::FieldIsDefined(RecordOpKind::IgnoreEmptyOpt),
-    "field_is_defined_with_opts" => BinaryOp::FieldIsDefined(RecordOpKind::ConsiderAllFields),
-    "elem_at" => BinaryOp::ArrayElemAt(),
-    "hash" => BinaryOp::Hash(),
-    "serialize" => BinaryOp::Serialize(),
-    "deserialize" => BinaryOp::Deserialize(),
-    "pow" => BinaryOp::Pow(),
-    "str_split" => BinaryOp::StrSplit(),
-    "str_contains" => BinaryOp::StrContains(),
-    "record_insert" => BinaryOp::DynExtend {
+    "contract/apply" => BinaryOp::ContractApply,
+    "contract/array_lazy_app" => BinaryOp::ContractArrayLazyApp,
+    "contract/record_lazy_app" => BinaryOp::ContractRecordLazyApp,
+    "unseal" => BinaryOp::Unseal,
+    "seal" => BinaryOp::Seal,
+    "label/go_field" => BinaryOp::LabelGoField,
+    "record/has_field" => BinaryOp::RecordHasField(RecordOpKind::IgnoreEmptyOpt),
+    "record/has_field_with_opts" => BinaryOp::RecordHasField(RecordOpKind::ConsiderAllFields),
+    "record/field_is_defined" => BinaryOp::RecordFieldIsDefined(RecordOpKind::IgnoreEmptyOpt),
+    "record/field_is_defined_with_opts" => BinaryOp::RecordFieldIsDefined(RecordOpKind::ConsiderAllFields),
+    "array/at" => BinaryOp::ArrayAt,
+    "hash" => BinaryOp::Hash,
+    "serialize" => BinaryOp::Serialize,
+    "deserialize" => BinaryOp::Deserialize,
+    "pow" => BinaryOp::Pow,
+    "string/split" => BinaryOp::StringSplit,
+    "string/contains" => BinaryOp::StringContains,
+    "record/insert" => BinaryOp::RecordInsert {
         ext_kind: RecordExtKind::WithValue,
         metadata: Default::default(),
         pending_contracts: Default::default(),
         op_kind: RecordOpKind::IgnoreEmptyOpt,
     },
-    "record_insert_with_opts" => BinaryOp::DynExtend {
+    "record/insert_with_opts" => BinaryOp::RecordInsert {
         ext_kind: RecordExtKind::WithValue,
         metadata: Default::default(),
         pending_contracts: Default::default(),
         op_kind: RecordOpKind::ConsiderAllFields,
     },
-    "record_remove" => BinaryOp::DynRemove(RecordOpKind::IgnoreEmptyOpt),
-    "record_remove_with_opts" => BinaryOp::DynRemove(RecordOpKind::ConsiderAllFields),
-    "label_with_message" => BinaryOp::LabelWithMessage(),
-    "label_with_notes" => BinaryOp::LabelWithNotes(),
-    "label_append_note" => BinaryOp::LabelAppendNote(),
-    "lookup_type_variable" => BinaryOp::LookupTypeVar(),
+    "record/remove" => BinaryOp::RecordRemove(RecordOpKind::IgnoreEmptyOpt),
+    "record/remove_with_opts" => BinaryOp::RecordRemove(RecordOpKind::ConsiderAllFields),
+    "label/with_message" => BinaryOp::LabelWithMessage,
+    "label/with_notes" => BinaryOp::LabelWithNotes,
+    "label/append_note" => BinaryOp::LabelAppendNote,
+    "label/lookup_type_variable" => BinaryOp::LabelLookupTypeVar,
 }
 
 NOpPre<ArgRule>: UniTerm = {
-    "str_replace" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
-        UniTerm::from(mk_opn!(NAryOp::StrReplace(), t1, t2, t3)),
-    "str_replace_regex" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
-        UniTerm::from(mk_opn!(NAryOp::StrReplaceRegex(), t1, t2, t3)),
-    "str_substr" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
-        UniTerm::from(mk_opn!(NAryOp::StrSubstr(), t1, t2, t3)),
-    "record_seal_tail" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> <t4: ArgRule> =>
-        UniTerm::from(mk_opn!(NAryOp::RecordSealTail(), t1, t2, t3, t4)),
-    "record_unseal_tail" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
-        UniTerm::from(mk_opn!(NAryOp::RecordUnsealTail(), t1, t2, t3)),
-    "insert_type_variable" <key: ArgRule> <pol: ArgRule> <label: ArgRule> =>
-        UniTerm::from(mk_opn!(NAryOp::InsertTypeVar(), key, pol, label)),
-    "array_slice" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
-        UniTerm::from(mk_opn!(NAryOp::ArraySlice(), t1, t2, t3)),
+    "string/replace" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
+        UniTerm::from(mk_opn!(NAryOp::StringReplace, t1, t2, t3)),
+    "string/replace_regex" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
+        UniTerm::from(mk_opn!(NAryOp::StringReplaceRegex, t1, t2, t3)),
+    "string/substr" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
+        UniTerm::from(mk_opn!(NAryOp::StringSubstr, t1, t2, t3)),
+    "record/seal_tail" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> <t4: ArgRule> =>
+        UniTerm::from(mk_opn!(NAryOp::RecordSealTail, t1, t2, t3, t4)),
+    "record/unseal_tail" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
+        UniTerm::from(mk_opn!(NAryOp::RecordUnsealTail, t1, t2, t3)),
+    "label/insert_type_variable" <key: ArgRule> <pol: ArgRule> <label: ArgRule> =>
+        UniTerm::from(mk_opn!(NAryOp::LabelInsertTypeVar, key, pol, label)),
+    "array/slice" <t1: ArgRule> <t2: ArgRule> <t3: ArgRule> =>
+        UniTerm::from(mk_opn!(NAryOp::ArraySlice, t1, t2, t3)),
 }
 
 TypeBuiltin: Type = {
@@ -1521,49 +1508,49 @@ extern {
         "Array" => Token::Normal(NormalToken::Array),
 
         "typeof" => Token::Normal(NormalToken::Typeof),
-        "apply_contract" => Token::Normal(NormalToken::ApplyContract),
-        "array_lazy_app_ctr" => Token::Normal(NormalToken::ArrayLazyAppCtr),
-        "record_lazy_app_ctr" => Token::Normal(NormalToken::RecordLazyAppCtr),
+        "contract/apply" => Token::Normal(NormalToken::ContractApply),
+        "contract/array_lazy_app" => Token::Normal(NormalToken::ContractArrayLazyApp),
+        "contract/record_lazy_app" => Token::Normal(NormalToken::ContractRecordLazyApp),
         "op force" => Token::Normal(NormalToken::OpForce),
         "blame" => Token::Normal(NormalToken::Blame),
-        "chng_pol" => Token::Normal(NormalToken::ChangePol),
-        "polarity" => Token::Normal(NormalToken::Polarity),
-        "go_dom" => Token::Normal(NormalToken::GoDom),
-        "go_codom" => Token::Normal(NormalToken::GoCodom),
-        "go_array" => Token::Normal(NormalToken::GoArray),
-        "go_dict" => Token::Normal(NormalToken::GoDict),
-        "go_field" => Token::Normal(NormalToken::GoField),
+        "label/flip_polarity" => Token::Normal(NormalToken::LabelFlipPol),
+        "label/polarity" => Token::Normal(NormalToken::LabelPol),
+        "label/go_dom" => Token::Normal(NormalToken::LabelGoDom),
+        "label/go_codom" => Token::Normal(NormalToken::LabelGoCodom),
+        "label/go_array" => Token::Normal(NormalToken::LabelGoArray),
+        "label/go_dict" => Token::Normal(NormalToken::LabelGoDict),
+        "label/go_field" => Token::Normal(NormalToken::LabelGoField),
         "seal" => Token::Normal(NormalToken::Seal),
         "unseal" => Token::Normal(NormalToken::Unseal),
-        "embed" => Token::Normal(NormalToken::Embed),
-        "record_map" => Token::Normal(NormalToken::RecordMap),
-        "record_empty_with_tail" => Token::Normal(NormalToken::RecordEmptyWithTail),
-        "record_insert" => Token::Normal(NormalToken::RecordInsert),
-        "record_insert_with_opts" => Token::Normal(NormalToken::RecordInsertWithOpts),
-        "record_remove" => Token::Normal(NormalToken::RecordRemove),
-        "record_remove_with_opts" => Token::Normal(NormalToken::RecordRemoveWithOpts),
-        "record_seal_tail" => Token::Normal(NormalToken::RecordSealTail),
-        "record_unseal_tail" => Token::Normal(NormalToken::RecordUnsealTail),
+        "enum/embed" => Token::Normal(NormalToken::EnumEmbed),
+        "record/map" => Token::Normal(NormalToken::RecordMap),
+        "record/empty_with_tail" => Token::Normal(NormalToken::RecordEmptyWithTail),
+        "record/insert" => Token::Normal(NormalToken::RecordInsert),
+        "record/insert_with_opts" => Token::Normal(NormalToken::RecordInsertWithOpts),
+        "record/remove" => Token::Normal(NormalToken::RecordRemove),
+        "record/remove_with_opts" => Token::Normal(NormalToken::RecordRemoveWithOpts),
+        "record/seal_tail" => Token::Normal(NormalToken::RecordSealTail),
+        "record/unseal_tail" => Token::Normal(NormalToken::RecordUnsealTail),
         "seq" => Token::Normal(NormalToken::Seq),
         "deep_seq" => Token::Normal(NormalToken::DeepSeq),
-        "length" => Token::Normal(NormalToken::Length),
-        "fields" => Token::Normal(NormalToken::FieldsOf),
-        "fields_with_opts" => Token::Normal(NormalToken::FieldsOfWithOpts),
-        "values" => Token::Normal(NormalToken::ValuesOf),
+        "array/length" => Token::Normal(NormalToken::ArrayLength),
+        "record/fields" => Token::Normal(NormalToken::RecordFields),
+        "record/fields_with_opts" => Token::Normal(NormalToken::RecordFieldsWithOpts),
+        "record/values" => Token::Normal(NormalToken::RecordValues),
         "pow" => Token::Normal(NormalToken::Pow),
-        "rec_force_op" => Token::Normal(NormalToken::RecForceOp),
-        "rec_default_op" => Token::Normal(NormalToken::RecDefaultOp),
+        "op rec_force" => Token::Normal(NormalToken::OpRecForce),
+        "op rec_default" => Token::Normal(NormalToken::OpRecDefault),
         "trace" => Token::Normal(NormalToken::Trace),
-        "insert_type_variable" => Token::Normal(NormalToken::InsertTypeVar),
-        "lookup_type_variable" => Token::Normal(NormalToken::LookupTypeVar),
+        "label/insert_type_variable" => Token::Normal(NormalToken::LabelInsertTypeVar),
+        "label/lookup_type_variable" => Token::Normal(NormalToken::LabelLookupTypeVar),
 
-        "has_field" => Token::Normal(NormalToken::HasField),
-        "has_field_with_opts" => Token::Normal(NormalToken::HasFieldWithOpts),
-        "field_is_defined" => Token::Normal(NormalToken::FieldIsDefined),
-        "field_is_defined_with_opts" => Token::Normal(NormalToken::FieldIsDefinedWithOpts),
-        "map" => Token::Normal(NormalToken::Map),
-        "generate" => Token::Normal(NormalToken::ArrayGen),
-        "elem_at" => Token::Normal(NormalToken::ElemAt),
+        "record/has_field" => Token::Normal(NormalToken::RecordHasField),
+        "record/has_field_with_opts" => Token::Normal(NormalToken::RecordHasFieldWithOpts),
+        "record/field_is_defined" => Token::Normal(NormalToken::RecordFieldIsDefined),
+        "record/field_is_defined_with_opts" => Token::Normal(NormalToken::RecordFieldIsDefinedWithOpts),
+        "array/map" => Token::Normal(NormalToken::ArrayMap),
+        "array/generate" => Token::Normal(NormalToken::ArrayGen),
+        "array/at" => Token::Normal(NormalToken::ArrayAt),
 
         "merge" => Token::Normal(NormalToken::Merge),
         "default" => Token::Normal(NormalToken::Default),
@@ -1576,31 +1563,31 @@ extern {
         "hash" => Token::Normal(NormalToken::OpHash),
         "serialize" => Token::Normal(NormalToken::Serialize),
         "deserialize" => Token::Normal(NormalToken::Deserialize),
-        "str_split" => Token::Normal(NormalToken::StrSplit),
-        "str_trim" => Token::Normal(NormalToken::StrTrim),
-        "str_chars" => Token::Normal(NormalToken::StrChars),
-        "str_uppercase" => Token::Normal(NormalToken::StrUppercase),
-        "str_lowercase" => Token::Normal(NormalToken::StrLowercase),
-        "str_contains" => Token::Normal(NormalToken::StrContains),
-        "str_replace" => Token::Normal(NormalToken::StrReplace),
-        "str_replace_regex" => Token::Normal(NormalToken::StrReplaceRegex),
-        "str_is_match" => Token::Normal(NormalToken::StrIsMatch),
-        "str_find" => Token::Normal(NormalToken::StrFind),
-        "str_find_all" => Token::Normal(NormalToken::StrFindAll),
-        "str_length" => Token::Normal(NormalToken::StrLength),
-        "str_substr" => Token::Normal(NormalToken::StrSubstr),
-        "str_from" => Token::Normal(NormalToken::ToStr),
-        "num_from" => Token::Normal(NormalToken::NumFromStr),
-        "enum_from" => Token::Normal(NormalToken::EnumFromStr),
-        "label_with_message" => Token::Normal(NormalToken::LabelWithMessage),
-        "label_with_notes" => Token::Normal(NormalToken::LabelWithNotes),
-        "label_append_note" => Token::Normal(NormalToken::LabelAppendNote),
-        "label_push_diag" => Token::Normal(NormalToken::LabelPushDiag),
-        "array_slice" => Token::Normal(NormalToken::ArraySlice),
+        "string/split" => Token::Normal(NormalToken::StringSplit),
+        "string/trim" => Token::Normal(NormalToken::StringTrim),
+        "string/chars" => Token::Normal(NormalToken::StringChars),
+        "string/uppercase" => Token::Normal(NormalToken::StringUppercase),
+        "string/lowercase" => Token::Normal(NormalToken::StringLowercase),
+        "string/contains" => Token::Normal(NormalToken::StringContains),
+        "string/replace" => Token::Normal(NormalToken::StringReplace),
+        "string/replace_regex" => Token::Normal(NormalToken::StringReplaceRegex),
+        "string/is_match" => Token::Normal(NormalToken::StringIsMatch),
+        "string/find" => Token::Normal(NormalToken::StringFind),
+        "string/find_all" => Token::Normal(NormalToken::StringFindAll),
+        "string/length" => Token::Normal(NormalToken::StringLength),
+        "string/substr" => Token::Normal(NormalToken::StringSubstr),
+        "to_string" => Token::Normal(NormalToken::ToString),
+        "number/from_string" => Token::Normal(NormalToken::NumberFromString),
+        "enum/from_string" => Token::Normal(NormalToken::EnumFromString),
+        "label/with_message" => Token::Normal(NormalToken::LabelWithMessage),
+        "label/with_notes" => Token::Normal(NormalToken::LabelWithNotes),
+        "label/append_note" => Token::Normal(NormalToken::LabelAppendNote),
+        "label/push_diag" => Token::Normal(NormalToken::LabelPushDiag),
+        "array/slice" => Token::Normal(NormalToken::ArraySlice),
         "eval_nix" => Token::Normal(NormalToken::EvalNix),
-        "enum_unwrap_variant" => Token::Normal(NormalToken::EnumUnwrapVariant),
-        "enum_is_variant" => Token::Normal(NormalToken::EnumIsVariant),
-        "enum_get_tag" => Token::Normal(NormalToken::EnumGetTag),
+        "enum/unwrap_variant" => Token::Normal(NormalToken::EnumUnwrapVariant),
+        "enum/is_variant" => Token::Normal(NormalToken::EnumIsVariant),
+        "enum/get_tag" => Token::Normal(NormalToken::EnumGetTag),
         "pattern_branch" => Token::Normal(NormalToken::PatternBranch),
 
         "{" => Token::Normal(NormalToken::LBrace),
diff --git a/core/src/parser/lexer.rs b/core/src/parser/lexer.rs
index 4c679c65..0587038c 100644
--- a/core/src/parser/lexer.rs
+++ b/core/src/parser/lexer.rs
@@ -194,54 +194,54 @@ pub enum NormalToken<'input> {
     #[token("%typeof%")]
     Typeof,
 
-    #[token("%apply_contract%")]
-    ApplyContract,
-    #[token("%array_lazy_app_ctr%")]
-    ArrayLazyAppCtr,
-    #[token("%record_lazy_app_ctr%")]
-    RecordLazyAppCtr,
+    #[token("%contract/apply%")]
+    ContractApply,
+    #[token("%contract/array_lazy_apply%")]
+    ContractArrayLazyApp,
+    #[token("%contract/record_lazy_apply%")]
+    ContractRecordLazyApp,
     #[token("%blame%")]
     Blame,
-    #[token("%chng_pol%")]
-    ChangePol,
-    #[token("%polarity%")]
-    Polarity,
-    #[token("%go_dom%")]
-    GoDom,
-    #[token("%go_codom%")]
-    GoCodom,
-    #[token("%go_field%")]
-    GoField,
-    #[token("%go_array%")]
-    GoArray,
-    #[token("%go_dict%")]
-    GoDict,
-    #[token("%insert_type_variable%")]
-    InsertTypeVar,
-    #[token("%lookup_type_variable%")]
-    LookupTypeVar,
+    #[token("%label/flip_polarity%")]
+    LabelFlipPol,
+    #[token("%label/polarity%")]
+    LabelPol,
+    #[token("%label/go_dom%")]
+    LabelGoDom,
+    #[token("%label/go_codom%")]
+    LabelGoCodom,
+    #[token("%label/go_field%")]
+    LabelGoField,
+    #[token("%label/go_array%")]
+    LabelGoArray,
+    #[token("%label/go_dict%")]
+    LabelGoDict,
+    #[token("%label/insert_type_variable%")]
+    LabelInsertTypeVar,
+    #[token("%label/lookup_type_variable%")]
+    LabelLookupTypeVar,
 
     #[token("%seal%")]
     Seal,
     #[token("%unseal%")]
     Unseal,
-    #[token("%embed%")]
-    Embed,
-    #[token("%record_map%")]
+    #[token("%enum/embed%")]
+    EnumEmbed,
+    #[token("%record/map%")]
     RecordMap,
-    #[token("%record_insert%")]
+    #[token("%record/insert%")]
     RecordInsert,
-    #[token("%record_insert_with_opts%")]
+    #[token("%record/insert_with_opts%")]
     RecordInsertWithOpts,
-    #[token("%record_remove%")]
+    #[token("%record/remove%")]
     RecordRemove,
-    #[token("%record_remove_with_opts%")]
+    #[token("%record/remove_with_opts%")]
     RecordRemoveWithOpts,
-    #[token("%record_empty_with_tail%")]
+    #[token("%record/empty_with_tail%")]
     RecordEmptyWithTail,
-    #[token("%record_seal_tail%")]
+    #[token("%record/seal_tail%")]
     RecordSealTail,
-    #[token("%record_unseal_tail%")]
+    #[token("%record/unseal_tail%")]
     RecordUnsealTail,
     #[token("%seq%")]
     Seq,
@@ -249,38 +249,38 @@ pub enum NormalToken<'input> {
     DeepSeq,
     #[token("%force%")]
     OpForce,
-    #[token("%length%")]
-    Length,
-    #[token("%fields%")]
-    FieldsOf,
-    #[token("%fields_with_opts%")]
-    FieldsOfWithOpts,
-
-    #[token("%values%")]
-    ValuesOf,
+    #[token("%array/length%")]
+    ArrayLength,
+    #[token("%record/fields%")]
+    RecordFields,
+    #[token("%record/fields_with_opts%")]
+    RecordFieldsWithOpts,
+
+    #[token("%record/values%")]
+    RecordValues,
     #[token("%pow%")]
     Pow,
     #[token("%trace%")]
     Trace,
 
-    #[token("%has_field%")]
-    HasField,
-    #[token("%has_field_with_opts%")]
-    HasFieldWithOpts,
-    #[token("%map%")]
-    Map,
-    #[token("%elem_at%")]
-    ElemAt,
-    #[token("%generate%")]
+    #[token("%record/has_field%")]
+    RecordHasField,
+    #[token("%record/has_field_with_opts%")]
+    RecordHasFieldWithOpts,
+    #[token("%array/map%")]
+    ArrayMap,
+    #[token("%array/at%")]
+    ArrayAt,
+    #[token("%array/generate%")]
     ArrayGen,
     #[token("%rec_force%")]
-    RecForceOp,
+    OpRecForce,
     #[token("%rec_default%")]
-    RecDefaultOp,
-    #[token("%field_is_defined%")]
-    FieldIsDefined,
-    #[token("%field_is_defined_with_opts%")]
-    FieldIsDefinedWithOpts,
+    OpRecDefault,
+    #[token("%record/field_is_defined%")]
+    RecordFieldIsDefined,
+    #[token("%record/field_is_defined_with_opts%")]
+    RecordFieldIsDefinedWithOpts,
 
     #[token("merge")]
     Merge,
@@ -303,54 +303,54 @@ pub enum NormalToken<'input> {
     Serialize,
     #[token("%deserialize%")]
     Deserialize,
-    #[token("%str_split%")]
-    StrSplit,
-    #[token("%str_trim%")]
-    StrTrim,
-    #[token("%str_chars%")]
-    StrChars,
-    #[token("%str_uppercase%")]
-    StrUppercase,
-    #[token("%str_lowercase%")]
-    StrLowercase,
-    #[token("%str_contains%")]
-    StrContains,
-    #[token("%str_replace%")]
-    StrReplace,
-    #[token("%str_replace_regex%")]
-    StrReplaceRegex,
-    #[token("%str_is_match%")]
-    StrIsMatch,
-    #[token("%str_find%")]
-    StrFind,
-    #[token("%str_find_all%")]
-    StrFindAll,
-    #[token("%str_length%")]
-    StrLength,
-    #[token("%str_substr%")]
-    StrSubstr,
-    #[token("%to_str%")]
-    ToStr,
-    #[token("%num_from_str%")]
-    NumFromStr,
-    #[token("%enum_from_str%")]
-    EnumFromStr,
-    #[token("%enum_unwrap_variant%")]
+    #[token("%string/split%")]
+    StringSplit,
+    #[token("%string/trim%")]
+    StringTrim,
+    #[token("%string/chars%")]
+    StringChars,
+    #[token("%string/uppercase%")]
+    StringUppercase,
+    #[token("%string/lowercase%")]
+    StringLowercase,
+    #[token("%string/contains%")]
+    StringContains,
+    #[token("%string/replace%")]
+    StringReplace,
+    #[token("%string/replace_regex%")]
+    StringReplaceRegex,
+    #[token("%string/is_match%")]
+    StringIsMatch,
+    #[token("%string/find%")]
+    StringFind,
+    #[token("%string/find_all%")]
+    StringFindAll,
+    #[token("%string/length%")]
+    StringLength,
+    #[token("%string/substr%")]
+    StringSubstr,
+    #[token("%to_string%")]
+    ToString,
+    #[token("%number/from_string%")]
+    NumberFromString,
+    #[token("%enum/from_string%")]
+    EnumFromString,
+    #[token("%enum/unwrap_variant%")]
     EnumUnwrapVariant,
-    #[token("%enum_is_variant%")]
+    #[token("%enum/is_variant%")]
     EnumIsVariant,
-    #[token("%enum_get_tag%")]
+    #[token("%enum/get_tag%")]
     EnumGetTag,
 
-    #[token("%label_with_message%")]
+    #[token("%label/with_message%")]
     LabelWithMessage,
-    #[token("%label_with_notes%")]
+    #[token("%label/with_notes%")]
     LabelWithNotes,
-    #[token("%label_append_note%")]
+    #[token("%label/append_note%")]
     LabelAppendNote,
-    #[token("%label_push_diag%")]
+    #[token("%label/push_diag%")]
     LabelPushDiag,
-    #[token("%array_slice%")]
+    #[token("%array/slice%")]
     ArraySlice,
     #[token("%eval_nix%")]
     EvalNix,
diff --git a/core/src/parser/utils.rs b/core/src/parser/utils.rs
index 7f4e5b47..976dff64 100644
--- a/core/src/parser/utils.rs
+++ b/core/src/parser/utils.rs
@@ -244,7 +244,7 @@ impl InfixOp {
             // We treat `UnaryOp::BoolAnd` and `UnaryOp::BoolOr` separately.
             // They should morally be binary operators, but we represent them as unary
             // operators internally so that their second argument is evaluated lazily.
-            InfixOp::Unary(op @ UnaryOp::BoolAnd()) | InfixOp::Unary(op @ UnaryOp::BoolOr()) => {
+            InfixOp::Unary(op @ UnaryOp::BoolAnd) | InfixOp::Unary(op @ UnaryOp::BoolOr) => {
                 mk_fun!(
                     "x1",
                     "x2",
@@ -432,11 +432,11 @@ impl From<FieldMetadata> for FieldExtAnnot {
 pub fn mk_access(access: RichTerm, root: RichTerm) -> RichTerm {
     if let Some(label) = access.as_ref().try_str_chunk_as_static_str() {
         mk_term::op1(
-            UnaryOp::StaticAccess(LocIdent::new_with_pos(label, access.pos)),
+            UnaryOp::RecordAccess(LocIdent::new_with_pos(label, access.pos)),
             root,
         )
     } else {
-        mk_term::op2(BinaryOp::DynAccess(), access, root)
+        mk_term::op2(BinaryOp::RecordGet, access, root)
     }
 }
 
diff --git a/core/src/pretty.rs b/core/src/pretty.rs
index 52901ed1..7cf4d9cf 100644
--- a/core/src/pretty.rs
+++ b/core/src/pretty.rs
@@ -141,7 +141,7 @@ fn needs_parens_in_type_pos(typ: &Type) -> bool {
                 | Term::FunPattern(..)
                 | Term::Let(..)
                 | Term::LetPattern(..)
-                | Term::Op1(UnaryOp::Ite(), _)
+                | Term::Op1(UnaryOp::IfThenElse, _)
                 | Term::Import(..)
                 | Term::ResolvedImport(..)
         )
@@ -490,8 +490,8 @@ where
     fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> {
         use UnaryOp::*;
         match self {
-            BoolNot() => allocator.text("!"),
-            BoolAnd() | BoolOr() | StaticAccess(_) => {
+            BoolNot => allocator.text("!"),
+            BoolAnd | BoolOr | RecordAccess(_) => {
                 unreachable!(
                     "These are handled specially since they are actually encodings \
                     of binary operators (`BoolAnd` and `BoolOr`) or need special \
@@ -499,9 +499,9 @@ where
                     branch of `Term::pretty`"
                 )
             }
-            Embed(id) => docs![
+            EnumEmbed(id) => docs![
                 allocator,
-                "%embed%",
+                "%enum/embed%",
                 docs![allocator, allocator.line(), id.to_string()].nest(2)
             ],
             op => allocator.text(format!("%{op}%")).append(allocator.space()),
@@ -518,25 +518,25 @@ where
     fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> {
         use BinaryOp::*;
         match self {
-            Plus() => allocator.text("+"),
-            Sub() => allocator.text("-"),
+            Plus => allocator.text("+"),
+            Sub => allocator.text("-"),
 
-            Mult() => allocator.text("*"),
-            Div() => allocator.text("/"),
-            Modulo() => allocator.text("%"),
+            Mult => allocator.text("*"),
+            Div => allocator.text("/"),
+            Modulo => allocator.text("%"),
 
-            Eq() => allocator.text("=="),
-            LessThan() => allocator.text("<"),
-            GreaterThan() => allocator.text(">"),
-            GreaterOrEq() => allocator.text(">="),
-            LessOrEq() => allocator.text("<="),
+            Eq => allocator.text("=="),
+            LessThan => allocator.text("<"),
+            GreaterThan => allocator.text(">"),
+            GreaterOrEq => allocator.text(">="),
+            LessOrEq => allocator.text("<="),
 
             Merge(_) => allocator.text("&"),
 
-            StrConcat() => allocator.text("++"),
-            ArrayConcat() => allocator.text("@"),
+            StringConcat => allocator.text("++"),
+            ArrayConcat => allocator.text("@"),
 
-            DynAccess() => allocator.text("."),
+            RecordGet => allocator.text("."),
 
             op => allocator.as_string(format!("%{op}%")),
         }
@@ -875,28 +875,29 @@ where
             .append(body.pretty(allocator).nest(2))
             .group(),
             App(rt1, rt2) => match rt1.as_ref() {
-                App(iop, t) if matches!(iop.as_ref(), Op1(UnaryOp::Ite(), _)) => match iop.as_ref()
-                {
-                    Op1(UnaryOp::Ite(), i) => docs![
-                        allocator,
-                        "if ",
-                        i,
-                        " then",
-                        docs![allocator, allocator.line(), t].nest(2),
-                        allocator.line(),
-                        "else",
-                        docs![allocator, allocator.line(), rt2].nest(2)
-                    ]
-                    .group(),
-                    _ => unreachable!(),
-                },
-                Op1(op @ (UnaryOp::BoolAnd() | UnaryOp::BoolOr()), rt1) => docs![
+                App(iop, t) if matches!(iop.as_ref(), Op1(UnaryOp::IfThenElse, _)) => {
+                    match iop.as_ref() {
+                        Op1(UnaryOp::IfThenElse, i) => docs![
+                            allocator,
+                            "if ",
+                            i,
+                            " then",
+                            docs![allocator, allocator.line(), t].nest(2),
+                            allocator.line(),
+                            "else",
+                            docs![allocator, allocator.line(), rt2].nest(2)
+                        ]
+                        .group(),
+                        _ => unreachable!(),
+                    }
+                }
+                Op1(op @ (UnaryOp::BoolAnd | UnaryOp::BoolOr), rt1) => docs![
                     allocator,
                     allocator.atom(rt1),
                     allocator.line(),
                     match op {
-                        UnaryOp::BoolAnd() => "&& ",
-                        UnaryOp::BoolOr() => "|| ",
+                        UnaryOp::BoolAnd => "&& ",
+                        UnaryOp::BoolOr => "|| ",
                         _ => unreachable!(),
                     },
                     allocator.atom(rt2)
@@ -967,12 +968,12 @@ where
                 .group()
             }
 
-            Op1(UnaryOp::StaticAccess(id), rt) => {
+            Op1(UnaryOp::RecordAccess(id), rt) => {
                 docs![allocator, allocator.atom(rt), ".", ident_quoted(id)]
             }
-            Op1(UnaryOp::BoolNot(), rt) => docs![allocator, "!", allocator.atom(rt)],
+            Op1(UnaryOp::BoolNot, rt) => docs![allocator, "!", allocator.atom(rt)],
 
-            Op1(UnaryOp::BoolAnd() | UnaryOp::BoolOr() | UnaryOp::Ite(), _) => unreachable!(),
+            Op1(UnaryOp::BoolAnd | UnaryOp::BoolOr | UnaryOp::IfThenElse, _) => unreachable!(),
             Op1(op, rt) => match op.pos() {
                 OpPos::Prefix => docs![
                     allocator,
@@ -984,12 +985,12 @@ where
                     panic!("pretty print is not implemented for {op:?}")
                 }
             },
-            Op2(BinaryOp::DynAccess(), rtl, rtr) => {
+            Op2(BinaryOp::RecordGet, rtl, rtr) => {
                 docs![allocator, rtr, ".", rtl]
             }
             Op2(op, rtl, rtr) => docs![
                 allocator,
-                if (&BinaryOp::Sub(), &Num(Number::ZERO)) == (op, rtl.as_ref()) {
+                if (&BinaryOp::Sub, &Num(Number::ZERO)) == (op, rtl.as_ref()) {
                     allocator.text("-")
                 } else if op.pos() == OpPos::Prefix {
                     op.pretty(allocator).append(
@@ -1436,9 +1437,9 @@ mod tests {
     #[test]
     fn pretty_opn() {
         assert_long_short_term(
-            "%str_replace% string pattern replace",
+            "%string/replace% string pattern replace",
             indoc! {"
-                %str_replace%
+                %string/replace%
                   string
                   pattern
                   replace"
@@ -1456,9 +1457,9 @@ mod tests {
             },
         );
         assert_long_short_term(
-            "%str_split% string sep",
+            "%string/split% string sep",
             indoc! {"
-                %str_split%
+                %string/split%
                   string
                   sep"
             },
@@ -1508,9 +1509,9 @@ mod tests {
             },
         );
         assert_long_short_term(
-            "%embed% foo bar",
+            "%enum/embed% foo bar",
             indoc! {"
-                %embed%
+                %enum/embed%
                   foo
                   bar"
             },
diff --git a/core/src/serialize.rs b/core/src/serialize.rs
index d9f7d4d3..c56bfdfa 100644
--- a/core/src/serialize.rs
+++ b/core/src/serialize.rs
@@ -422,7 +422,7 @@ mod tests {
     fn assert_nickel_eq(term: RichTerm, expected: RichTerm) {
         assert_eq!(
             VirtualMachine::<_, CacheImpl>::new(DummyResolver {}, std::io::stderr())
-                .eval(mk_term::op2(BinaryOp::Eq(), term, expected))
+                .eval(mk_term::op2(BinaryOp::Eq, term, expected))
                 .map(Term::from),
             Ok(Term::Bool(true))
         )
diff --git a/core/src/term/mod.rs b/core/src/term/mod.rs
index 940b15fa..1ecdb31e 100644
--- a/core/src/term/mod.rs
+++ b/core/src/term/mod.rs
@@ -403,7 +403,7 @@ impl RuntimeContract {
 
         mk_app!(
             make::op2(
-                BinaryOp::ApplyContract(),
+                BinaryOp::ContractApply,
                 self.contract,
                 Term::Lbl(self.label)
             )
@@ -1043,8 +1043,8 @@ impl Term {
             | Term::Var(..)
             | Term::SealingKey(..)
             | Term::ForeignId(..)
-            | Term::Op1(UnaryOp::StaticAccess(_), _)
-            | Term::Op2(BinaryOp::DynAccess(), _, _)
+            | Term::Op1(UnaryOp::RecordAccess(_), _)
+            | Term::Op2(BinaryOp::RecordGet, _, _)
             // Those special cases aren't really atoms, but mustn't be parenthesized because they
             // are really functions taking additional non-strict arguments and printed as "partial"
             // infix operators.
@@ -1053,8 +1053,8 @@ impl Term {
             // must never be parenthesized, such as in `(x ||)`.
             //
             // We might want a more robust mechanism for pretty printing such operators.
-            | Term::Op1(UnaryOp::BoolAnd(), _)
-            | Term::Op1(UnaryOp::BoolOr(), _) => true,
+            | Term::Op1(UnaryOp::BoolAnd, _)
+            | Term::Op1(UnaryOp::BoolOr, _) => true,
             // A number with a minus sign as a prefix isn't a proper atom
             Term::Num(n) if *n >= 0 => true,
             Term::Let(..)
@@ -1161,32 +1161,32 @@ impl Deref for SharedTerm {
 #[derive(Clone, Debug, PartialEq)]
 pub enum UnaryOp {
     /// If-then-else.
-    Ite(),
+    IfThenElse,
 
     /// Return an enum tag representing the type of the term.
-    Typeof(),
+    Typeof,
 
     // Boolean AND and OR operator are encoded as unary operators so that they can be lazy in their
     // second argument.
     /// Boolean AND operator.
-    BoolAnd(),
+    BoolAnd,
 
     /// Boolean OR operator.
-    BoolOr(),
+    BoolOr,
 
     /// Boolean NOT operator.
-    BoolNot(),
+    BoolNot,
 
     /// Raise a blame, which stops the execution and prints an error according to the label
     /// argument.
-    Blame(),
+    Blame,
 
     /// Typecast an enum to a larger enum type.
     ///
     /// `Embed` is used to upcast enums. For example, if a value `x` has enum type `a | b`, then
     /// `embed c x` will have enum type `a | b | c`. It only affects typechecking as at runtime
     /// `embed someId` act like the identity.
-    Embed(LocIdent),
+    EnumEmbed(LocIdent),
 
     /// A specialized primop for match when all patterns are enum tags. In that case, instead of
     /// compiling to a generic sequence of if-then-else, we can be much more efficient by indexing
@@ -1194,26 +1194,26 @@ pub enum UnaryOp {
     /// tags to the corresponding branches, and the default case when `has_default` is `true`.
     TagsOnlyMatch { has_default: bool },
 
-    /// Static access to a record field.
+    /// Static record access.
     ///
     /// Static means that the field identifier is a statically known string inside the source.
-    StaticAccess(LocIdent),
+    RecordAccess(LocIdent),
 
     /// Map a function on each element of an array.
-    ArrayMap(),
+    ArrayMap,
 
     /// Map a function on a record.
     ///
     /// The mapped function must take two arguments, the name of the field as a string, and the
     /// content of the field. `RecordMap` then replaces the content of each field by the result of
     /// the function: i.e., `recordMap f {a=2;}` evaluates to `{a=(f "a" 2);}`.
-    RecordMap(),
+    RecordMap,
 
     /// Inverse the polarity of a label.
-    ChangePolarity(),
+    LabelFlipPol,
 
     /// Get the polarity of a label.
-    Pol(),
+    LabelPol,
 
     /// Go to the domain in the type path of a label.
     ///
@@ -1234,101 +1234,101 @@ pub enum UnaryOp {
     ///  ^^^ new type path
     /// ------------------- original type
     /// ```
-    GoDom(),
+    LabelGoDom,
 
     /// Go to the codomain in the type path of a label.
     ///
     /// See `GoDom`.
-    GoCodom(),
+    LabelGoCodom,
 
     /// Go to the array in the type path of a label.
     ///
     /// See `GoDom`.
-    GoArray(),
+    LabelGoArray,
 
     /// Go to the type ascribed to every field in a dictionary.
     ///
     /// See `GoDom`.
-    GoDict(),
+    LabelGoDict,
 
     /// Force the evaluation of its argument and proceed with the second.
-    Seq(),
+    Seq,
 
     /// Recursively force the evaluation of its first argument then returns the second.
     ///
     /// Recursive here means that the evaluation does not stop at a WHNF, but the content of arrays
     /// and records is also recursively forced.
-    DeepSeq(),
+    DeepSeq,
 
     /// Return the length of an array.
-    ArrayLength(),
+    ArrayLength,
 
     /// Generate an array of a given length by mapping a `Num -> Num` function onto `[1,..,n]`.
-    ArrayGen(),
+    ArrayGen,
 
     /// Generated by the evaluation of a string with interpolated expressions. `ChunksConcat`
     /// applied to the current chunk to evaluate. As additional state, it uses a string
     /// accumulator, the indentation of the chunk being evaluated, and the remaining chunks to be
     /// evaluated, all stored on the stack.
-    ChunksConcat(),
+    ChunksConcat,
 
     /// Return the names of the fields of a record as a string array.
-    FieldsOf(RecordOpKind),
+    RecordFields(RecordOpKind),
 
     /// Return the values of the fields of a record as an array.
-    ValuesOf(),
+    RecordValues,
 
     /// Remove heading and trailing spaces from a string.
-    StrTrim(),
+    StringTrim,
 
     /// Return the array of characters of a string.
-    StrChars(),
+    StringChars,
 
     /// Transform a string to uppercase.
-    StrUppercase(),
+    StringUppercase,
 
     /// Transform a string to lowercase.
-    StrLowercase(),
+    StringLowercase,
 
     /// Return the length of a string.
-    StrLength(),
+    StringLength,
 
     /// Transform a data to a string.
-    ToStr(),
+    ToString,
 
     /// Transform a string to a number.
-    NumFromStr(),
+    NumberFromString,
 
     /// Transform a string to an enum.
-    EnumFromStr(),
+    EnumFromString,
 
     /// Test if a regex matches a string.
-    /// Like [`UnaryOp::StrFind`], this is a unary operator because we would like a way to share the
-    /// same "compiled regex" for many matching calls. This is done by returning functions
-    /// wrapping [`UnaryOp::StrIsMatchCompiled`] and [`UnaryOp::StrFindCompiled`]
-    StrIsMatch(),
+    /// Like [`UnaryOp::StringFind`], this is a unary operator because we would like a way to share
+    /// the same "compiled regex" for many matching calls. This is done by returning functions
+    /// wrapping [`UnaryOp::StringIsMatchCompiled`] and [`UnaryOp::StringFindCompiled`]
+    StringIsMatch,
 
     /// Match a regex on a string, and returns the captured groups together, the index of the
     /// match, etc.
-    StrFind(),
+    StringFind,
 
     /// Returns all matches of a regex on a string, as an array of matches. Each
     /// match contains the match groups, the starting index of the match and the
     /// matched string.
-    StrFindAll(),
+    StringFindAll,
 
-    /// Version of [`UnaryOp::StrIsMatch`] which remembers the compiled regex.
-    StrIsMatchCompiled(CompiledRegex),
+    /// Version of [`UnaryOp::StringIsMatch`] which remembers the compiled regex.
+    StringIsMatchCompiled(CompiledRegex),
 
-    /// Version of [`UnaryOp::StrFind`] which remembers the compiled regex.
-    StrFindCompiled(CompiledRegex),
+    /// Version of [`UnaryOp::StringFind`] which remembers the compiled regex.
+    StringFindCompiled(CompiledRegex),
 
-    /// Version of [`UnaryOp::StrFindAll`] which remembers the compiled regex.
-    StrFindAllCompiled(CompiledRegex),
+    /// Version of [`UnaryOp::StringFindAll`] which remembers the compiled regex.
+    StringFindAllCompiled(CompiledRegex),
 
     /// Force full evaluation of a term and return it.
     ///
-    /// This was added in the context of [`BinaryOp::ArrayLazyAppCtr`], in particular to make
+    /// This was added in the context of [`BinaryOp::ContractArrayLazyApp`], in particular to make
     /// serialization work with lazy array contracts.
     ///
     /// # `Force` vs. `DeepSeq`
@@ -1383,14 +1383,14 @@ pub enum UnaryOp {
     ///
     /// If a value has any explicit priority annotation, then the original annotation takes
     /// precedence and the default doesn't apply.
-    RecDefault(),
+    RecDefault,
 
     /// Recursive force priority operator. Similar to [UnaryOp::RecDefault], but propagate the
     /// `force` annotation.
     ///
     /// As opposed to `RecDefault`, the `force` takes precedence and erase any prior explicit
     /// priority annotation.
-    RecForce(),
+    RecForce,
 
     /// Creates an "empty" record with the sealed tail of its [`Term::Record`]
     /// argument.
@@ -1398,29 +1398,29 @@ pub enum UnaryOp {
     /// Used in the `$record` contract implementation to ensure that we can
     /// define a `field_diff` function that preserves the sealed polymorphic
     /// tail of its argument.
-    RecordEmptyWithTail(),
+    RecordEmptyWithTail,
 
     /// Print a message when encountered during evaluation and proceed with the evaluation of the
     /// argument on the top of the stack. Operationally the same as the identity function
-    Trace(),
+    Trace,
 
     /// Push a new, fresh diagnostic on the diagnostic stack of a contract label. This has the
     /// effect of saving the current diagnostic, as following calls to primop that modifies the
     /// label's current diagnostic will modify the fresh one, istead of the one being stacked.
     /// This primop shouldn't be used directly by user a priori, but is used internally during e.g.
     /// contract application.
-    LabelPushDiag(),
+    LabelPushDiag,
 
     /// Evaluate a string of nix code into a resulting nickel value. Currently completely (strictly) evaluates the nix code, and must result in a value serializable into JSON.
     #[cfg(feature = "nix-experimental")]
-    EvalNix(),
+    EvalNix,
 
     /// Unwrap the variant from an enum: `%unwrap_enum_variant% ('Foo t) := t`
-    EnumUnwrapVariant(),
+    EnumUnwrapVariant,
     /// Return true if the given parameter is an enum variant.
-    EnumIsVariant(),
+    EnumIsVariant,
     /// Extract the tag from an enum tag or an enum variant.
-    EnumGetTag(),
+    EnumGetTag,
 
     /// Take a record representing bindings to be added to the local environment and proceed to
     /// evaluate a pattern branch given as a second argument (which isn't a proper primop argument
@@ -1434,67 +1434,67 @@ pub enum UnaryOp {
     /// the same kind of constraints when updating record fields with the recursive environment of
     /// a record, for example). This is why the name tries to make it clear that it shouldn't be
     /// used blindly for something else.
-    PatternBranch(),
+    PatternBranch,
 }
 
 impl fmt::Display for UnaryOp {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use UnaryOp::*;
         match self {
-            Ite() => write!(f, "if_then_else"),
-            Typeof() => write!(f, "typeof"),
-            BoolAnd() => write!(f, "bool_and"),
-            BoolOr() => write!(f, "bool_or"),
-            BoolNot() => write!(f, "bool_not"),
-            Blame() => write!(f, "blame"),
-            Embed(_) => write!(f, "embed"),
+            IfThenElse => write!(f, "if_then_else"),
+            Typeof => write!(f, "typeof"),
+            BoolAnd => write!(f, "bool/and"),
+            BoolOr => write!(f, "bool/or"),
+            BoolNot => write!(f, "bool/not"),
+            Blame => write!(f, "blame"),
+            EnumEmbed(_) => write!(f, "enum/embed"),
             TagsOnlyMatch { .. } => write!(f, "match"),
-            StaticAccess(_) => write!(f, "static_access"),
-            ArrayMap() => write!(f, "map"),
-            RecordMap() => write!(f, "record_map"),
-            ChangePolarity() => write!(f, "chng_pol"),
-            Pol() => write!(f, "polarity"),
-            GoDom() => write!(f, "go_dom"),
-            GoCodom() => write!(f, "go_codom"),
-            GoArray() => write!(f, "go_array"),
-            GoDict() => write!(f, "go_dict"),
-            Seq() => write!(f, "seq"),
-            DeepSeq() => write!(f, "deep_seq"),
-            ArrayLength() => write!(f, "length"),
-            ArrayGen() => write!(f, "generate"),
-            ChunksConcat() => write!(f, "chunks_concat"),
-            FieldsOf(RecordOpKind::IgnoreEmptyOpt) => write!(f, "fields"),
-            FieldsOf(RecordOpKind::ConsiderAllFields) => write!(f, "fields_with_opts"),
-            ValuesOf() => write!(f, "values"),
-            StrTrim() => write!(f, "str_trim"),
-            StrChars() => write!(f, "str_chars"),
-            StrUppercase() => write!(f, "str_uppercase"),
-            StrLowercase() => write!(f, "str_lowercase"),
-            StrLength() => write!(f, "str_length"),
-            ToStr() => write!(f, "to_str"),
-            NumFromStr() => write!(f, "num_from_str"),
-            EnumFromStr() => write!(f, "enum_from_str"),
-            StrIsMatch() => write!(f, "str_is_match"),
-            StrFind() => write!(f, "str_find"),
-            StrFindAll() => write!(f, "str_find_all"),
-            StrIsMatchCompiled(_) => write!(f, "str_is_match_compiled"),
-            StrFindCompiled(_) => write!(f, "str_find_compiled"),
-            StrFindAllCompiled(_) => write!(f, "str_find_all_compiled"),
+            RecordAccess(_) => write!(f, "record/access"),
+            ArrayMap => write!(f, "array/map"),
+            RecordMap => write!(f, "record/map"),
+            LabelFlipPol => write!(f, "label/flip_polarity"),
+            LabelPol => write!(f, "label/polarity"),
+            LabelGoDom => write!(f, "label/go_dom"),
+            LabelGoCodom => write!(f, "label/go_codom"),
+            LabelGoArray => write!(f, "label/go_array"),
+            LabelGoDict => write!(f, "label/go_dict"),
+            Seq => write!(f, "seq"),
+            DeepSeq => write!(f, "deep_seq"),
+            ArrayLength => write!(f, "array/length"),
+            ArrayGen => write!(f, "array/generate"),
+            ChunksConcat => write!(f, "chunks_concat"),
+            RecordFields(RecordOpKind::IgnoreEmptyOpt) => write!(f, "record/fields"),
+            RecordFields(RecordOpKind::ConsiderAllFields) => write!(f, "record/fields_with_opts"),
+            RecordValues => write!(f, "record/values"),
+            StringTrim => write!(f, "string/trim"),
+            StringChars => write!(f, "string/chars"),
+            StringUppercase => write!(f, "string/uppercase"),
+            StringLowercase => write!(f, "string/lowercase"),
+            StringLength => write!(f, "string/length"),
+            ToString => write!(f, "to_string"),
+            NumberFromString => write!(f, "number/from_string"),
+            EnumFromString => write!(f, "enum/from_string"),
+            StringIsMatch => write!(f, "string/is_match"),
+            StringFind => write!(f, "string/find"),
+            StringFindAll => write!(f, "string/find_all"),
+            StringIsMatchCompiled(_) => write!(f, "string/is_match_compiled"),
+            StringFindCompiled(_) => write!(f, "string/find_compiled"),
+            StringFindAllCompiled(_) => write!(f, "string/find_all_compiled"),
             Force { .. } => write!(f, "force"),
-            RecDefault() => write!(f, "rec_default"),
-            RecForce() => write!(f, "rec_force"),
-            RecordEmptyWithTail() => write!(f, "record_empty_with_tail"),
-            Trace() => write!(f, "trace"),
-            LabelPushDiag() => write!(f, "label_push_diag"),
+            RecDefault => write!(f, "rec_default"),
+            RecForce => write!(f, "rec_force"),
+            RecordEmptyWithTail => write!(f, "record/empty_with_tail"),
+            Trace => write!(f, "trace"),
+            LabelPushDiag => write!(f, "label/push_diag"),
 
             #[cfg(feature = "nix-experimental")]
-            EvalNix() => write!(f, "eval_nix"),
+            EvalNix => write!(f, "eval_nix"),
 
-            EnumUnwrapVariant() => write!(f, "enum_unwrap_variant"),
-            EnumIsVariant() => write!(f, "enum_is_variant"),
-            EnumGetTag() => write!(f, "enum_get_tag"),
+            EnumUnwrapVariant => write!(f, "enum/unwrap_variant"),
+            EnumIsVariant => write!(f, "enum/is_variant"),
+            EnumGetTag => write!(f, "enum/get_tag"),
 
-            PatternBranch() => write!(f, "with_env"),
+            PatternBranch => write!(f, "pattern_branch"),
         }
     }
 }
@@ -1539,8 +1539,8 @@ impl UnaryOp {
     pub fn pos(&self) -> OpPos {
         use UnaryOp::*;
         match self {
-            BoolAnd() | BoolOr() | StaticAccess(_) => OpPos::Postfix,
-            Ite() => OpPos::Special,
+            BoolAnd | BoolOr | RecordAccess(_) => OpPos::Postfix,
+            IfThenElse => OpPos::Special,
             _ => OpPos::Prefix,
         }
     }
@@ -1571,63 +1571,63 @@ pub enum RecordOpKind {
 #[derive(Clone, Debug, PartialEq)]
 pub enum BinaryOp {
     /// Addition of numerals.
-    Plus(),
+    Plus,
 
     /// Subtraction of numerals.
-    Sub(),
+    Sub,
 
     /// Multiplication of numerals.
-    Mult(),
+    Mult,
 
     /// Floating-point division of numerals.
-    Div(),
+    Div,
 
     /// Modulo of numerals.
-    Modulo(),
+    Modulo,
 
     /// Raise a number to a power.
-    Pow(),
+    Pow,
 
     /// Concatenation of strings.
-    StrConcat(),
+    StringConcat,
 
     /// Polymorphic equality.
-    Eq(),
+    Eq,
 
     /// Strictly less than comparison operator.
-    LessThan(),
+    LessThan,
 
     /// Less than or equal comparison operator.
-    LessOrEq(),
+    LessOrEq,
 
     /// Strictly greater than comparison operator.
-    GreaterThan(),
+    GreaterThan,
 
     /// Greater than or equal comparison operator.
-    GreaterOrEq(),
+    GreaterOrEq,
 
     /// Apply a contract to a label and a value. The value is is stored on the stack unevaluated,
     /// while the contract and the label are the strict arguments to this operator. `ApplyContract`
     /// also accepts contracts as records, which are translated to a function that merge said
     /// contract with its argument. Finally, this operator marks the location of the contract
     /// argument on the stack for better error reporting.
-    ApplyContract(),
+    ContractApply,
 
     /// Unseal a sealed term.
     ///
     /// See [`BinaryOp::Seal`].
-    Unseal(),
+    Unseal,
 
     /// Go to a specific field in the type path of a label.
     ///
-    /// See `GoDom`.
-    GoField(),
+    /// See `LabelGoDom`.
+    LabelGoField,
 
     /// Extend a record with a dynamic field.
     ///
     /// Dynamic means that the field name may be an expression instead of a statically known
-    /// string. `DynExtend` tries to evaluate this name to a string, and in case of success, add a
-    /// field with this name to the given record with the expression on top of the stack as
+    /// string. `RecordExtend` tries to evaluate this name to a string, and in case of success, add
+    /// a field with this name to the given record with the expression on top of the stack as
     /// content.
     ///
     /// The field may have been defined with attached metadata, pending contracts and may or may
@@ -1635,30 +1635,31 @@ pub enum BinaryOp {
     /// aren't first class values, at least at the time of writing), so for now we attach it
     /// directly to the extend primop. This isn't ideal, and in the future we may want to have a
     /// more principled primop.
-    DynExtend {
+    RecordInsert {
         metadata: FieldMetadata,
         pending_contracts: Vec<RuntimeContract>,
         ext_kind: RecordExtKind,
         op_kind: RecordOpKind,
     },
 
-    /// Remove a field from a record. The field name is given as an arbitrary Nickel expression.
-    DynRemove(RecordOpKind),
+    /// Remove a field from a record. The field name is given as an argument.
+    RecordRemove(RecordOpKind),
 
-    /// Access the field of record. The field name is given as an arbitrary Nickel expression.
-    DynAccess(),
+    /// Dynamically access a field of record. The field name is given as an argument which should
+    /// evaluate to a string.
+    RecordGet,
 
     /// Test if a record has a specific field.
-    HasField(RecordOpKind),
+    RecordHasField(RecordOpKind),
 
     /// Test if the field of a record exists and has a definition.
-    FieldIsDefined(RecordOpKind),
+    RecordFieldIsDefined(RecordOpKind),
 
     /// Concatenate two arrays.
-    ArrayConcat(),
+    ArrayConcat,
 
     /// Access the n-th element of an array.
-    ArrayElemAt(),
+    ArrayAt,
 
     /// The merge operator (see [crate::eval::merge]). `Merge` is parametrized by a
     /// [crate::label::MergeLabel], which carries additional information for error-reporting
@@ -1666,51 +1667,51 @@ pub enum BinaryOp {
     Merge(MergeLabel),
 
     /// Hash a string.
-    Hash(),
+    Hash,
 
     /// Serialize a value to a string.
-    Serialize(),
+    Serialize,
 
     /// Deserialize a string to a value.
-    Deserialize(),
+    Deserialize,
 
     /// Split a string into an array.
-    StrSplit(),
+    StringSplit,
 
     /// Determine if a string is a substring of another one.
-    StrContains(),
+    StringContains,
 
     /// Seal a term with a sealing key (see [`Term::Sealed`]).
-    Seal(),
+    Seal,
 
     /// Lazily apply a contract to an Array.
     /// This simply inserts a contract into the array attributes.
-    ArrayLazyAppCtr(),
+    ContractArrayLazyApp,
 
     /// Lazily map contracts over a record. The arguments are a label and a function which takes
     /// the name of the field as a parameter and returns the corresponding contract.
-    RecordLazyAppCtr(),
+    ContractRecordLazyApp,
 
     /// Set the message of the current diagnostic of a label.
-    LabelWithMessage(),
+    LabelWithMessage,
 
     /// Set the notes of the current diagnostic of a label.
-    LabelWithNotes(),
+    LabelWithNotes,
 
     /// Append a note to the current diagnostic of a label.
-    LabelAppendNote(),
+    LabelAppendNote,
 
     /// Look up the [`crate::label::TypeVarData`] associated with a [`SealingKey`] in the type
     /// environment of a [label](Term::Lbl)
-    LookupTypeVar(),
+    LabelLookupTypeVar,
 }
 
 impl BinaryOp {
     pub fn pos(&self) -> OpPos {
         use BinaryOp::*;
         match self {
-            Plus() | Sub() | Mult() | Div() | Modulo() | StrConcat() | Eq() | LessThan()
-            | LessOrEq() | GreaterThan() | GreaterOrEq() | ArrayConcat() | Merge(_) => OpPos::Infix,
+            Plus | Sub | Mult | Div | Modulo | StringConcat | Eq | LessThan | LessOrEq
+            | GreaterThan | GreaterOrEq | ArrayConcat | Merge(_) => OpPos::Infix,
             _ => OpPos::Prefix,
         }
     }
@@ -1720,53 +1721,57 @@ impl fmt::Display for BinaryOp {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use BinaryOp::*;
         match self {
-            Plus() => write!(f, "plus"),
-            Sub() => write!(f, "sub"),
-            Mult() => write!(f, "mult"),
-            Div() => write!(f, "div"),
-            Modulo() => write!(f, "modulo"),
-            Pow() => write!(f, "pow"),
-            StrConcat() => write!(f, "str_concat"),
-            Eq() => write!(f, "eq"),
-            LessThan() => write!(f, "less_than"),
-            LessOrEq() => write!(f, "less_or_eq"),
-            GreaterThan() => write!(f, "greater_than"),
-            GreaterOrEq() => write!(f, "greater_or_eq"),
-            ApplyContract() => write!(f, "apply_contract"),
-            Unseal() => write!(f, "unseal"),
-            GoField() => write!(f, "go_field"),
-            DynExtend {
+            Plus => write!(f, "plus"),
+            Sub => write!(f, "sub"),
+            Mult => write!(f, "mult"),
+            Div => write!(f, "div"),
+            Modulo => write!(f, "modulo"),
+            Pow => write!(f, "pow"),
+            StringConcat => write!(f, "string/concat"),
+            Eq => write!(f, "eq"),
+            LessThan => write!(f, "less_than"),
+            LessOrEq => write!(f, "less_or_eq"),
+            GreaterThan => write!(f, "greater_than"),
+            GreaterOrEq => write!(f, "greater_or_eq"),
+            ContractApply => write!(f, "contract/apply"),
+            Unseal => write!(f, "unseal"),
+            LabelGoField => write!(f, "label/go_field"),
+            RecordInsert {
                 op_kind: RecordOpKind::IgnoreEmptyOpt,
                 ..
-            } => write!(f, "record_insert"),
-            DynExtend {
+            } => write!(f, "record/insert"),
+            RecordInsert {
                 op_kind: RecordOpKind::ConsiderAllFields,
                 ..
-            } => write!(f, "record_insert_with_opts"),
-            DynRemove(RecordOpKind::IgnoreEmptyOpt) => write!(f, "record_remove"),
-            DynRemove(RecordOpKind::ConsiderAllFields) => write!(f, "record_remove_with_opts"),
-            DynAccess() => write!(f, "dyn_access"),
-            HasField(RecordOpKind::IgnoreEmptyOpt) => write!(f, "has_field"),
-            HasField(RecordOpKind::ConsiderAllFields) => write!(f, "has_field_with_opts"),
-            FieldIsDefined(RecordOpKind::IgnoreEmptyOpt) => write!(f, "field_is_defined"),
-            FieldIsDefined(RecordOpKind::ConsiderAllFields) => {
-                write!(f, "field_is_defined_with_opts")
+            } => write!(f, "record/insert_with_opts"),
+            RecordRemove(RecordOpKind::IgnoreEmptyOpt) => write!(f, "record/remove"),
+            RecordRemove(RecordOpKind::ConsiderAllFields) => write!(f, "record/remove_with_opts"),
+            RecordGet => write!(f, "record/get"),
+            RecordHasField(RecordOpKind::IgnoreEmptyOpt) => write!(f, "record/has_field"),
+            RecordHasField(RecordOpKind::ConsiderAllFields) => {
+                write!(f, "record/has_field_with_opts")
+            }
+            RecordFieldIsDefined(RecordOpKind::IgnoreEmptyOpt) => {
+                write!(f, "record/field_is_defined")
+            }
+            RecordFieldIsDefined(RecordOpKind::ConsiderAllFields) => {
+                write!(f, "record/field_is_defined_with_opts")
             }
-            ArrayConcat() => write!(f, "array_concat"),
-            ArrayElemAt() => write!(f, "elem_at"),
+            ArrayConcat => write!(f, "array/concat"),
+            ArrayAt => write!(f, "array/at"),
             Merge(_) => write!(f, "merge"),
-            Hash() => write!(f, "hash"),
-            Serialize() => write!(f, "serialize"),
-            Deserialize() => write!(f, "deserialize"),
-            StrSplit() => write!(f, "str_split"),
-            StrContains() => write!(f, "str_contains"),
-            Seal() => write!(f, "seal"),
-            ArrayLazyAppCtr() => write!(f, "array_lazy_app_ctr"),
-            RecordLazyAppCtr() => write!(f, "record_lazy_app_ctr"),
-            LabelWithMessage() => write!(f, "label_with_message"),
-            LabelWithNotes() => write!(f, "label_with_notes"),
-            LabelAppendNote() => write!(f, "label_append_note"),
-            LookupTypeVar() => write!(f, "lookup_type_variable"),
+            Hash => write!(f, "hash"),
+            Serialize => write!(f, "serialize"),
+            Deserialize => write!(f, "deserialize"),
+            StringSplit => write!(f, "string/split"),
+            StringContains => write!(f, "string/contains"),
+            Seal => write!(f, "seal"),
+            ContractArrayLazyApp => write!(f, "contract/array_lazy_apply"),
+            ContractRecordLazyApp => write!(f, "contract/record_lazy_apply"),
+            LabelWithMessage => write!(f, "label/with_message"),
+            LabelWithNotes => write!(f, "label/with_notes"),
+            LabelAppendNote => write!(f, "label/append_note"),
+            LabelLookupTypeVar => write!(f, "label/lookup_type_variable"),
         }
     }
 }
@@ -1776,17 +1781,17 @@ impl fmt::Display for BinaryOp {
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum NAryOp {
     /// Replace a substring by another one in a string.
-    StrReplace(),
+    StringReplace,
 
-    /// Same as [`NAryOp::StrReplace`], but the pattern is interpreted as a regular expression.
-    StrReplaceRegex(),
+    /// Same as [`NAryOp::StringReplace`], but the pattern is interpreted as a regular expression.
+    StringReplaceRegex,
 
     /// Return a substring of an original string.
-    StrSubstr(),
+    StringSubstr,
 
     /// The merge operator in contract mode (see [crate::eval::merge]). The arguments are in order
     /// the contract's label, the value to check, and the contract as a record.
-    MergeContract(),
+    MergeContract,
 
     /// Seals one record into the tail of another. Used to ensure that functions using polymorphic
     /// record contracts do not violate parametricity.
@@ -1797,7 +1802,7 @@ pub enum NAryOp {
     ///     attempted,
     ///   - a [record](Term::Record), which is the record we wish to seal the tail into,
     ///   - the [record](Term::Record) that we wish to seal.
-    RecordSealTail(),
+    RecordSealTail,
 
     /// Unseals a term from the tail of a record and returns it.
     ///
@@ -1806,7 +1811,7 @@ pub enum NAryOp {
     ///   - a [label](Term::Lbl) which will be used to assign blame correctly if
     ///     something goes wrong while unsealing,
     ///   - the [record](Term::Record) whose tail we wish to unseal.
-    RecordUnsealTail(),
+    RecordUnsealTail,
 
     /// Insert type variable data into the `type_environment` of a [`crate::label::Label`]
     ///
@@ -1815,25 +1820,25 @@ pub enum NAryOp {
     ///   - the [introduction polarity](crate::label::Polarity) of the type variable
     ///   - the [kind](crate::typ::VarKind) of the type variable
     ///   - a [label](Term::Lbl) on which to operate
-    InsertTypeVar(),
+    LabelInsertTypeVar,
 
     /// Return a sub-array corresponding to a range. Given that Nickel uses array slices under the
     /// hood, as long as the array isn't modified later, this operation is constant in time and
     /// memory.
-    ArraySlice(),
+    ArraySlice,
 }
 
 impl NAryOp {
     pub fn arity(&self) -> usize {
         match self {
-            NAryOp::StrReplace()
-            | NAryOp::StrReplaceRegex()
-            | NAryOp::StrSubstr()
-            | NAryOp::MergeContract()
-            | NAryOp::RecordUnsealTail()
-            | NAryOp::InsertTypeVar()
-            | NAryOp::ArraySlice() => 3,
-            NAryOp::RecordSealTail() => 4,
+            NAryOp::StringReplace
+            | NAryOp::StringReplaceRegex
+            | NAryOp::StringSubstr
+            | NAryOp::MergeContract
+            | NAryOp::RecordUnsealTail
+            | NAryOp::LabelInsertTypeVar
+            | NAryOp::ArraySlice => 3,
+            NAryOp::RecordSealTail => 4,
         }
     }
 }
@@ -1842,14 +1847,14 @@ impl fmt::Display for NAryOp {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use NAryOp::*;
         match self {
-            StrReplace() => write!(f, "str_replace"),
-            StrReplaceRegex() => write!(f, "str_replace_regex"),
-            StrSubstr() => write!(f, "str_substr"),
-            MergeContract() => write!(f, "merge_contract"),
-            RecordSealTail() => write!(f, "record_seal_tail"),
-            RecordUnsealTail() => write!(f, "record_unseal_tail"),
-            InsertTypeVar() => write!(f, "insert_type_variable"),
-            ArraySlice() => write!(f, "array_slice"),
+            StringReplace => write!(f, "string/replace"),
+            StringReplaceRegex => write!(f, "string/replace_regex"),
+            StringSubstr => write!(f, "string/substr"),
+            MergeContract => write!(f, "merge_contract"),
+            RecordSealTail => write!(f, "record/seal_tail"),
+            RecordUnsealTail => write!(f, "record/unseal_tail"),
+            LabelInsertTypeVar => write!(f, "label/insert_type_variable"),
+            ArraySlice => write!(f, "array/slice"),
         }
     }
 }
@@ -2576,7 +2581,11 @@ pub mod make {
         T2: Into<RichTerm>,
         T3: Into<RichTerm>,
     {
-        mk_app!(Term::Op1(UnaryOp::Ite(), cond.into()), t1.into(), t2.into())
+        mk_app!(
+            Term::Op1(UnaryOp::IfThenElse, cond.into()),
+            t1.into(),
+            t2.into()
+        )
     }
 
     pub fn op1<T>(op: UnaryOp, t: T) -> RichTerm
@@ -2610,7 +2619,7 @@ pub mod make {
         T: Into<RichTerm>,
     {
         Ok(mk_app!(
-            op2(BinaryOp::ApplyContract(), typ.contract()?, Term::Lbl(l)),
+            op2(BinaryOp::ContractApply, typ.contract()?, Term::Lbl(l)),
             t.into()
         ))
     }
@@ -2646,7 +2655,7 @@ pub mod make {
     {
         let mut term = record.into();
         for f in fields.into_iter() {
-            term = make::op1(UnaryOp::StaticAccess(f.into()), term);
+            term = make::op1(UnaryOp::RecordAccess(f.into()), term);
         }
         term
     }
@@ -2659,9 +2668,9 @@ mod tests {
     #[test]
     fn make_static_access() {
         let t = make::op1(
-            UnaryOp::StaticAccess("record".into()),
+            UnaryOp::RecordAccess("record".into()),
             make::op1(
-                UnaryOp::StaticAccess("records".into()),
+                UnaryOp::RecordAccess("records".into()),
                 make::var("predicates"),
             ),
         );
diff --git a/core/src/term/pattern/compile.rs b/core/src/term/pattern/compile.rs
index d8d15bed..3fb6029b 100644
--- a/core/src/term/pattern/compile.rs
+++ b/core/src/term/pattern/compile.rs
@@ -31,9 +31,9 @@ use crate::{
     },
 };
 
-/// Generate a standard `%record_insert%` primop as generated by the parser.
+/// Generate a standard `%record/insert%` primop as generated by the parser.
 fn record_insert() -> BinaryOp {
-    BinaryOp::DynExtend {
+    BinaryOp::RecordInsert {
         ext_kind: RecordExtKind::WithValue,
         metadata: Default::default(),
         pending_contracts: Default::default(),
@@ -46,7 +46,7 @@ fn record_insert() -> BinaryOp {
 
 /// Generate a Nickel expression which inserts a new binding in the working dictionary.
 ///
-/// `%record_insert% "<id>" bindings_id value_id`
+/// `%record/insert% "<id>" bindings_id value_id`
 fn insert_binding(id: LocIdent, value_id: LocIdent, bindings_id: LocIdent) -> RichTerm {
     mk_app!(
         make::op2(
@@ -62,23 +62,23 @@ fn insert_binding(id: LocIdent, value_id: LocIdent, bindings_id: LocIdent) -> Ri
 /// remove the `field` from it.
 ///
 /// ```nickel
-/// %record_insert% "<REST_FIELD>"
-///   (%record_remove% "<REST_FIELD>" bindings_id)
-///   (%record_remove% "<field>"
+/// %record/insert% "<REST_FIELD>"
+///   (%record/remove% "<REST_FIELD>" bindings_id)
+///   (%record/remove% "<field>"
 ///     (%static_access(REST_FIELD) bindings_id)
 ///   )
 /// ```
 fn remove_from_rest(rest_field: LocIdent, field: LocIdent, bindings_id: LocIdent) -> RichTerm {
-    let rest = make::op1(UnaryOp::StaticAccess(rest_field), Term::Var(bindings_id));
+    let rest = make::op1(UnaryOp::RecordAccess(rest_field), Term::Var(bindings_id));
 
     let rest_shrinked = make::op2(
-        BinaryOp::DynRemove(RecordOpKind::ConsiderAllFields),
+        BinaryOp::RecordRemove(RecordOpKind::ConsiderAllFields),
         Term::Str(field.label().into()),
         rest,
     );
 
     let bindings_shrinked = make::op2(
-        BinaryOp::DynRemove(RecordOpKind::ConsiderAllFields),
+        BinaryOp::RecordRemove(RecordOpKind::ConsiderAllFields),
         Term::Str(rest_field.into()),
         Term::Var(bindings_id),
     );
@@ -103,11 +103,11 @@ fn remove_from_rest(rest_field: LocIdent, field: LocIdent, bindings_id: LocIdent
 ///
 /// ```nickel
 /// if !(%field_is_defined% "<field>" record_id) then
-///   if %has_field% "<field>" record_id then
+///   if %record/has_field% "<field>" record_id then
 ///     record_id & { "<field>" = default }
 ///   else
 ///     # Merging is potentially more costly, and we can just fallback to record insertion here.
-///     %record_insert% "<field>" record_id default
+///     %record/insert% "<field>" record_id default
 /// else
 ///   record_id
 /// ```
@@ -117,16 +117,16 @@ pub(crate) fn with_default_value(
     default: RichTerm,
 ) -> RichTerm {
     let field_not_defined = make::op1(
-        UnaryOp::BoolNot(),
+        UnaryOp::BoolNot,
         make::op2(
-            BinaryOp::FieldIsDefined(RecordOpKind::ConsiderAllFields),
+            BinaryOp::RecordFieldIsDefined(RecordOpKind::ConsiderAllFields),
             Term::Str(field.label().into()),
             Term::Var(record_id),
         ),
     );
 
     let has_field = make::op2(
-        BinaryOp::HasField(RecordOpKind::ConsiderAllFields),
+        BinaryOp::RecordHasField(RecordOpKind::ConsiderAllFields),
         Term::Str(field.label().into()),
         Term::Var(record_id),
     );
@@ -223,7 +223,7 @@ impl CompilePart for Pattern {
     // a template of some sort):
     //
     // < if let Some(alias) = alias { >
-    //   let bindings = %record_insert% <alias> bindings arg in
+    //   let bindings = %record/insert% <alias> bindings arg in
     // < } >
     // <pattern_data.compile()> arg bindings
     fn compile_part(&self, value_id: LocIdent, bindings_id: LocIdent) -> RichTerm {
@@ -233,7 +233,7 @@ impl CompilePart for Pattern {
 
         // Either
         //
-        // let bindings = %record_insert% <alias> bindings arg in
+        // let bindings = %record/insert% <alias> bindings arg in
         // continuation
         //
         // if `alias` is set, or just `continuation` otherwise.
@@ -254,7 +254,7 @@ impl CompilePart for PatternData {
         match self {
             PatternData::Wildcard => Term::Var(bindings_id).into(),
             PatternData::Any(id) => {
-                // %record_insert% "<id>" value_id bindings_id
+                // %record/insert% "<id>" value_id bindings_id
                 insert_binding(*id, value_id, bindings_id)
             }
             PatternData::Record(pat) => pat.compile_part(value_id, bindings_id),
@@ -282,16 +282,16 @@ impl CompilePart for ConstantPatternData {
 
             // %typeof% value_id == '<nickel_type>
             let type_matches = make::op2(
-                BinaryOp::Eq(),
-                make::op1(UnaryOp::Typeof(), Term::Var(value_id)),
+                BinaryOp::Eq,
+                make::op1(UnaryOp::Typeof, Term::Var(value_id)),
                 Term::Enum(nickel_type.into()),
             );
 
             // value_id == <value>
-            let value_matches = make::op2(BinaryOp::Eq(), Term::Var(value_id), value);
+            let value_matches = make::op2(BinaryOp::Eq, Term::Var(value_id), value);
 
             // <type_matches> && <value_matches>
-            let if_condition = mk_app!(make::op1(UnaryOp::BoolAnd(), type_matches), value_matches);
+            let if_condition = mk_app!(make::op1(UnaryOp::BoolAnd, type_matches), value_matches);
 
             make::if_then_else(if_condition, Term::Var(bindings_id), Term::Null)
         };
@@ -329,8 +329,8 @@ impl CompilePart for OrPattern {
                 let prev_bindings = LocIdent::fresh();
 
                 let is_prev_not_null = make::op1(
-                    UnaryOp::BoolNot(),
-                    make::op2(BinaryOp::Eq(), Term::Var(prev_bindings), Term::Null),
+                    UnaryOp::BoolNot,
+                    make::op2(BinaryOp::Eq, Term::Var(prev_bindings), Term::Null),
                 );
 
                 let if_block = make::if_then_else(
@@ -360,7 +360,7 @@ impl CompilePart for RecordPattern {
     //   let final_bindings_id =
     //     <fold (field, value) in fields
     //      - cont is the accumulator
-    //      - initial accumulator is `%record_insert% "<REST_FIELD>" bindings_id value_id`
+    //      - initial accumulator is `%record/insert% "<REST_FIELD>" bindings_id value_id`
     //      >
     //
     //     # If there is a default value, we must set it before the %field_is_defined% check below,
@@ -396,23 +396,23 @@ impl CompilePart for RecordPattern {
     //       if (%static_access% <REST_FIELD> final_bindings_id) != {} then
     //         null
     //       else
-    //         %record_remove% "<REST>" final_bindings_id
+    //         %record/remove% "<REST>" final_bindings_id
     //     <else if self.tail is capture(rest)>
     //       # move the rest from REST_FIELD to rest, and remove REST_FIELD
-    //       %record_remove% "<REST>"
-    //         (%record_insert% <rest>
+    //       %record/remove% "<REST>"
+    //         (%record/insert% <rest>
     //           final_bindings_id
     //           (%static_access% <REST_FIELD> final_bindings_id)
     //         )
     //     <else if self.tail is open>
-    //       %record_remove% "<REST>" final_bindings_id
+    //       %record/remove% "<REST>" final_bindings_id
     //     <end if>
     // else
     //   null
     fn compile_part(&self, value_id: LocIdent, bindings_id: LocIdent) -> RichTerm {
         let rest_field = LocIdent::fresh();
 
-        // `%record_insert% "<REST>" bindings_id value_id`
+        // `%record/insert% "<REST>" bindings_id value_id`
         let init_bindings = mk_app!(
             make::op2(
                 record_insert(),
@@ -426,7 +426,7 @@ impl CompilePart for RecordPattern {
         //
         // <fold (field, value) in fields
         //  - cont is the accumulator
-        //  - initial accumulator is `%record_insert% "<REST>" bindings_id value_id`
+        //  - initial accumulator is `%record/insert% "<REST>" bindings_id value_id`
         // >
         //
         // # If there is a default value, we must set it before the %field_is_defined% check below,
@@ -468,9 +468,9 @@ impl CompilePart for RecordPattern {
 
             // %static_access(field)% (%static_access(REST_FIELD)% local_bindings_id)
             let extracted_value = make::op1(
-                UnaryOp::StaticAccess(field),
+                UnaryOp::RecordAccess(field),
                 make::op1(
-                    UnaryOp::StaticAccess(rest_field),
+                    UnaryOp::RecordAccess(rest_field),
                     Term::Var(local_bindings_id),
                 ),
             );
@@ -486,7 +486,7 @@ impl CompilePart for RecordPattern {
             // else
             //  <inner_else_block>
             let inner_if = make::if_then_else(
-                make::op2(BinaryOp::Eq(), Term::Var(local_bindings_id), Term::Null),
+                make::op2(BinaryOp::Eq, Term::Var(local_bindings_id), Term::Null),
                 Term::Null,
                 inner_else_block,
             );
@@ -516,7 +516,7 @@ impl CompilePart for RecordPattern {
 
             // %field_is_defined% field value_id
             let has_field = make::op2(
-                BinaryOp::FieldIsDefined(RecordOpKind::ConsiderAllFields),
+                BinaryOp::RecordFieldIsDefined(RecordOpKind::ConsiderAllFields),
                 Term::Str(field.label().into()),
                 Term::Var(value_id),
             );
@@ -540,16 +540,16 @@ impl CompilePart for RecordPattern {
 
         // %typeof% value_id == 'Record
         let is_record: RichTerm = make::op2(
-            BinaryOp::Eq(),
-            make::op1(UnaryOp::Typeof(), Term::Var(value_id)),
+            BinaryOp::Eq,
+            make::op1(UnaryOp::Typeof, Term::Var(value_id)),
             Term::Enum("Record".into()),
         );
 
         let final_bindings_id = LocIdent::fresh();
 
-        // %record_remove% "<REST>" final_bindings_id
+        // %record/remove% "<REST>" final_bindings_id
         let bindings_without_rest = make::op2(
-            BinaryOp::DynRemove(RecordOpKind::ConsiderAllFields),
+            BinaryOp::RecordRemove(RecordOpKind::ConsiderAllFields),
             Term::Str(rest_field.into()),
             Term::Var(final_bindings_id),
         );
@@ -559,14 +559,14 @@ impl CompilePart for RecordPattern {
             // if (%static_access% <REST_FIELD> final_bindings_id) != {} then
             //   null
             // else
-            //   %record_remove% "<REST>" final_bindings_id
+            //   %record/remove% "<REST>" final_bindings_id
             TailPattern::Empty => make::if_then_else(
                 make::op1(
-                    UnaryOp::BoolNot(),
+                    UnaryOp::BoolNot,
                     make::op2(
-                        BinaryOp::Eq(),
+                        BinaryOp::Eq,
                         make::op1(
-                            UnaryOp::StaticAccess(rest_field),
+                            UnaryOp::RecordAccess(rest_field),
                             Term::Var(final_bindings_id),
                         ),
                         Term::Record(RecordData::empty()),
@@ -575,13 +575,13 @@ impl CompilePart for RecordPattern {
                 Term::Null,
                 bindings_without_rest,
             ),
-            // %record_remove% "<REST>"
-            //   (%record_insert% <rest>
+            // %record/remove% "<REST>"
+            //   (%record/insert% <rest>
             //     final_bindings_id
             //     (%static_access% <REST_FIELD> final_bindings_id)
             //   )
             TailPattern::Capture(rest) => make::op2(
-                BinaryOp::DynRemove(RecordOpKind::ConsiderAllFields),
+                BinaryOp::RecordRemove(RecordOpKind::ConsiderAllFields),
                 Term::Str(rest_field.into()),
                 mk_app!(
                     make::op2(
@@ -590,12 +590,12 @@ impl CompilePart for RecordPattern {
                         Term::Var(final_bindings_id),
                     ),
                     make::op1(
-                        UnaryOp::StaticAccess(rest_field),
+                        UnaryOp::RecordAccess(rest_field),
                         Term::Var(final_bindings_id)
                     )
                 ),
             ),
-            // %record_remove% "<REST>" final_bindings_id
+            // %record/remove% "<REST>" final_bindings_id
             TailPattern::Open => bindings_without_rest,
         };
 
@@ -606,7 +606,7 @@ impl CompilePart for RecordPattern {
         // else
         //   <tail_block>
         let guard_tail_block = make::if_then_else(
-            make::op2(BinaryOp::Eq(), Term::Var(final_bindings_id), Term::Null),
+            make::op2(BinaryOp::Eq, Term::Var(final_bindings_id), Term::Null),
             Term::Null,
             tail_block,
         );
@@ -651,10 +651,10 @@ impl CompilePart for ArrayPattern {
     //     null
     //   else
     //     <if self.tail is capture(rest)>
-    //       %record_insert%
+    //       %record/insert%
     //         <rest>
     //         final_bindings_id
-    //         (%array_slice% <self.patterns.len()> value_len value_id)
+    //         (%array/slice% <self.patterns.len()> value_len value_id)
     //     <else>
     //       final_bindings_id
     //     <end if>
@@ -688,7 +688,7 @@ impl CompilePart for ArrayPattern {
 
                 // %array_access% idx value_id
                 let extracted_value = make::op2(
-                    BinaryOp::ArrayElemAt(),
+                    BinaryOp::ArrayAt,
                     Term::Var(value_id),
                     Term::Num(idx.into()),
                 );
@@ -704,7 +704,7 @@ impl CompilePart for ArrayPattern {
                 // else
                 //  <inner_else_block>
                 let inner_if = make::if_then_else(
-                    make::op2(BinaryOp::Eq(), Term::Var(local_bindings_id), Term::Null),
+                    make::op2(BinaryOp::Eq, Term::Var(local_bindings_id), Term::Null),
                     Term::Null,
                     inner_else_block,
                 );
@@ -716,20 +716,20 @@ impl CompilePart for ArrayPattern {
 
         // %typeof% value_id == 'Array
         let is_array: RichTerm = make::op2(
-            BinaryOp::Eq(),
-            make::op1(UnaryOp::Typeof(), Term::Var(value_id)),
+            BinaryOp::Eq,
+            make::op1(UnaryOp::Typeof, Term::Var(value_id)),
             Term::Enum("Array".into()),
         );
 
         let comp_op = if self.is_open() {
-            BinaryOp::GreaterOrEq()
+            BinaryOp::GreaterOrEq
         } else {
-            BinaryOp::Eq()
+            BinaryOp::Eq
         };
 
         // <is_array> && value_len <comp_op> <self.patterns.len()>
         let outer_check = mk_app!(
-            make::op1(UnaryOp::BoolAnd(), is_array),
+            make::op1(UnaryOp::BoolAnd, is_array),
             make::op2(comp_op, Term::Var(value_len_id), pats_len.clone(),)
         );
 
@@ -739,10 +739,10 @@ impl CompilePart for ArrayPattern {
         let tail_block = match self.tail {
             // final_bindings_id
             TailPattern::Empty | TailPattern::Open => make::var(final_bindings_id),
-            // %record_insert%
+            // %record/insert%
             //    <rest>
             //    final_bindings_id
-            //    (%array_slice% <self.patterns.len()> value_len value_id)
+            //    (%array/slice% <self.patterns.len()> value_len value_id)
             TailPattern::Capture(rest) => mk_app!(
                 make::op2(
                     record_insert(),
@@ -750,7 +750,7 @@ impl CompilePart for ArrayPattern {
                     Term::Var(final_bindings_id),
                 ),
                 make::opn(
-                    NAryOp::ArraySlice(),
+                    NAryOp::ArraySlice,
                     vec![pats_len, Term::Var(value_len_id), Term::Var(value_id)]
                 )
             ),
@@ -763,7 +763,7 @@ impl CompilePart for ArrayPattern {
         // else
         //   <tail_block>
         let guard_tail_block = make::if_then_else(
-            make::op2(BinaryOp::Eq(), Term::Var(final_bindings_id), Term::Null),
+            make::op2(BinaryOp::Eq, Term::Var(final_bindings_id), Term::Null),
             Term::Null,
             tail_block,
         );
@@ -778,7 +778,7 @@ impl CompilePart for ArrayPattern {
         // finally, we need to bind `value_len_id` to the length of the array
         make::let_in(
             value_len_id,
-            make::op1(UnaryOp::ArrayLength(), Term::Var(value_id)),
+            make::op1(UnaryOp::ArrayLength, Term::Var(value_id)),
             outer_if,
         )
     }
@@ -788,8 +788,8 @@ impl CompilePart for EnumPattern {
     fn compile_part(&self, value_id: LocIdent, bindings_id: LocIdent) -> RichTerm {
         // %enum_get_tag% value_id == '<self.tag>
         let tag_matches = make::op2(
-            BinaryOp::Eq(),
-            make::op1(UnaryOp::EnumGetTag(), Term::Var(value_id)),
+            BinaryOp::Eq,
+            make::op1(UnaryOp::EnumGetTag, Term::Var(value_id)),
             Term::Enum(self.tag),
         );
 
@@ -803,8 +803,8 @@ impl CompilePart for EnumPattern {
             // %enum_is_variant% value_id && <tag_matches>
             let if_condition = mk_app!(
                 make::op1(
-                    UnaryOp::BoolAnd(),
-                    make::op1(UnaryOp::EnumIsVariant(), Term::Var(value_id)),
+                    UnaryOp::BoolAnd,
+                    make::op1(UnaryOp::EnumIsVariant, Term::Var(value_id)),
                 ),
                 tag_matches
             );
@@ -813,7 +813,7 @@ impl CompilePart for EnumPattern {
                 if_condition,
                 make::let_in(
                     value_id,
-                    make::op1(UnaryOp::EnumUnwrapVariant(), Term::Var(value_id)),
+                    make::op1(UnaryOp::EnumUnwrapVariant, Term::Var(value_id)),
                     pat.compile_part(value_id, bindings_id),
                 ),
                 Term::Null,
@@ -826,21 +826,21 @@ impl CompilePart for EnumPattern {
 
             // %typeof% value_id == 'Enum
             let is_enum = make::op2(
-                BinaryOp::Eq(),
-                make::op1(UnaryOp::Typeof(), Term::Var(value_id)),
+                BinaryOp::Eq,
+                make::op1(UnaryOp::Typeof, Term::Var(value_id)),
                 Term::Enum("Enum".into()),
             );
 
             // !(%enum_is_variant% value_id)
             let is_enum_tag = make::op1(
-                UnaryOp::BoolNot(),
-                make::op1(UnaryOp::EnumIsVariant(), Term::Var(value_id)),
+                UnaryOp::BoolNot,
+                make::op1(UnaryOp::EnumIsVariant, Term::Var(value_id)),
             );
 
             // <is_enum> && <is_enum_tag> && <tag_matches>
             let if_condition = mk_app!(
-                make::op1(UnaryOp::BoolAnd(), is_enum,),
-                mk_app!(make::op1(UnaryOp::BoolAnd(), is_enum_tag,), tag_matches)
+                make::op1(UnaryOp::BoolAnd, is_enum,),
+                mk_app!(make::op1(UnaryOp::BoolAnd, is_enum_tag,), tag_matches)
             );
 
             make::if_then_else(if_condition, Term::Var(bindings_id), Term::Null)
@@ -968,20 +968,20 @@ impl Compile for MatchData {
 
                 // inner if condition:
                 // bindings_id == null || !<guard>
-                let inner_if_cond = make::op2(BinaryOp::Eq(), Term::Var(bindings_id), Term::Null);
+                let inner_if_cond = make::op2(BinaryOp::Eq, Term::Var(bindings_id), Term::Null);
                 let inner_if_cond = if let Some(guard) = branch.guard {
                     // the guard must be evaluated in the same environment as the body of the
                     // branch, as it might use bindings introduced by the pattern. Since `||` is
                     // lazy in Nickel, we know that `bindings_id` is not null if the guard
                     // condition is ever evaluated.
                     let guard_cond = mk_app!(
-                        make::op1(UnaryOp::PatternBranch(), Term::Var(bindings_id)),
+                        make::op1(UnaryOp::PatternBranch, Term::Var(bindings_id)),
                         guard
                     );
 
                     mk_app!(
-                        make::op1(UnaryOp::BoolOr(), inner_if_cond),
-                        make::op1(UnaryOp::BoolNot(), guard_cond)
+                        make::op1(UnaryOp::BoolOr, inner_if_cond),
+                        make::op1(UnaryOp::BoolNot, guard_cond)
                     )
                 } else {
                     inner_if_cond
@@ -998,7 +998,7 @@ impl Compile for MatchData {
                     inner_if_cond,
                     cont,
                     mk_app!(
-                        make::op1(UnaryOp::PatternBranch(), Term::Var(bindings_id),),
+                        make::op1(UnaryOp::PatternBranch, Term::Var(bindings_id),),
                         branch.body
                     ),
                 );
diff --git a/core/src/typ.rs b/core/src/typ.rs
index 6e012db9..b3ac6ecb 100644
--- a/core/src/typ.rs
+++ b/core/src/typ.rs
@@ -999,7 +999,7 @@ impl Subcontract for EnumRows {
                         // 'Tag (%apply_contract% T label_arg variant_arg)
                         let arg = mk_app!(
                             mk_term::op2(
-                                BinaryOp::ApplyContract(),
+                                BinaryOp::ContractApply,
                                 ty.subcontract(vars.clone(), pol, sy)?,
                                 mk_term::var(label_arg)
                             ),
@@ -1042,7 +1042,7 @@ impl Subcontract for EnumRows {
             (
                 mk_app!(
                     mk_term::op2(
-                        BinaryOp::ApplyContract(),
+                        BinaryOp::ContractApply,
                         get_var_contract(&vars, var.ident(), var.pos)?,
                         mk_term::var(label_arg)
                     ),
diff --git a/core/src/typecheck/eq.rs b/core/src/typecheck/eq.rs
index e78f6a87..12833514 100644
--- a/core/src/typecheck/eq.rs
+++ b/core/src/typecheck/eq.rs
@@ -448,7 +448,7 @@ fn contract_eq_bounded<E: TermEnvironment>(
 
             value_eq && ty_eq
         }
-        (Op1(UnaryOp::StaticAccess(id1), t1), Op1(UnaryOp::StaticAccess(id2), t2)) => {
+        (Op1(UnaryOp::RecordAccess(id1), t1), Op1(UnaryOp::RecordAccess(id2), t2)) => {
             id1 == id2 && contract_eq_bounded(state, t1, env1, t2, env2)
         }
         (Type(ty1), Type(ty2)) => type_eq_bounded(
diff --git a/core/src/typecheck/operation.rs b/core/src/typecheck/operation.rs
index 46f068ef..04562329 100644
--- a/core/src/typecheck/operation.rs
+++ b/core/src/typecheck/operation.rs
@@ -16,7 +16,7 @@ pub fn get_uop_type(
 ) -> Result<(UnifType, UnifType), TypecheckError> {
     Ok(match op {
         // forall a. bool -> a -> a -> a
-        UnaryOp::Ite() => {
+        UnaryOp::IfThenElse => {
             let branches = state.table.fresh_type_uvar(var_level);
 
             (
@@ -25,7 +25,7 @@ pub fn get_uop_type(
             )
         }
         // Dyn -> [| 'Number, 'Bool, 'String, 'Enum, 'Function, 'Array, 'Record, 'Label, 'Other |]
-        UnaryOp::Typeof() => (
+        UnaryOp::Typeof => (
             mk_uniftype::dynamic(),
             mk_uty_enum!(
                 "Number",
@@ -41,21 +41,21 @@ pub fn get_uop_type(
             ),
         ),
         // Bool -> Bool -> Bool
-        UnaryOp::BoolAnd() | UnaryOp::BoolOr() => {
+        UnaryOp::BoolAnd | UnaryOp::BoolOr => {
             (mk_uniftype::bool(), mk_uty_arrow!(TypeF::Bool, TypeF::Bool))
         }
         // Bool -> Bool
-        UnaryOp::BoolNot() => (mk_uniftype::bool(), mk_uniftype::bool()),
+        UnaryOp::BoolNot => (mk_uniftype::bool(), mk_uniftype::bool()),
         // forall a. Dyn -> a
-        UnaryOp::Blame() => {
+        UnaryOp::Blame => {
             let res = state.table.fresh_type_uvar(var_level);
 
             (mk_uniftype::dynamic(), res)
         }
         // Dyn -> Polarity
-        UnaryOp::Pol() => (mk_uniftype::dynamic(), mk_uty_enum!("Positive", "Negative")),
+        UnaryOp::LabelPol => (mk_uniftype::dynamic(), mk_uty_enum!("Positive", "Negative")),
         // forall rows. [| ; rows |] -> [| id ; rows |]
-        UnaryOp::Embed(id) => {
+        UnaryOp::EnumEmbed(id) => {
             let row_var_id = state.table.fresh_erows_var_id(var_level);
             let row = UnifEnumRows::UnifVar {
                 id: row_var_id,
@@ -71,20 +71,20 @@ pub fn get_uop_type(
         UnaryOp::TagsOnlyMatch { .. } => panic!("cannot typecheck match primop"),
         // Morally, Label -> Label
         // Dyn -> Dyn
-        UnaryOp::ChangePolarity()
-        | UnaryOp::GoDom()
-        | UnaryOp::GoCodom()
-        | UnaryOp::GoArray()
-        | UnaryOp::GoDict() => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
+        UnaryOp::LabelFlipPol
+        | UnaryOp::LabelGoDom
+        | UnaryOp::LabelGoCodom
+        | UnaryOp::LabelGoArray
+        | UnaryOp::LabelGoDict => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
         // forall rows a. { id: a | rows} -> a
-        UnaryOp::StaticAccess(id) => {
+        UnaryOp::RecordAccess(id) => {
             let rows = state.table.fresh_rrows_uvar(var_level);
             let res = state.table.fresh_type_uvar(var_level);
 
             (mk_uty_record!((*id, res.clone()); rows), res)
         }
         // forall a b. Array a -> (a -> b) -> Array b
-        UnaryOp::ArrayMap() => {
+        UnaryOp::ArrayMap => {
             let a = state.table.fresh_type_uvar(var_level);
             let b = state.table.fresh_type_uvar(var_level);
 
@@ -95,7 +95,7 @@ pub fn get_uop_type(
             )
         }
         // forall a. Num -> (Num -> a) -> Array a
-        UnaryOp::ArrayGen() => {
+        UnaryOp::ArrayGen => {
             let a = state.table.fresh_type_uvar(var_level);
 
             let f_type = mk_uty_arrow!(TypeF::Number, a.clone());
@@ -105,7 +105,7 @@ pub fn get_uop_type(
             )
         }
         // forall a b. { _ : a} -> (Str -> a -> b) -> { _ : b }
-        UnaryOp::RecordMap() => {
+        UnaryOp::RecordMap => {
             // Assuming f has type Str -> a -> b,
             // this has type Dict(a) -> Dict(b)
 
@@ -119,21 +119,21 @@ pub fn get_uop_type(
             )
         }
         // forall a b. a -> b -> b
-        UnaryOp::Seq() | UnaryOp::DeepSeq() => {
+        UnaryOp::Seq | UnaryOp::DeepSeq => {
             let fst = state.table.fresh_type_uvar(var_level);
             let snd = state.table.fresh_type_uvar(var_level);
 
             (fst, mk_uty_arrow!(snd.clone(), snd))
         }
         // forall a. Array a -> Num
-        UnaryOp::ArrayLength() => {
+        UnaryOp::ArrayLength => {
             let ty_elt = state.table.fresh_type_uvar(var_level);
             (mk_uniftype::array(ty_elt), mk_uniftype::num())
         }
         // This should not happen, as ChunksConcat() is only produced during evaluation.
-        UnaryOp::ChunksConcat() => panic!("cannot type ChunksConcat()"),
+        UnaryOp::ChunksConcat => panic!("cannot type ChunksConcat()"),
         // forall a. { _: a } -> Array Str
-        UnaryOp::FieldsOf(_) => {
+        UnaryOp::RecordFields(_) => {
             let ty_a = state.table.fresh_type_uvar(var_level);
 
             (
@@ -142,37 +142,37 @@ pub fn get_uop_type(
             )
         }
         // forall a. { _: a } -> Array a
-        UnaryOp::ValuesOf() => {
+        UnaryOp::RecordValues => {
             let ty_a = state.table.fresh_type_uvar(var_level);
 
             (mk_uniftype::dict(ty_a.clone()), mk_uniftype::array(ty_a))
         }
         // Str -> Str
-        UnaryOp::StrTrim() => (mk_uniftype::str(), mk_uniftype::str()),
+        UnaryOp::StringTrim => (mk_uniftype::str(), mk_uniftype::str()),
         // Str -> Array Str
-        UnaryOp::StrChars() => (mk_uniftype::str(), mk_uniftype::array(mk_uniftype::str())),
+        UnaryOp::StringChars => (mk_uniftype::str(), mk_uniftype::array(mk_uniftype::str())),
         // Str -> Str
-        UnaryOp::StrUppercase() => (mk_uniftype::str(), mk_uniftype::str()),
+        UnaryOp::StringUppercase => (mk_uniftype::str(), mk_uniftype::str()),
         // Str -> Str
-        UnaryOp::StrLowercase() => (mk_uniftype::str(), mk_uniftype::str()),
+        UnaryOp::StringLowercase => (mk_uniftype::str(), mk_uniftype::str()),
         // Str -> Num
-        UnaryOp::StrLength() => (mk_uniftype::str(), mk_uniftype::num()),
+        UnaryOp::StringLength => (mk_uniftype::str(), mk_uniftype::num()),
         // Dyn -> Str
-        UnaryOp::ToStr() => (mk_uniftype::dynamic(), mk_uniftype::str()),
+        UnaryOp::ToString => (mk_uniftype::dynamic(), mk_uniftype::str()),
         // Str -> Num
-        UnaryOp::NumFromStr() => (mk_uniftype::str(), mk_uniftype::num()),
+        UnaryOp::NumberFromString => (mk_uniftype::str(), mk_uniftype::num()),
         // Str -> < | a> for a rigid type variable a
-        UnaryOp::EnumFromStr() => (
+        UnaryOp::EnumFromString => (
             mk_uniftype::str(),
             mk_uty_enum!(; state.table.fresh_erows_const(var_level)),
         ),
         // Str -> Str -> Bool
-        UnaryOp::StrIsMatch() => (
+        UnaryOp::StringIsMatch => (
             mk_uniftype::str(),
             mk_uty_arrow!(mk_uniftype::str(), mk_uniftype::bool()),
         ),
         // Str -> Str -> {matched: Str, index: Num, groups: Array Str}
-        UnaryOp::StrFind() => (
+        UnaryOp::StringFind => (
             mk_uniftype::str(),
             mk_uty_arrow!(
                 mk_uniftype::str(),
@@ -184,7 +184,7 @@ pub fn get_uop_type(
             ),
         ),
         // String -> String -> Array { matched: String, index: Number, groups: Array String }
-        UnaryOp::StrFindAll() => (
+        UnaryOp::StringFindAll => (
             mk_uniftype::str(),
             mk_uty_arrow!(
                 mk_uniftype::str(),
@@ -196,9 +196,9 @@ pub fn get_uop_type(
             ),
         ),
         // Str -> Bool
-        UnaryOp::StrIsMatchCompiled(_) => (mk_uniftype::str(), mk_uniftype::bool()),
+        UnaryOp::StringIsMatchCompiled(_) => (mk_uniftype::str(), mk_uniftype::bool()),
         // Str -> {matched: Str, index: Num, groups: Array Str}
-        UnaryOp::StrFindCompiled(_) => (
+        UnaryOp::StringFindCompiled(_) => (
             mk_uniftype::str(),
             mk_uty_record!(
                 ("matched", TypeF::String),
@@ -206,7 +206,7 @@ pub fn get_uop_type(
                 ("groups", mk_uniftype::array(TypeF::String))
             ),
         ),
-        UnaryOp::StrFindAllCompiled(_) => (
+        UnaryOp::StringFindAllCompiled(_) => (
             mk_uniftype::str(),
             mk_uniftype::array(mk_uty_record!(
                 ("matched", TypeF::String),
@@ -217,43 +217,43 @@ pub fn get_uop_type(
         // Dyn -> Dyn
         UnaryOp::Force { .. } => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
         // forall a. a -> a
-        UnaryOp::RecDefault() => {
+        UnaryOp::RecDefault => {
             let ty = state.table.fresh_type_uvar(var_level);
             (ty.clone(), ty)
         }
         // forall a. a -> a
-        UnaryOp::RecForce() => {
+        UnaryOp::RecForce => {
             let ty = state.table.fresh_type_uvar(var_level);
             (ty.clone(), ty)
         }
-        UnaryOp::RecordEmptyWithTail() => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
+        UnaryOp::RecordEmptyWithTail => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
 
         // forall a. Str -> a -> a
-        UnaryOp::Trace() => {
+        UnaryOp::Trace => {
             let ty = state.table.fresh_type_uvar(var_level);
             (mk_uniftype::str(), mk_uty_arrow!(ty.clone(), ty))
         }
         // Morally: Lbl -> Lbl
         // Actual: Dyn -> Dyn
-        UnaryOp::LabelPushDiag() => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
+        UnaryOp::LabelPushDiag => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
         // Str -> Dyn
         #[cfg(feature = "nix-experimental")]
-        UnaryOp::EvalNix() => (mk_uniftype::str(), mk_uniftype::dynamic()),
+        UnaryOp::EvalNix => (mk_uniftype::str(), mk_uniftype::dynamic()),
         // Because the tag isn't fixed, we can't really provide a proper static type for this
         // primop.
         // This isn't a problem, as this operator is mostly internal and pattern matching should be
         // used to destructure enum variants.
-        UnaryOp::EnumUnwrapVariant() => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
+        UnaryOp::EnumUnwrapVariant => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
         // Same as `EnumUnwrapVariant` just above.
-        UnaryOp::EnumGetTag() => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
+        UnaryOp::EnumGetTag => (mk_uniftype::dynamic(), mk_uniftype::dynamic()),
         // Note that is_variant breaks parametricity, so it can't get a polymorphic type.
         // Dyn -> Bool
-        UnaryOp::EnumIsVariant() => (mk_uniftype::dynamic(), mk_uniftype::bool()),
+        UnaryOp::EnumIsVariant => (mk_uniftype::dynamic(), mk_uniftype::bool()),
         // [crate::term::UnaryOp::PatternBranch] shouldn't appear anywhere in actual code, because its
         // second argument can't be properly typechecked: it has unbound variables. However, it's
         // not hard to come up with a vague working type for it, so we do.
         // forall a. {_ : a} -> Dyn -> Dyn
-        UnaryOp::PatternBranch() => {
+        UnaryOp::PatternBranch => {
             let ty_elt = state.table.fresh_type_uvar(var_level);
             (
                 mk_uniftype::dict(ty_elt),
@@ -271,57 +271,54 @@ pub fn get_bop_type(
 ) -> Result<(UnifType, UnifType, UnifType), TypecheckError> {
     Ok(match op {
         // Num -> Num -> Num
-        BinaryOp::Plus()
-        | BinaryOp::Sub()
-        | BinaryOp::Mult()
-        | BinaryOp::Div()
-        | BinaryOp::Modulo() => (mk_uniftype::num(), mk_uniftype::num(), mk_uniftype::num()),
+        BinaryOp::Plus | BinaryOp::Sub | BinaryOp::Mult | BinaryOp::Div | BinaryOp::Modulo => {
+            (mk_uniftype::num(), mk_uniftype::num(), mk_uniftype::num())
+        }
         // Sym -> Dyn -> Dyn -> Dyn
-        BinaryOp::Seal() => (
+        BinaryOp::Seal => (
             mk_uniftype::sym(),
             mk_uniftype::dynamic(),
             mk_uty_arrow!(TypeF::Dyn, TypeF::Dyn),
         ),
         // Str -> Str -> Str
-        BinaryOp::StrConcat() => (mk_uniftype::str(), mk_uniftype::str(), mk_uniftype::str()),
+        BinaryOp::StringConcat => (mk_uniftype::str(), mk_uniftype::str(), mk_uniftype::str()),
         // Ideally: Contract -> Label -> Dyn -> Dyn
         // Currently: Dyn -> Dyn -> (Dyn -> Dyn)
-        BinaryOp::ApplyContract() => (
+        BinaryOp::ContractApply => (
             mk_uniftype::dynamic(),
             mk_uniftype::dynamic(),
             mk_uty_arrow!(mk_uniftype::dynamic(), mk_uniftype::dynamic()),
         ),
         // Sym -> Dyn -> Dyn -> Dyn
-        BinaryOp::Unseal() => (
+        BinaryOp::Unseal => (
             mk_uniftype::sym(),
             mk_uniftype::dynamic(),
             mk_uty_arrow!(TypeF::Dyn, TypeF::Dyn),
         ),
         // forall a b. a -> b -> Bool
-        BinaryOp::Eq() => (
+        BinaryOp::Eq => (
             state.table.fresh_type_uvar(var_level),
             state.table.fresh_type_uvar(var_level),
             mk_uniftype::bool(),
         ),
         // Num -> Num -> Bool
-        BinaryOp::LessThan()
-        | BinaryOp::LessOrEq()
-        | BinaryOp::GreaterThan()
-        | BinaryOp::GreaterOrEq() => (mk_uniftype::num(), mk_uniftype::num(), mk_uniftype::bool()),
+        BinaryOp::LessThan | BinaryOp::LessOrEq | BinaryOp::GreaterThan | BinaryOp::GreaterOrEq => {
+            (mk_uniftype::num(), mk_uniftype::num(), mk_uniftype::bool())
+        }
         // Str -> Dyn -> Dyn
-        BinaryOp::GoField() => (
+        BinaryOp::LabelGoField => (
             mk_uniftype::str(),
             mk_uniftype::dynamic(),
             mk_uniftype::dynamic(),
         ),
         // forall a. Str -> { _ : a} -> a
-        BinaryOp::DynAccess() => {
+        BinaryOp::RecordGet => {
             let res = state.table.fresh_type_uvar(var_level);
 
             (mk_uniftype::str(), mk_uniftype::dict(res.clone()), res)
         }
         // forall a. Str -> {_ : a} -> a -> {_ : a}
-        BinaryOp::DynExtend {
+        BinaryOp::RecordInsert {
             ext_kind: RecordExtKind::WithValue,
             ..
         } => {
@@ -333,7 +330,7 @@ pub fn get_bop_type(
             )
         }
         // forall a. Str -> {_ : a} -> {_ : a}
-        BinaryOp::DynExtend {
+        BinaryOp::RecordInsert {
             ext_kind: RecordExtKind::WithoutValue,
             ..
         } => {
@@ -345,7 +342,7 @@ pub fn get_bop_type(
             )
         }
         // forall a. Str -> { _ : a } -> { _ : a}
-        BinaryOp::DynRemove(_) => {
+        BinaryOp::RecordRemove(_) => {
             let res = state.table.fresh_type_uvar(var_level);
             (
                 mk_uniftype::str(),
@@ -354,7 +351,7 @@ pub fn get_bop_type(
             )
         }
         // forall a. Str -> {_: a} -> Bool
-        BinaryOp::HasField(_) => {
+        BinaryOp::RecordHasField(_) => {
             let ty_elt = state.table.fresh_type_uvar(var_level);
             (
                 mk_uniftype::str(),
@@ -363,7 +360,7 @@ pub fn get_bop_type(
             )
         }
         // forall a. Str -> {_: a} -> Bool
-        BinaryOp::FieldIsDefined(_) => {
+        BinaryOp::RecordFieldIsDefined(_) => {
             let ty_elt = state.table.fresh_type_uvar(var_level);
             (
                 mk_uniftype::str(),
@@ -372,13 +369,13 @@ pub fn get_bop_type(
             )
         }
         // forall a. Array a -> Array a -> Array a
-        BinaryOp::ArrayConcat() => {
+        BinaryOp::ArrayConcat => {
             let ty_elt = state.table.fresh_type_uvar(var_level);
             let ty_array = mk_uniftype::array(ty_elt);
             (ty_array.clone(), ty_array.clone(), ty_array)
         }
         // forall a. Array a -> Num -> a
-        BinaryOp::ArrayElemAt() => {
+        BinaryOp::ArrayAt => {
             let ty_elt = state.table.fresh_type_uvar(var_level);
             (
                 mk_uniftype::array(ty_elt.clone()),
@@ -393,13 +390,13 @@ pub fn get_bop_type(
             mk_uniftype::dynamic(),
         ),
         // <Md5, Sha1, Sha256, Sha512> -> Str -> Str
-        BinaryOp::Hash() => (
+        BinaryOp::Hash => (
             mk_uty_enum!("Md5", "Sha1", "Sha256", "Sha512"),
             mk_uniftype::str(),
             mk_uniftype::str(),
         ),
         // forall a. <Json, Yaml, Toml> -> a -> Str
-        BinaryOp::Serialize() => {
+        BinaryOp::Serialize => {
             let ty_input = state.table.fresh_type_uvar(var_level);
             (
                 mk_uty_enum!("Json", "Yaml", "Toml"),
@@ -408,24 +405,24 @@ pub fn get_bop_type(
             )
         }
         // <Json, Yaml, Toml> -> Str -> Dyn
-        BinaryOp::Deserialize() => (
+        BinaryOp::Deserialize => (
             mk_uty_enum!("Json", "Yaml", "Toml"),
             mk_uniftype::str(),
             mk_uniftype::dynamic(),
         ),
         // Num -> Num -> Num
-        BinaryOp::Pow() => (mk_uniftype::num(), mk_uniftype::num(), mk_uniftype::num()),
+        BinaryOp::Pow => (mk_uniftype::num(), mk_uniftype::num(), mk_uniftype::num()),
         // Str -> Str -> Bool
-        BinaryOp::StrContains() => (mk_uniftype::str(), mk_uniftype::str(), mk_uniftype::bool()),
+        BinaryOp::StringContains => (mk_uniftype::str(), mk_uniftype::str(), mk_uniftype::bool()),
         // Str -> Str -> Array Str
-        BinaryOp::StrSplit() => (
+        BinaryOp::StringSplit => (
             mk_uniftype::str(),
             mk_uniftype::str(),
             mk_uniftype::array(TypeF::String),
         ),
         // The first argument is a contract, the second is a label.
         // forall a. Dyn -> Dyn -> Array a -> Array a
-        BinaryOp::ArrayLazyAppCtr() => {
+        BinaryOp::ContractArrayLazyApp => {
             let ty_elt = state.table.fresh_type_uvar(var_level);
             let ty_array = mk_uniftype::array(ty_elt);
             (
@@ -436,7 +433,7 @@ pub fn get_bop_type(
         }
         // The first argument is a label, the third is a contract.
         // forall a. Dyn -> {_: a} -> Dyn -> {_: a}
-        BinaryOp::RecordLazyAppCtr() => {
+        BinaryOp::ContractRecordLazyApp => {
             let ty_field = state.table.fresh_type_uvar(var_level);
             let ty_dict = mk_uniftype::dict(ty_field);
             (
@@ -447,28 +444,28 @@ pub fn get_bop_type(
         }
         // Morally: Str -> Lbl -> Lbl
         // Actual: Str -> Dyn -> Dyn
-        BinaryOp::LabelWithMessage() => (
+        BinaryOp::LabelWithMessage => (
             mk_uniftype::str(),
             mk_uniftype::dynamic(),
             mk_uniftype::dynamic(),
         ),
         // Morally: Array Str -> Lbl -> Lbl
         // Actual: Array Str -> Dyn -> Dyn
-        BinaryOp::LabelWithNotes() => (
+        BinaryOp::LabelWithNotes => (
             mk_uniftype::array(TypeF::String),
             mk_uniftype::dynamic(),
             mk_uniftype::dynamic(),
         ),
         // Morally: Str -> Lbl -> Lbl
         // Actual: Str -> Dyn -> Dyn
-        BinaryOp::LabelAppendNote() => (
+        BinaryOp::LabelAppendNote => (
             mk_uniftype::str(),
             mk_uniftype::dynamic(),
             mk_uniftype::dynamic(),
         ),
         // Morally: Sym -> Lbl -> TypeVarData
         // Actual: Sym -> Dyn -> TypeVarData
-        BinaryOp::LookupTypeVar() => (
+        BinaryOp::LabelLookupTypeVar => (
             mk_uniftype::sym(),
             mk_uniftype::dynamic(),
             TypeVarData::unif_type(),
@@ -483,17 +480,17 @@ pub fn get_nop_type(
 ) -> Result<(Vec<UnifType>, UnifType), TypecheckError> {
     Ok(match op {
         // Str -> Str -> Str -> Str
-        NAryOp::StrReplace() | NAryOp::StrReplaceRegex() => (
+        NAryOp::StringReplace | NAryOp::StringReplaceRegex => (
             vec![mk_uniftype::str(), mk_uniftype::str(), mk_uniftype::str()],
             mk_uniftype::str(),
         ),
         // Str -> Num -> Num -> Str
-        NAryOp::StrSubstr() => (
+        NAryOp::StringSubstr => (
             vec![mk_uniftype::str(), mk_uniftype::num(), mk_uniftype::num()],
             mk_uniftype::str(),
         ),
         // Dyn -> Dyn -> Dyn -> Dyn -> Dyn
-        NAryOp::RecordSealTail() => (
+        NAryOp::RecordSealTail => (
             vec![
                 mk_uniftype::dynamic(),
                 mk_uniftype::dynamic(),
@@ -503,7 +500,7 @@ pub fn get_nop_type(
             mk_uniftype::dynamic(),
         ),
         // Dyn -> Dyn -> Dyn -> Dyn
-        NAryOp::RecordUnsealTail() => (
+        NAryOp::RecordUnsealTail => (
             vec![
                 mk_uniftype::dynamic(),
                 mk_uniftype::dynamic(),
@@ -512,7 +509,7 @@ pub fn get_nop_type(
             mk_uniftype::dynamic(),
         ),
         // Num -> Num -> Array a -> Array a
-        NAryOp::ArraySlice() => {
+        NAryOp::ArraySlice => {
             let element_type = state.table.fresh_type_uvar(var_level);
 
             (
@@ -525,10 +522,10 @@ pub fn get_nop_type(
             )
         }
         // This should not happen, as MergeContract() is only produced during evaluation.
-        NAryOp::MergeContract() => panic!("cannot typecheck MergeContract()"),
+        NAryOp::MergeContract => panic!("cannot typecheck MergeContract()"),
         // Morally: Sym -> Polarity -> Lbl -> Lbl
         // Actual: Sym -> Polarity -> Dyn -> Dyn
-        NAryOp::InsertTypeVar() => (
+        NAryOp::LabelInsertTypeVar => (
             vec![
                 mk_uniftype::sym(),
                 Polarity::unif_type(),
diff --git a/core/stdlib/internals.ncl b/core/stdlib/internals.ncl
index 0a633b82..7a5b9d03 100644
--- a/core/stdlib/internals.ncl
+++ b/core/stdlib/internals.ncl
@@ -17,7 +17,7 @@
 
   "$array" = fun element_contract label value =>
     if %typeof% value == 'Array then
-      %array_lazy_app_ctr% (%go_array% label) value element_contract
+      %contract/array_lazy_apply% (%label/go_array% label) value element_contract
     else
       %blame% label,
 
@@ -32,10 +32,10 @@
     if %typeof% value == 'Function then
       (
         fun x =>
-          %apply_contract%
+          %contract/apply%
             codomain
-            (%go_codom% label)
-            (value (%apply_contract% domain (%chng_pol% (%go_dom% label)) x))
+            (%label/go_codom% label)
+            (value (%contract/apply% domain (%label/flip_polarity% (%label/go_dom% label)) x))
       )
     else
       %blame% label,
@@ -47,9 +47,9 @@
         fun x =>
           value
             (
-              %apply_contract%
+              %contract/apply%
                 domain
-                (%chng_pol% (%go_dom% label))
+                (%label/flip_polarity% (%label/go_dom% label))
                 x
             )
       )
@@ -61,9 +61,9 @@
     if %typeof% value == 'Function then
       (
         fun x =>
-          %apply_contract%
+          %contract/apply%
             codomain
-            (%go_codom% label)
+            (%label/go_codom% label)
             (value x)
       )
     else
@@ -77,8 +77,8 @@
       %blame% label,
 
   "$forall_var" = fun sealing_key label value =>
-    let current_polarity = %polarity% label in
-    let polarity = (%lookup_type_variable% sealing_key label).polarity in
+    let current_polarity = %label/polarity% label in
+    let polarity = (%label/lookup_type_variable% sealing_key label).polarity in
     if polarity == current_polarity then
       %unseal% sealing_key value (%blame% label)
     else
@@ -92,33 +92,33 @@
       # forall polarity (here `polarity`). Because we only have the `chng_pol`
       # primop, and we know that in this branch they are unequal, flipping the
       # current polarity will indeed give the original forall's polarity.
-      %seal% sealing_key (%chng_pol% label) value,
+      %seal% sealing_key (%label/flip_polarity% label) value,
 
   "$forall" = fun sealing_key polarity contract label value =>
-    contract (%insert_type_variable% sealing_key polarity label) value,
+    contract (%label/insert_type_variable% sealing_key polarity label) value,
 
   "$enum" = fun case label value =>
     if %typeof% value == 'Enum then
-      %apply_contract% case label value
+      %contract/apply% case label value
     else
-      %blame% (%label_with_message% "expected an enum" label),
+      %blame% (%label/with_message% "expected an enum" label),
 
   "$enum_fail" = fun label =>
-    %blame% (%label_with_message% "tag not in the enum type" label),
+    %blame% (%label/with_message% "tag not in the enum type" label),
 
   # Contract for an enum variant with tag `'tag`, that is any value of the form
   # `'tag exp`.
   "$enum_variant" = fun tag label value =>
-    if %enum_is_variant% value then
-      let value_tag = %enum_get_tag% value in
+    if %enum/is_variant% value then
+      let value_tag = %enum/get_tag% value in
 
       if value_tag == tag then
         value
       else
-        let msg = "expected `'%{%to_str% tag}`, got `'%{%to_str% value_tag}`" in
-        %blame% (%label_with_message% "tag mismatch: %{msg}" label)
+        let msg = "expected `'%{%to_string% tag}`, got `'%{%to_string% value_tag}`" in
+        %blame% (%label/with_message% "tag mismatch: %{msg}" label)
     else
-      %blame% (%label_with_message% "expected an enum variant" label),
+      %blame% (%label/with_message% "expected an enum variant" label),
 
   "$forall_enum_tail" = fun label value =>
     # Theoretically, we should seal/unseal values that are part of enum tail
@@ -157,63 +157,63 @@
         std.array.fold_left
           (
             fun acc field =>
-              if %has_field% field right then
+              if %record/has_field% field right then
                 acc
               else
-                %record_insert% field acc (left."%{field}")
+                %record/insert% field acc (left."%{field}")
           )
-          (%record_empty_with_tail% left)
-          (%fields% left)
+          (%record/empty_with_tail% left)
+          (%record/fields% left)
       in
       let contracts_not_in_value = field_diff field_contracts value in
-      let missing_fields = %fields% contracts_not_in_value in
-      if %length% missing_fields == 0 then
+      let missing_fields = %record/fields% contracts_not_in_value in
+      if %array/length% missing_fields == 0 then
         let tail_fields = field_diff value field_contracts in
         let fields_with_contracts =
           std.array.fold_left
             (
               fun acc field =>
-                if %has_field% field field_contracts then
+                if %record/has_field% field field_contracts then
                   let contract = field_contracts."%{field}" in
-                  let label = %go_field% field label in
+                  let label = %label/go_field% field label in
                   let val = value."%{field}" in
-                  %record_insert% field acc (%apply_contract% contract label val)
+                  %record/insert% field acc (%contract/apply% contract label val)
                 else
                   acc
             )
             {}
-            (%fields% value)
+            (%record/fields% value)
         in
         tail_contract fields_with_contracts label tail_fields
       else
-        let plural = if %length% missing_fields == 1 then "" else "s" in
+        let plural = if %array/length% missing_fields == 1 then "" else "s" in
         %blame%
           (
-            %label_with_message%
+            %label/with_message%
               "missing field%{plural} `%{std.string.join ", " missing_fields}`"
               label
           )
     else
-      %blame% (%label_with_message% "not a record" label),
+      %blame% (%label/with_message% "not a record" label),
 
   # Lazy dictionary contract for `{_ | T}`
   "$dict_contract" = fun contract label value =>
     if %typeof% value == 'Record then
-      %record_lazy_app_ctr% (%go_dict% label) value (fun _field => contract)
+      %contract/record_lazy_apply% (%label/go_dict% label) value (fun _field => contract)
     else
-      %blame% (%label_with_message% "not a record" label),
+      %blame% (%label/with_message% "not a record" label),
 
   # Eager dictionary contract for `{_ : T}`
   "$dict_type" = fun contract label value =>
     if %typeof% value == 'Record then
-      %record_map%
+      %record/map%
         value
         (
           fun _field field_value =>
-            %apply_contract% contract (%go_dict% label) field_value
+            %contract/apply% contract (%label/go_dict% label) field_value
         )
     else
-      %blame% (%label_with_message% "not a record" label),
+      %blame% (%label/with_message% "not a record" label),
 
   # A specialized version of either `{_ | Dyn}` or `{_ : Dyn}` (which are
   # equivalent), but which is constant time.
@@ -221,22 +221,22 @@
     if %typeof% value == 'Record then
       value
     else
-      %blame% (%label_with_message% "not a record" label),
+      %blame% (%label/with_message% "not a record" label),
 
   "$forall_record_tail" = fun sealing_key constr acc label value =>
-    let current_polarity = %polarity% label in
-    let polarity = (%lookup_type_variable% sealing_key label).polarity in
-    let plural = fun list => if %length% list == 1 then "" else "s" in
+    let current_polarity = %label/polarity% label in
+    let polarity = (%label/lookup_type_variable% sealing_key label).polarity in
+    let plural = fun list => if %array/length% list == 1 then "" else "s" in
     if polarity == current_polarity then
       if value == {} then
-        let tagged_label = %label_with_message% "polymorphic tail mismatch" label in
-        let tail = %record_unseal_tail% sealing_key tagged_label value in
+        let tagged_label = %label/with_message% "polymorphic tail mismatch" label in
+        let tail = %record/unseal_tail% sealing_key tagged_label value in
         acc & tail
       else
-        let extra_fields = %fields% value in
+        let extra_fields = %record/fields% value in
         %blame%
           (
-            %label_with_message%
+            %label/with_message%
               "extra field%{plural extra_fields} `%{std.string.join ", " extra_fields}`"
               label
           )
@@ -244,18 +244,18 @@
       let conflicts =
         std.array.filter
           (fun field => std.array.elem field constr)
-          (%fields% value)
+          (%record/fields% value)
       in
       if conflicts != [] then
         %blame%
           (
-            %label_with_message%
+            %label/with_message%
               "field%{plural conflicts} not allowed in tail: `%{std.string.join ", " conflicts}`"
               label
           )
       else
         # See [^forall_chng_pol]
-        %record_seal_tail% sealing_key (%chng_pol% label) acc value,
+        %record/seal_tail% sealing_key (%label/flip_polarity% label) acc value,
 
   "$dyn_tail" = fun acc label value => acc & value,
 
@@ -263,11 +263,11 @@
     if value == {} then
       acc
     else
-      let extra_fields = %fields% value in
-      let plural = if %length% extra_fields == 1 then "" else "s" in
+      let extra_fields = %record/fields% value in
+      let plural = if %array/length% extra_fields == 1 then "" else "s" in
       %blame%
         (
-          %label_with_message%
+          %label/with_message%
             "extra field%{plural} `%{std.string.join ", " extra_fields}`"
             label
         ),
diff --git a/core/stdlib/std.ncl b/core/stdlib/std.ncl
index 5c83bff4..69b3ecea 100644
--- a/core/stdlib/std.ncl
+++ b/core/stdlib/std.ncl
@@ -61,12 +61,12 @@
         "%
       = fun label value =>
         if %typeof% value == 'Array then
-          if %length% value != 0 then
+          if %array/length% value != 0 then
             value
           else
-            %blame% (%label_with_message% "empty array" label)
+            %blame% (%label/with_message% "empty array" label)
         else
-          %blame% (%label_with_message% "not a array" label),
+          %blame% (%label/with_message% "not a array" label),
 
     first
       : forall a. Array a -> a
@@ -81,7 +81,7 @@
             "this is the head"
           ```
         "%
-      = fun array => %elem_at% array 0,
+      = fun array => %array/at% array 0,
 
     last
       : forall a. Array a -> a
@@ -96,7 +96,7 @@
             "this is not"
           ```
         "%
-      = fun array => %elem_at% array (%length% array - 1),
+      = fun array => %array/at% array (%array/length% array - 1),
 
     drop_first
       : forall a. Array a -> Array a
@@ -111,7 +111,7 @@
             [ 2, 3 ]
           ```
         "%
-      = fun array => %array_slice% 1 (%length% array) array,
+      = fun array => %array/slice% 1 (%array/length% array) array,
 
     drop_last
       : forall a. Array a -> Array a
@@ -126,7 +126,7 @@
             [ 1, 2 ]
           ```
         "%
-      = fun array => %array_slice% 0 (%length% array - 1) array,
+      = fun array => %array/slice% 0 (%array/length% array - 1) array,
 
     length
       : forall a. Array a -> Number
@@ -140,7 +140,7 @@
             2
           ```
         "%
-      = fun l => %length% l,
+      = fun l => %array/length% l,
 
     map
       : forall a b. (a -> b) -> Array a -> Array b
@@ -155,7 +155,7 @@
             [ 2, 3, 4 ]
           ```
         "%
-      = fun f l => %map% l f,
+      = fun f l => %array/map% l f,
 
     at
       : forall a. Number -> Array a -> a
@@ -170,7 +170,7 @@
             "three"
           ```
         "%
-      = fun n l => %elem_at% l n,
+      = fun n l => %array/at% l n,
 
     at_or
       : forall a. Number -> a -> Array a -> a
@@ -191,8 +191,8 @@
           ```
         "%
       = fun n default_value array =>
-        if n < %length% array then
-          %elem_at% array n
+        if n < %array/length% array then
+          %array/at% array n
         else
           default_value,
 
@@ -276,7 +276,7 @@
           ```
         "%
       = fun f acc array =>
-        let length = %length% array in
+        let length = %array/length% array in
         if length == 0 then
           acc
         else
@@ -285,7 +285,7 @@
               acc
             else
               let next_acc =
-                %elem_at% array n
+                %array/at% array n
                 |> f acc
               in
               go next_acc (n + 1)
@@ -318,13 +318,13 @@
           ```
         "%
       = fun f fst l =>
-        let length = %length% l in
+        let length = %array/length% l in
         let rec go = fun n =>
           if n == length then
             fst
           else
             go (n + 1)
-            |> f (%elem_at% l n)
+            |> f (%array/at% l n)
         in go 0,
 
     prepend
@@ -495,7 +495,7 @@
             [ 0, 1, 4, 9 ]
           ```
         "%
-      = fun f n => %generate% n f,
+      = fun f n => %array/generate% n f,
 
     sort
       : forall a. (a -> a -> [| 'Lesser, 'Equal, 'Greater |]) -> Array a -> Array a
@@ -518,9 +518,9 @@
         "%
       #TODO: maybe inline partition to avoid contract checks?
       = fun cmp array =>
-        let length = %length% array in
-        let first = %elem_at% array 0 in
-        let rest = %array_slice% 1 length array in
+        let length = %array/length% array in
+        let first = %array/at% array 0 in
+        let rest = %array/slice% 1 length array in
         let parts = partition (fun x => (cmp x first == 'Lesser)) rest in
         if length <= 1 then
           array
@@ -559,12 +559,12 @@
           ```
         "%
       = fun v array =>
-        let length = %length% array in
+        let length = %array/length% array in
         if length <= 1 then
           array
         else
-          let first = %elem_at% array 0 in
-          let rest = %array_slice% 1 length array in
+          let first = %array/at% array 0 in
+          let rest = %array/slice% 1 length array in
           [first] @ (flat_map (fun a => [v, a]) rest),
 
     slice
@@ -590,7 +590,7 @@
             => [ "!" ]
            ```
         "%
-      = fun start end value => %array_slice% start end value,
+      = fun start end value => %array/slice% start end value,
 
     split_at
       : forall a. Number -> Array a -> { left : Array a, right : Array a }
@@ -618,8 +618,8 @@
         "%
       = fun index value =>
         {
-          left = %array_slice% 0 index value,
-          right = %array_slice% index (%length% value) value
+          left = %array/slice% 0 index value,
+          right = %array/slice% index (%array/length% value) value
         },
 
     replicate
@@ -641,7 +641,7 @@
             => [ "x", "x", "x", "x", "x" ]
           ```
         "%
-      = fun n x => %generate% n (fun _i => x),
+      = fun n x => %array/generate% n (fun _i => x),
 
     range_step
       : Number -> Number -> Number -> Array Number
@@ -664,7 +664,7 @@
           ```
         "%
       = fun start end step =>
-        %generate%
+        %array/generate%
           ((end - start) / step |> std.number.floor)
           (fun i => start + i * step),
 
@@ -727,8 +727,8 @@
           ```
         "%
       = fun f array =>
-        let first = %elem_at% array 0 in
-        let rest = %array_slice% 1 (%length% array) array in
+        let first = %array/at% array 0 in
+        let rest = %array/slice% 1 (%array/length% array) array in
         fold_left f first rest,
 
     reduce_right
@@ -767,9 +767,9 @@
           ```
         "%
       = fun f array =>
-        let last_index = %length% array - 1 in
-        let last = %elem_at% array last_index in
-        let rest = %array_slice% 0 last_index array in
+        let last_index = %array/length% array - 1 in
+        let last = %array/at% array last_index in
+        let rest = %array/slice% 0 last_index array in
         fold_right f last rest,
 
     zip_with
@@ -869,7 +869,7 @@
             { extra = diff.extra, missing = std.record.fields diff.rest }
           in
         let blame_fields_differ = fun qualifier fields ctr_label =>
-          let plural = if %length% fields == 1 then "" else "s" in
+          let plural = if %array/length% fields == 1 then "" else "s" in
           ctr_label
           |> label.with_message "%{qualifier} field%{plural} `%{std.string.join ", " fields}`"
           |> label.append_note "`std.contract.Equal some_record` requires that the checked value is equal to the record `some_record`, but the sets of their fields differ."
@@ -880,9 +880,9 @@
           let check_typeof_eq = fun ctr_label value =>
             let value_type = %typeof% value in
             if value_type == constant_type && value_type == 'Enum then
-              if %enum_is_variant% value != %enum_is_variant% constant then
+              if %enum/is_variant% value != %enum/is_variant% constant then
                 let enum_kind = fun x =>
-                  if %enum_is_variant% x then
+                  if %enum/is_variant% x then
                     "enum variant"
                   else
                     "enum tag"
@@ -898,7 +898,7 @@
               value
             else
               ctr_label
-              |> label.with_message "expected `%{%to_str% constant_type}`, got `%{%to_str% value_type}`"
+              |> label.with_message "expected `%{%to_string% constant_type}`, got `%{%to_string% value_type}`"
               |> label.append_note "`std.contract.Equal some_value` requires that the checked value is equal to `some_value`, but they don't have the same type."
               |> blame
           in
@@ -908,16 +908,16 @@
               # we map the constant from {field1 = val1, .., fieldn = valn} to
               # {field1 = Equal val1, .., fieldn = Equal valn}, building a
               # dictionary of equality contracts
-              let contract_map = %record_map% constant (fun _key => Equal) in
+              let contract_map = %record/map% constant (fun _key => Equal) in
               fun ctr_label value =>
                 let value = check_typeof_eq ctr_label value in
                 let diff = fields_diff constant value in
-                if %length% diff.extra != 0 then
+                if %array/length% diff.extra != 0 then
                   blame_fields_differ "extra" diff.extra ctr_label
-                else if %length% diff.missing != 0 then
+                else if %array/length% diff.missing != 0 then
                   blame_fields_differ "missing" diff.missing ctr_label
                 else
-                  %record_lazy_app_ctr%
+                  %contract/record_lazy_apply%
                     ctr_label
                     value
                     (
@@ -927,18 +927,18 @@
             'Array =>
               fun ctr_label value =>
                 let value = check_typeof_eq ctr_label value in
-                let value_length = %length% value in
-                if value_length == %length% constant then
-                  %generate%
+                let value_length = %array/length% value in
+                if value_length == %array/length% constant then
+                  %array/generate%
                     value_length
                     (
                       fun i =>
-                        %elem_at% value i
-                        |> std.contract.apply (Equal (%elem_at% constant i)) ctr_label
+                        %array/at% value i
+                        |> std.contract.apply (Equal (%array/at% constant i)) ctr_label
                     )
                 else
                   ctr_label
-                  |> label.with_message "array length mismatch (expected `%{%to_str% (%length% constant)}`, got `%{%to_str% value_length})`"
+                  |> label.with_message "array length mismatch (expected `%{%to_string% (%array/length% constant)}`, got `%{%to_string% value_length})`"
                   |> label.append_note "`std.contract.Equal some_array` requires that the checked value is equal to the array `some_array`, but their lengths differ."
                   |> blame,
 
@@ -997,7 +997,7 @@
           0 | IsZero
           ```
         "%
-      = fun message label => %blame% (%label_with_message% message label),
+      = fun message label => %blame% (%label/with_message% message label),
 
     from_predicate
       | doc m%"
@@ -1121,7 +1121,7 @@
               5 | Contract
               ```
             "%
-          = fun message label => %label_with_message% message label,
+          = fun message label => %label/with_message% message label,
 
         with_notes
           | doc m%"
@@ -1165,9 +1165,9 @@
               5 | Contract
               ```
             "%
-          # the %label_with_notes% operator expects an array of strings which is
+          # the %label/with_notes% operator expects an array of strings which is
           # fully evaluated, thus we force the notes first
-          = fun notes label => %label_with_notes% (%force% notes) label,
+          = fun notes label => %label/with_notes% (%force% notes) label,
 
         append_note
           | doc m%"
@@ -1187,7 +1187,7 @@
               in
               null | AlwaysFailWithNotes
             "%
-          = fun note label => %label_append_note% note label,
+          = fun note label => %label/append_note% note label,
       },
 
     apply
@@ -1249,7 +1249,7 @@
           using the parent contract message and note.
         "%
       = fun contract label value =>
-        %apply_contract% contract (%label_push_diag% label) value,
+        %contract/apply% contract (%label/push_diag% label) value,
 
     unstable
       | doc m%"
@@ -1302,7 +1302,7 @@
               if value >= 0 then
                 value
               else
-                %blame% (%label_append_note% "Expected a positive number, got %{%to_str% value}" label)
+                %blame% (%label/append_note% "Expected a positive number, got %{%to_string% value}" label)
             else
               value,
 
@@ -1320,7 +1320,7 @@
               checked by an associated static type annotation.
             "%
           = fun label =>
-            PosNumber (%label_with_message% "invalid range step" label),
+            PosNumber (%label/with_message% "invalid range step" label),
 
         RangeFun
           | doc m%"
@@ -1347,8 +1347,8 @@
               let RangeSecond = fun start label value =>
                 if %typeof% start == 'Number && %typeof% value == 'Number then
                   if start > value then
-                    let start_as_str = %to_str% start in
-                    let end_as_str = %to_str% value in
+                    let start_as_str = %to_string% start in
+                    let end_as_str = %to_string% value in
                     label
                     |> attach_message
                     |> label_module.append_note "Expected a range end greater than %{start_as_str} (range start), got %{end_as_str}"
@@ -1391,7 +1391,7 @@
                 let label =
                   label
                   |> attach_message
-                  |> label_module.append_note "Expected array index to be a positive integer, got %{%to_str% value} "
+                  |> label_module.append_note "Expected array index to be a positive integer, got %{%to_string% value} "
                 in
                 std.contract.apply std.number.Nat label value
               else
@@ -1400,12 +1400,12 @@
 
             let ArrayIndexSecond = fun type min_size label value =>
               if %typeof% min_size == 'Number && %typeof% value == 'Array then
-                let max_idx = type |> match { 'Index => %length% value - 1, 'Split => %length% value } in
+                let max_idx = type |> match { 'Index => %array/length% value - 1, 'Split => %array/length% value } in
                 if min_size > max_idx then
-                  let index_as_str = %to_str% min_size in
-                  let max_as_str = %to_str% max_idx in
+                  let index_as_str = %to_string% min_size in
+                  let max_as_str = %to_string% max_idx in
                   let note =
-                    if %length% value == 0 then
+                    if %array/length% value == 0 then
                       "Can't index into an empty array"
                     else
                       "Expected an array index between 0 and %{max_as_str} (included), got %{index_as_str}"
@@ -1454,7 +1454,7 @@
                 let label =
                   label
                   |> attach_message
-                  |> label_module.append_note "Expected the array slice start index to be a positive integer, got %{%to_str% value}"
+                  |> label_module.append_note "Expected the array slice start index to be a positive integer, got %{%to_string% value}"
                 in
                 std.contract.apply std.number.Nat label value
               else
@@ -1466,13 +1466,13 @@
                 if value < start then
                   label
                   |> attach_message
-                  |> label_module.append_note "Expected the array slice indices to satisfy `start <= end`, but got %{%to_str% start} (start) and %{%to_str% value} (end)"
+                  |> label_module.append_note "Expected the array slice indices to satisfy `start <= end`, but got %{%to_string% start} (start) and %{%to_string% value} (end)"
                   |> blame
                 else
                   let label =
                     label
                     |> attach_message
-                    |> label_module.append_note "Expected the array slice end index to be a positive integer, got %{%to_str% value}"
+                    |> label_module.append_note "Expected the array slice end index to be a positive integer, got %{%to_string% value}"
                   in
                   std.contract.apply std.number.Nat label value
               else
@@ -1481,9 +1481,9 @@
 
             let ArraySliceArray = fun end_index label value =>
               if %typeof% end_index == 'Number && %typeof% value == 'Array then
-                if end_index > %length% value then
-                  let index_as_str = %to_str% end_index in
-                  let size_as_str = %to_str% (%length% value) in
+                if end_index > %array/length% value then
+                  let index_as_str = %to_string% end_index in
+                  let size_as_str = %to_string% (%array/length% value) in
                   label
                   |> attach_message
                   |> label_module.append_note "Expected the slice end index to be between 0 and %{size_as_str} (array's length), got %{index_as_str}"
@@ -1541,7 +1541,7 @@
               let HasFieldSecond = fun field label value =>
                 if %typeof% field == 'String
                 && %typeof% value == 'Record
-                && !(%has_field% field value) then
+                && !(%record/has_field% field value) then
                   label
                   |> attach_message
                   |> label_module.append_note
@@ -1620,7 +1620,7 @@
         in
         %typeof% value
         |> match {
-          'String => %enum_from_str% value,
+          'String => %enum/from_string% value,
           'Enum =>
             if is_enum_variant value then
               std.contract.blame label
@@ -1648,7 +1648,7 @@
           => false
         ```
       "%
-      = fun value => std.is_enum value && !(%enum_is_variant% value),
+      = fun value => std.is_enum value && !(%enum/is_variant% value),
 
     is_enum_variant
       : Dyn -> Bool
@@ -1669,7 +1669,7 @@
           => false
         ```
       "%
-      = fun value => %enum_is_variant% value,
+      = fun value => %enum/is_variant% value,
   },
 
   function = {
@@ -1802,9 +1802,9 @@
           if value % 1 == 0 then
             value
           else
-            %blame% (%label_with_message% "not an integer" label)
+            %blame% (%label/with_message% "not an integer" label)
         else
-          %blame% (%label_with_message% "not a number" label),
+          %blame% (%label/with_message% "not a number" label),
 
     Nat
       | doc m%"
@@ -1826,9 +1826,9 @@
           if value % 1 == 0 && value >= 0 then
             value
           else
-            %blame% (%label_with_message% "not a natural" label)
+            %blame% (%label/with_message% "not a natural" label)
         else
-          %blame% (%label_with_message% "not a number" label),
+          %blame% (%label/with_message% "not a number" label),
 
     PosNat
       | doc m%"
@@ -1850,9 +1850,9 @@
           if value % 1 == 0 && value > 0 then
             value
           else
-            %blame% (%label_with_message% "not positive integer" label)
+            %blame% (%label/with_message% "not positive integer" label)
         else
-          %blame% (%label_with_message% "not a number" label),
+          %blame% (%label/with_message% "not a number" label),
 
     NonZero
       | doc m%"
@@ -1872,9 +1872,9 @@
           if value != 0 then
             value
           else
-            %blame% (%label_with_message% "non-zero" label)
+            %blame% (%label/with_message% "non-zero" label)
         else
-          %blame% (%label_with_message% "not a number" label),
+          %blame% (%label/with_message% "not a number" label),
 
     is_integer
       : Number -> Bool
@@ -2032,7 +2032,7 @@
           { hello = 2, world = 3 }
         ```
       "%
-      = fun f r => %record_map% r f,
+      = fun f r => %record/map% r f,
 
     fields
       : forall a. { _ : a } -> Array String
@@ -2056,7 +2056,7 @@
           [ "one", "two" ]
         ```
       "%
-      = fun r => %fields% r,
+      = fun r => %record/fields% r,
 
     fields_with_opts
       : forall a. { _ : a } -> Array String
@@ -2074,7 +2074,7 @@
           [ "one", "two", "three_opt" ]
         ```
       "%
-      = fun r => %fields_with_opts% r,
+      = fun r => %record/fields_with_opts% r,
 
     values
       : forall a. { _ : a } -> Array a
@@ -2088,7 +2088,7 @@
           [ 1, "world" ]
         ```
       "%
-      = fun r => %values% r,
+      = fun r => %record/values% r,
 
     has_field
       : forall a. String -> { _ : a } -> Bool
@@ -2116,7 +2116,7 @@
           => false
         ```
       "%
-      = fun field r => %has_field% field r,
+      = fun field r => %record/has_field% field r,
 
     has_field_with_opts
       : forall a. String -> { _ : a } -> Bool
@@ -2139,7 +2139,7 @@
         |> std.record.has_field_with_opts "two_opt"
           => true
       "%
-      = fun field r => %has_field_with_opts% field r,
+      = fun field r => %record/has_field_with_opts% field r,
 
     get
       : forall a. String -> { _ : a } -> a
@@ -2188,7 +2188,7 @@
         ```
       "%%
       = fun field default_value record =>
-        if %has_field% field record then
+        if %record/has_field% field record then
           record."%{field}"
         else
           default_value,
@@ -2229,7 +2229,7 @@
             => error
           ```
         "%%
-      = fun field content r => %record_insert% field r content,
+      = fun field content r => %record/insert% field r content,
 
     insert_with_opts
       : forall a. String -> a -> { _ : a } -> { _ : a }
@@ -2260,7 +2260,7 @@
             => error
           ```
         "%%
-      = fun field content r => %record_insert_with_opts% field r content,
+      = fun field content r => %record/insert_with_opts% field r content,
 
     remove
       : forall a. String -> { _ : a } -> { _ : a }
@@ -2293,7 +2293,7 @@
             => error
           ```
         "%
-      = fun field r => %record_remove% field r,
+      = fun field r => %record/remove% field r,
 
     remove_with_opts
       : forall a. String -> { _ : a } -> { _ : a }
@@ -2319,7 +2319,7 @@
             => error
           ```
         "%
-      = fun field r => %record_remove_with_opts% field r,
+      = fun field r => %record/remove_with_opts% field r,
 
     update
       : forall a. String -> a -> { _ : a } -> { _ : a }
@@ -2360,12 +2360,12 @@
         "%
       = fun field content r =>
         let r =
-          if %has_field% field r then
-            %record_remove% field r
+          if %record/has_field% field r then
+            %record/remove% field r
           else
             r
         in
-        %record_insert% field r content,
+        %record/insert% field r content,
 
     map_values
       : forall a b. (a -> b) -> { _ : a } -> { _ : b }
@@ -2422,7 +2422,7 @@
       "%
       = fun bindings =>
         bindings
-        |> std.array.fold_left (fun accum { field, value } => %record_insert% "%{field}" accum value) {},
+        |> std.array.fold_left (fun accum { field, value } => %record/insert% "%{field}" accum value) {},
 
     is_empty
       : forall a. { _ : a } -> Bool
@@ -2513,9 +2513,9 @@
           else if s == "false" then
             "false"
           else
-            %blame% (%label_with_message% "expected \"true\" or \"false\", got %{s}" l)
+            %blame% (%label/with_message% "expected \"true\" or \"false\", got %{s}" l)
         else
-          %blame% (%label_with_message% "not a string" l),
+          %blame% (%label/with_message% "not a string" l),
 
     NumberLiteral
       | doc m%"
@@ -2534,15 +2534,15 @@
       "%
       =
         let pattern = m%"^[+-]?(\d+(\.\d*)?(e[+-]?\d+)?|\.\d+(e[+-]?\d+)?)$"% in
-        let is_num_literal = %str_is_match% pattern in
+        let is_num_literal = %string/is_match% pattern in
         fun l s =>
           if %typeof% s == 'String then
             if is_num_literal s then
               s
             else
-              %blame% (%label_with_message% "invalid number literal" l)
+              %blame% (%label/with_message% "invalid number literal" l)
           else
-            %blame% (%label_with_message% "not a string" l),
+            %blame% (%label/with_message% "not a string" l),
 
     Character
       | doc m%"
@@ -2566,9 +2566,9 @@
           if length s == 1 then
             s
           else
-            %blame% (%label_with_message% "length different than one" l)
+            %blame% (%label/with_message% "length different than one" l)
         else
-          %blame% (%label_with_message% "not a string" l),
+          %blame% (%label/with_message% "not a string" l),
 
     Stringable
       | doc m%"
@@ -2627,12 +2627,12 @@
       "%
       = fun l s =>
         if %typeof% s == 'String then
-          if %str_length% s > 0 then
+          if %string/length% s > 0 then
             s
           else
-            %blame% (%label_with_message% "empty string" l)
+            %blame% (%label/with_message% "empty string" l)
         else
-          %blame% (%label_with_message% "not a string" l),
+          %blame% (%label/with_message% "not a string" l),
 
     join
       : String -> Array String -> String
@@ -2651,13 +2651,13 @@
         ```
       "%
       = fun sep fragments =>
-        let length = %length% fragments in
+        let length = %array/length% fragments in
         if length == 0 then
           ""
         else
-          let first = %elem_at% fragments 0 in
+          let first = %array/at% fragments 0 in
           let rest =
-            %array_slice% 1 length fragments
+            %array/slice% 1 length fragments
             |> std.array.fold_left (fun acc s => acc ++ sep ++ s) ""
           in
           first ++ rest,
@@ -2679,7 +2679,7 @@
           => [ "1,2,3" ]
         ```
       "%
-      = fun sep s => %str_split% s sep,
+      = fun sep s => %string/split% s sep,
 
     trim
       : String -> String
@@ -2695,7 +2695,7 @@
           => "1   2   3"
         ```
       "%
-      = fun s => %str_trim% s,
+      = fun s => %string/trim% s,
 
     characters
       : String -> Array String
@@ -2709,7 +2709,7 @@
           => [ "H", "e", "l", "l", "o" ]
         ```
       "%
-      = fun s => %str_chars% s,
+      = fun s => %string/chars% s,
 
     uppercase
       : String -> String
@@ -2728,7 +2728,7 @@
           => "HELLO.WORLD"
         ```
       "%
-      = fun s => %str_uppercase% s,
+      = fun s => %string/uppercase% s,
 
     lowercase
       : String -> String
@@ -2748,7 +2748,7 @@
           => "hello.world"
         ```
       "%
-      = fun s => %str_lowercase% s,
+      = fun s => %string/lowercase% s,
 
     contains
       : String -> String -> Bool
@@ -2769,7 +2769,7 @@
           => false
         ```
       "%
-      = fun subs s => %str_contains% s subs,
+      = fun subs s => %string/contains% s subs,
 
     replace
       : String -> String -> String -> String
@@ -2789,7 +2789,7 @@
         ```
       "%
       = fun pattern replace s =>
-        %str_replace% s pattern replace,
+        %string/replace% s pattern replace,
 
     replace_regex
       : String -> String -> String -> String
@@ -2811,7 +2811,7 @@
       ```
       "%
       = fun pattern replace s =>
-        %str_replace_regex% s pattern replace,
+        %string/replace_regex% s pattern replace,
 
     is_match
       : String -> String -> Bool
@@ -2865,7 +2865,7 @@
           => true
         ```
       "%
-      = fun regex => %str_is_match% regex,
+      = fun regex => %string/is_match% regex,
 
     find
       : String -> String -> { matched : String, index : Number, groups : Array String }
@@ -2898,7 +2898,7 @@
         regular expression will only be compiled once (see the documentation of
         `std.string.is_match` for more details).
       "%
-      = fun regex => %str_find% regex,
+      = fun regex => %string/find% regex,
 
     find_all
       : String -> String -> Array { matched : String, index : Number, groups : Array String }
@@ -2939,7 +2939,7 @@
         regular expression will only be compiled once (see the documentation of
         `std.string.is_match` for more details).
       "%
-      = fun regex => %str_find_all% regex,
+      = fun regex => %string/find_all% regex,
 
     length
       : String -> Number
@@ -2967,7 +2967,7 @@
           => 1
         ```
       "%
-      = fun s => %str_length% s,
+      = fun s => %string/length% s,
 
     substring
       : Number -> Number -> String -> String
@@ -2993,7 +2993,7 @@
           error
         ```
       "%
-      = fun start end s => %str_substr% s start end,
+      = fun start end s => %string/substr% s start end,
 
     from
       | Stringable -> String
@@ -3014,7 +3014,7 @@
           => error
         ```
       "%
-      = fun x => %to_str% x,
+      = fun x => %to_string% x,
 
     from_number
       | Number -> String
@@ -3072,7 +3072,7 @@
           => 123
         ```
       "%
-      = fun s => %num_from_str% s,
+      = fun s => %number/from_string% s,
 
     to_bool
       : String -> Bool
@@ -3107,7 +3107,7 @@
           => '"hey,there!"
         ```
       "%
-      = fun s => %enum_from_str% s,
+      = fun s => %enum/from_string% s,
   },
 
   test = {
@@ -3400,7 +3400,7 @@
         "null"
       ```
     "%
-    = fun x => %to_str% x,
+    = fun x => %to_string% x,
 
   trace
     : forall a. String -> a -> a
diff --git a/doc/manual/syntax.md b/doc/manual/syntax.md
index 4353a765..7d7dbb3d 100644
--- a/doc/manual/syntax.md
+++ b/doc/manual/syntax.md
@@ -655,6 +655,29 @@ left-associative, so `x |> f |> g` will be interpreted as `g (f x)`. For example
 "HELLO"
 ```
 
+## Primitive operators
+
+Primitive operators are low-level native functions that are built into the
+interpreter. Primitive operators are enclosed between two `%` signs, as in
+`%array/length%`. They have a fixed number of arguments and must always be fully
+applied. For example, `%array/length%` takes one argument, and writing `let op =
+%array/length% in op []` is a syntax error, while `let length = %array/length%
+[] in length` is valid. As some primitive operators return functions, they can
+sometimes be applied to more arguments than their official arity, but never
+less.
+
+Primitive operators are strict in their arguments, meaning that they force the
+evaluation of their arguments before performing any further action. Primitive
+operators are internal and you should never use them directly unless you really
+know what you are doing. They aren't part of any stability guarantee, as they
+can be added, removed or modified at any minor version update. All useful
+primitive operators are wrapped as a stdlib function, which has documentation,
+stability guarantees, can be partially applied, etc.
+
+They are mentioned here so that the reader isn't lost if they ever encounter
+them, e.g. reading the implementation of the standard library or when they
+appear in error messages.
+
 ## Pattern matching
 
 Match expressions and destructuring offer an ergonomic way to check and to
diff --git a/lsp/nls/src/analysis.rs b/lsp/nls/src/analysis.rs
index c2e904b7..1ee7e6ab 100644
--- a/lsp/nls/src/analysis.rs
+++ b/lsp/nls/src/analysis.rs
@@ -108,7 +108,7 @@ fn find_static_accesses(rt: &RichTerm) -> HashMap<Ident, Vec<RichTerm>> {
     let mut map: HashMap<Ident, Vec<RichTerm>> = HashMap::new();
     rt.traverse_ref(
         &mut |rt: &RichTerm, _scope: &()| {
-            if let Term::Op1(UnaryOp::StaticAccess(id), _) = rt.as_ref() {
+            if let Term::Op1(UnaryOp::RecordAccess(id), _) = rt.as_ref() {
                 map.entry(id.ident()).or_default().push(rt.clone());
             }
             TraverseControl::Continue::<_, ()>
diff --git a/lsp/nls/src/field_walker.rs b/lsp/nls/src/field_walker.rs
index 7bdaa8a9..38bce07b 100644
--- a/lsp/nls/src/field_walker.rs
+++ b/lsp/nls/src/field_walker.rs
@@ -447,7 +447,7 @@ impl<'a> FieldResolver<'a> {
                 combine(self.resolve_container(t1), self.resolve_container(t2))
             }
             Term::Let(_, _, body, _) | Term::LetPattern(_, _, body) => self.resolve_container(body),
-            Term::Op1(UnaryOp::StaticAccess(id), term) => {
+            Term::Op1(UnaryOp::RecordAccess(id), term) => {
                 self.containers_at_path(term, std::iter::once(id.ident()))
             }
             Term::Annotated(annot, term) => {
diff --git a/lsp/nls/src/position.rs b/lsp/nls/src/position.rs
index a2579695..290b868e 100644
--- a/lsp/nls/src/position.rs
+++ b/lsp/nls/src/position.rs
@@ -223,7 +223,7 @@ pub(crate) mod tests {
 
         // Index 23 points to the y in x.y
         let term_y = table.get(ByteIndex(23)).unwrap();
-        assert_matches!(term_y.term.as_ref(), Term::Op1(UnaryOp::StaticAccess(_), _));
+        assert_matches!(term_y.term.as_ref(), Term::Op1(UnaryOp::RecordAccess(_), _));
 
         // Index 21 points to the x in x.y
         let term_x = table.get(ByteIndex(21)).unwrap();
@@ -240,7 +240,7 @@ pub(crate) mod tests {
         let table = PositionLookup::new(&rt);
         assert_matches!(
             table.get(ByteIndex(18)).unwrap().term.as_ref(),
-            Term::Op1(UnaryOp::StaticAccess(_), _)
+            Term::Op1(UnaryOp::RecordAccess(_), _)
         );
 
         // This case has some mutual recursion between types and terms, which hit a bug in our
diff --git a/lsp/nls/src/requests/completion.rs b/lsp/nls/src/requests/completion.rs
index ef3b1c15..d556aab8 100644
--- a/lsp/nls/src/requests/completion.rs
+++ b/lsp/nls/src/requests/completion.rs
@@ -47,7 +47,7 @@ fn extract_static_path(mut rt: RichTerm) -> (RichTerm, Vec<Ident>) {
     let mut path = Vec::new();
 
     loop {
-        if let Term::Op1(UnaryOp::StaticAccess(id), parent) = rt.term.as_ref() {
+        if let Term::Op1(UnaryOp::RecordAccess(id), parent) = rt.term.as_ref() {
             path.push(id.ident());
             rt = parent.clone();
         } else {
@@ -76,7 +76,7 @@ fn sanitize_record_path_for_completion(
 
         range.end = cursor.index;
         incomplete::parse_path_from_incomplete_input(range, &env, world)
-    } else if let Term::Op1(UnaryOp::StaticAccess(_), parent) = term.term.as_ref() {
+    } else if let Term::Op1(UnaryOp::RecordAccess(_), parent) = term.term.as_ref() {
         // For completing record paths, we discard the last path element: if we're
         // completing `foo.bar.bla`, we only look at `foo.bar` to find the completions.
         Some(parent.clone())
diff --git a/lsp/nls/src/requests/hover.rs b/lsp/nls/src/requests/hover.rs
index 1299b1d5..653406cc 100644
--- a/lsp/nls/src/requests/hover.rs
+++ b/lsp/nls/src/requests/hover.rs
@@ -105,7 +105,7 @@ fn term_hover(rt: &RichTerm, world: &World) -> Option<HoverData> {
     let span = rt.pos.into_opt();
 
     match rt.as_ref() {
-        Term::Op1(UnaryOp::StaticAccess(id), parent) => {
+        Term::Op1(UnaryOp::RecordAccess(id), parent) => {
             let resolver = FieldResolver::new(world);
             let parents = resolver.resolve_record(parent);
             let (values, metadata) = values_and_metadata_from_field(parents, id.ident());
diff --git a/lsp/nls/src/world.rs b/lsp/nls/src/world.rs
index 092a4944..9b5e9a83 100644
--- a/lsp/nls/src/world.rs
+++ b/lsp/nls/src/world.rs
@@ -257,7 +257,7 @@ impl World {
                             .collect()
                     }
                 }
-                (Term::Op1(UnaryOp::StaticAccess(id), parent), _) => {
+                (Term::Op1(UnaryOp::RecordAccess(id), parent), _) => {
                     let parents = resolver.resolve_record(parent);
                     parents
                         .iter()
@@ -334,7 +334,7 @@ impl World {
                     accesses
                         .into_iter()
                         .filter_map(|access| {
-                            let Term::Op1(UnaryOp::StaticAccess(id), _) = access.as_ref() else {
+                            let Term::Op1(UnaryOp::RecordAccess(id), _) = access.as_ref() else {
                                 return None;
                             };
                             if world.get_defs(&access, None).contains(&span) {
diff --git a/notes/fixing-sealing-and-recursive-records.md b/notes/fixing-sealing-and-recursive-records.md
index 24d2046c..2ca60700 100644
--- a/notes/fixing-sealing-and-recursive-records.md
+++ b/notes/fixing-sealing-and-recursive-records.md
@@ -117,7 +117,7 @@ Final result:
 }
 ```
 
-Run of a `%record_map% { foo | Num =  1, bar | PosNat | Even = foo + 1} (fun _key ((+) 1)`
+Run of a `%record/map% { foo | Num =  1, bar | PosNat | Even = foo + 1} (fun _key ((+) 1)`
 
 ```
 App(Op1(record_map, record), function)


Test patch:
diff --git a/cli/tests/snapshot/inputs/errors/record_forall_constraints_contract.ncl b/cli/tests/snapshot/inputs/errors/record_forall_constraints_contract.ncl
index f0d16908..704996d6 100644
--- a/cli/tests/snapshot/inputs/errors/record_forall_constraints_contract.ncl
+++ b/cli/tests/snapshot/inputs/errors/record_forall_constraints_contract.ncl
@@ -1,3 +1,3 @@
 # capture = 'stderr'
 # command = ['eval']
-let f | forall r. { ; r } -> { x: Number; r } = fun r => %record_insert% "x" r 1 in f { x = 0 }
+let f | forall r. { ; r } -> { x: Number; r } = fun r => %record/insert% "x" r 1 in f { x = 0 }
diff --git a/cli/tests/snapshot/inputs/errors/record_forall_constraints_typecheck.ncl b/cli/tests/snapshot/inputs/errors/record_forall_constraints_typecheck.ncl
index 63647fc7..52c4e4ee 100644
--- a/cli/tests/snapshot/inputs/errors/record_forall_constraints_typecheck.ncl
+++ b/cli/tests/snapshot/inputs/errors/record_forall_constraints_typecheck.ncl
@@ -1,3 +1,3 @@
 # capture = 'stderr'
 # command = ['eval']
-let f | forall r. { ; r } -> { x: Number; r } = fun r => %record_insert% "x" r 1 in (f { x = 0 } : _)
+let f | forall r. { ; r } -> { x: Number; r } = fun r => %record/insert% "x" r 1 in (f { x = 0 } : _)
diff --git a/cli/tests/snapshot/snapshots/snapshot__eval_stderr_record_forall_constraints_contract.ncl.snap b/cli/tests/snapshot/snapshots/snapshot__eval_stderr_record_forall_constraints_contract.ncl.snap
index f85af091..662b27d2 100644
--- a/cli/tests/snapshot/snapshots/snapshot__eval_stderr_record_forall_constraints_contract.ncl.snap
+++ b/cli/tests/snapshot/snapshots/snapshot__eval_stderr_record_forall_constraints_contract.ncl.snap
@@ -6,7 +6,7 @@ error: contract broken by the caller
        field not allowed in tail: `x`
   ┌─ [INPUTS_PATH]/errors/record_forall_constraints_contract.ncl:3:19
   │
-3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record_insert% "x" r 1 in f { x = 0 }
+3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record/insert% "x" r 1 in f { x = 0 }
   │                   -------                                                             --------- evaluated to this expression
   │                   │                                                                    
   │                   expected type of the argument provided by the caller
@@ -14,19 +14,17 @@ error: contract broken by the caller
 note: 
   ┌─ [INPUTS_PATH]/errors/record_forall_constraints_contract.ncl:3:58
   │
-3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record_insert% "x" r 1 in f { x = 0 }
+3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record/insert% "x" r 1 in f { x = 0 }
   │                                                          ^^^^^^^^^^^^^^^^^^^^^^^ While calling to r
 
 note: 
   ┌─ [INPUTS_PATH]/errors/record_forall_constraints_contract.ncl:3:85
   │
-3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record_insert% "x" r 1 in f { x = 0 }
+3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record/insert% "x" r 1 in f { x = 0 }
   │                                                                                     ----------- (1) calling f
 
 note: 
   ┌─ [INPUTS_PATH]/errors/record_forall_constraints_contract.ncl:3:49
   │
-3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record_insert% "x" r 1 in f { x = 0 }
+3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record/insert% "x" r 1 in f { x = 0 }
   │                                                 -------------------------------- (2) calling <func>
-
-
diff --git a/cli/tests/snapshot/snapshots/snapshot__eval_stderr_record_forall_constraints_typecheck.ncl.snap b/cli/tests/snapshot/snapshots/snapshot__eval_stderr_record_forall_constraints_typecheck.ncl.snap
index 0625bf98..e4f084ea 100644
--- a/cli/tests/snapshot/snapshots/snapshot__eval_stderr_record_forall_constraints_typecheck.ncl.snap
+++ b/cli/tests/snapshot/snapshots/snapshot__eval_stderr_record_forall_constraints_typecheck.ncl.snap
@@ -5,7 +5,7 @@ expression: err
 error: multiple record row declarations
   ┌─ [INPUTS_PATH]/errors/record_forall_constraints_typecheck.ncl:3:88
   │
-3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record_insert% "x" r 1 in (f { x = 0 } : _)
+3 │ let f | forall r. { ; r } -> { x: Number; r } = fun r => %record/insert% "x" r 1 in (f { x = 0 } : _)
   │                                                                                        ^^^^^^^^^ this expression
   │
   = Found an expression with the row `x : _a`
@@ -15,5 +15,3 @@ error: multiple record row declarations
 note: while matching types
  = Expected type { ; r }
  = With inferred type { x : _a }
-
-
diff --git a/core/src/eval/tests.rs b/core/src/eval/tests.rs
index d749feb6..6af36c07 100644
--- a/core/src/eval/tests.rs
+++ b/core/src/eval/tests.rs
@@ -55,7 +55,7 @@ fn blame_panics() {
         evaluated_arg: _,
         label,
         call_stack: _,
-    }) = eval_no_import(mk_term::op1(UnaryOp::Blame(), Term::Lbl(l.clone())))
+    }) = eval_no_import(mk_term::op1(UnaryOp::Blame, Term::Lbl(l.clone())))
     {
         assert_eq!(label, l);
     } else {
@@ -95,7 +95,7 @@ fn simple_ite() {
 #[test]
 fn simple_plus() {
     let t = mk_term::op2(
-        BinaryOp::Plus(),
+        BinaryOp::Plus,
         mk_term::integer(5),
         Term::Num(Number::try_from(7.5).unwrap()),
     );
@@ -107,17 +107,14 @@ fn simple_plus() {
 
 #[test]
 fn asking_for_various_types() {
-    let num = mk_term::op1(
-        UnaryOp::Typeof(),
-        Term::Num(Number::try_from(45.3).unwrap()),
-    );
+    let num = mk_term::op1(UnaryOp::Typeof, Term::Num(Number::try_from(45.3).unwrap()));
     assert_eq!(Ok(Term::Enum("Number".into())), eval_no_import(num));
 
-    let boolean = mk_term::op1(UnaryOp::Typeof(), Term::Bool(true));
+    let boolean = mk_term::op1(UnaryOp::Typeof, Term::Bool(true));
     assert_eq!(Ok(Term::Enum("Bool".into())), eval_no_import(boolean));
 
     let lambda = mk_term::op1(
-        UnaryOp::Typeof(),
+        UnaryOp::Typeof,
         mk_fun!("x", mk_app!(mk_term::var("x"), mk_term::var("x"))),
     );
     assert_eq!(Ok(Term::Enum("Function".into())), eval_no_import(lambda));
@@ -186,7 +183,7 @@ fn imports() {
         "x",
         "lib",
         mk_term::op1(
-            UnaryOp::StaticAccess(LocIdent::from("f")),
+            UnaryOp::RecordAccess(LocIdent::from("f")),
             mk_term::var("x"),
         ),
         &mut vm,
@@ -203,7 +200,7 @@ fn interpolation_simple() {
     let mut chunks = vec![
         StrChunk::Literal(String::from("Hello")),
         StrChunk::expr(mk_term::op2(
-            BinaryOp::StrConcat(),
+            BinaryOp::StringConcat,
             mk_term::string(", "),
             mk_term::string("World!"),
         )),
@@ -230,7 +227,7 @@ fn interpolation_nested() {
         StrChunk::Literal(String::from(" How")),
         StrChunk::expr(
             Term::Op2(
-                BinaryOp::StrConcat(),
+                BinaryOp::StringConcat,
                 mk_term::string(" ar"),
                 mk_term::string("e"),
             )
@@ -375,7 +372,7 @@ fn foreign_id() {
 
     // Foreign ids cannot be compared for equality.
     let t_eq = mk_term::op2(
-        BinaryOp::Eq(),
+        BinaryOp::Eq,
         RichTerm::from(Term::ForeignId(43)),
         RichTerm::from(Term::ForeignId(42)),
     );
@@ -392,7 +389,7 @@ fn foreign_id() {
         Err(EvalError::MergeIncompatibleArgs { .. })
     );
 
-    let t_typeof = mk_term::op1(UnaryOp::Typeof(), Term::ForeignId(42));
+    let t_typeof = mk_term::op1(UnaryOp::Typeof, Term::ForeignId(42));
     let ty = eval_no_import(t_typeof).unwrap();
     let fid = LocIdent::from(Ident::new("ForeignId"));
     assert_matches!(ty, Term::Enum(f) if f == fid);
diff --git a/core/src/parser/tests.rs b/core/src/parser/tests.rs
index 3620f971..5d0783ae 100644
--- a/core/src/parser/tests.rs
+++ b/core/src/parser/tests.rs
@@ -96,9 +96,9 @@ fn strings() {
     assert_eq!(
         parse_without_pos("\"hello\" ++ \"World\" ++ \"!!\" "),
         Op2(
-            BinaryOp::StrConcat(),
+            BinaryOp::StringConcat,
             Op2(
-                BinaryOp::StrConcat(),
+                BinaryOp::StringConcat,
                 mk_single_chunk("hello"),
                 mk_single_chunk("World"),
             )
@@ -121,13 +121,13 @@ fn symbolic_strings() {
 fn plus() {
     assert_eq!(
         parse_without_pos("3 + 4"),
-        Op2(BinaryOp::Plus(), mk_term::integer(3), mk_term::integer(4)).into()
+        Op2(BinaryOp::Plus, mk_term::integer(3), mk_term::integer(4)).into()
     );
     assert_eq!(
         parse_without_pos("(true + false) + 4"),
         Op2(
-            BinaryOp::Plus(),
-            Op2(BinaryOp::Plus(), Bool(true).into(), Bool(false).into()).into(),
+            BinaryOp::Plus,
+            Op2(BinaryOp::Plus, Bool(true).into(), Bool(false).into()).into(),
             mk_term::integer(4),
         )
         .into()
@@ -145,7 +145,7 @@ fn ite() {
     assert_eq!(
         parse_without_pos("if true then 3 else 4"),
         mk_app!(
-            mk_term::op1(UnaryOp::Ite(), Bool(true)),
+            mk_term::op1(UnaryOp::IfThenElse, Bool(true)),
             mk_term::integer(3),
             mk_term::integer(4)
         )
@@ -184,12 +184,12 @@ fn lets() {
 fn unary_op() {
     assert_eq!(
         parse_without_pos("%typeof% x"),
-        mk_term::op1(UnaryOp::Typeof(), mk_term::var("x"))
+        mk_term::op1(UnaryOp::Typeof, mk_term::var("x"))
     );
     assert_eq!(
         parse_without_pos("%typeof% x y"),
         mk_app!(
-            mk_term::op1(UnaryOp::Typeof(), mk_term::var("x")),
+            mk_term::op1(UnaryOp::Typeof, mk_term::var("x")),
             mk_term::var("y")
         ),
     );
@@ -274,7 +274,7 @@ fn record_terms() {
             vec![(
                 StrChunks(vec![StrChunk::expr(mk_term::integer(123))]).into(),
                 Field::from(mk_app!(
-                    mk_term::op1(UnaryOp::Ite(), mk_term::integer(4)),
+                    mk_term::op1(UnaryOp::IfThenElse, mk_term::integer(4)),
                     mk_term::integer(5),
                     mk_term::integer(6)
                 ))
diff --git a/core/tests/integration/contract_label_path.rs b/core/tests/integration/contract_label_path.rs
index 3bc12b4b..2a3fd716 100644
--- a/core/tests/integration/contract_label_path.rs
+++ b/core/tests/integration/contract_label_path.rs
@@ -26,7 +26,7 @@ fn array_contracts_label_path_is_set_correctly() {
     res.unwrap_err().into_diagnostics(&mut files, None);
 
     let res = eval(
-        "(%elem_at% (\
+        "(%array/at% (\
             ({foo = [(fun x => \"a\")]} | {foo: Array (forall a. a -> Number)}).foo\
         ) 0) false",
     );
diff --git a/core/tests/integration/contracts_fail.rs b/core/tests/integration/contracts_fail.rs
index e7720fd3..5f7ad452 100644
--- a/core/tests/integration/contracts_fail.rs
+++ b/core/tests/integration/contracts_fail.rs
@@ -25,7 +25,7 @@ fn array() {
     res.unwrap_err().into_diagnostics(&mut files, None);
 
     let res = eval(
-        "(%elem_at% (\
+        "(%array/at% (\
             ({foo = [(fun x => \"a\")]} | {foo: Array (forall a. a -> Number)}).foo) 0) false",
     );
     match &res {
diff --git a/core/tests/integration/inputs/adts/enum_primops.ncl b/core/tests/integration/inputs/adts/enum_primops.ncl
index eb7e261b..47120e73 100644
--- a/core/tests/integration/inputs/adts/enum_primops.ncl
+++ b/core/tests/integration/inputs/adts/enum_primops.ncl
@@ -2,10 +2,10 @@
 
 
 [
-  %enum_unwrap_variant% ('Left (1+1)) == 2,
-  !(%enum_is_variant% 'Right),
-  %enum_is_variant% ('Right 1),
-  %enum_get_tag% 'Right == 'Right,
-  %enum_get_tag% ('Right "stuff") == 'Right,
+  %enum/unwrap_variant% ('Left (1+1)) == 2,
+  !(%enum/is_variant% 'Right),
+  %enum/is_variant% ('Right 1),
+  %enum/get_tag% 'Right == 'Right,
+  %enum/get_tag% ('Right "stuff") == 'Right,
 ]
 |> std.test.assert_all
diff --git a/core/tests/integration/inputs/arrays/elem_at_empty_array.ncl b/core/tests/integration/inputs/arrays/elem_at_empty_array.ncl
index fd22c36d..598dd3c2 100644
--- a/core/tests/integration/inputs/arrays/elem_at_empty_array.ncl
+++ b/core/tests/integration/inputs/arrays/elem_at_empty_array.ncl
@@ -2,4 +2,4 @@
 #
 # [test.metadata]
 # error = 'EvalError::Other'
-%elem_at% [] 0
+%array/at% [] 0
diff --git a/core/tests/integration/inputs/arrays/elem_at_index_out_of_bounds.ncl b/core/tests/integration/inputs/arrays/elem_at_index_out_of_bounds.ncl
index 994c66a7..0266b977 100644
--- a/core/tests/integration/inputs/arrays/elem_at_index_out_of_bounds.ncl
+++ b/core/tests/integration/inputs/arrays/elem_at_index_out_of_bounds.ncl
@@ -2,4 +2,4 @@
 #
 # [test.metadata]
 # error = 'EvalError::Other'
-%elem_at% [true, false, true] 3
\ No newline at end of file
+%array/at% [true, false, true] 3
diff --git a/core/tests/integration/inputs/arrays/elem_at_negative_index.ncl b/core/tests/integration/inputs/arrays/elem_at_negative_index.ncl
index 64c5ecfc..b7bdcd6b 100644
--- a/core/tests/integration/inputs/arrays/elem_at_negative_index.ncl
+++ b/core/tests/integration/inputs/arrays/elem_at_negative_index.ncl
@@ -2,4 +2,4 @@
 #
 # [test.metadata]
 # error = 'EvalError::Other'
-%elem_at% [1, 2, 3] (-1)
+%array/at% [1, 2, 3] (-1)
diff --git a/core/tests/integration/inputs/arrays/elem_at_non_array_arg.ncl b/core/tests/integration/inputs/arrays/elem_at_non_array_arg.ncl
index a9e80345..dede8fc4 100644
--- a/core/tests/integration/inputs/arrays/elem_at_non_array_arg.ncl
+++ b/core/tests/integration/inputs/arrays/elem_at_non_array_arg.ncl
@@ -2,4 +2,4 @@
 #
 # [test.metadata]
 # error = 'EvalError::NAryPrimopTypeError'
-%elem_at% {} 0
+%array/at% {} 0
diff --git a/core/tests/integration/inputs/arrays/elem_at_non_int_index.ncl b/core/tests/integration/inputs/arrays/elem_at_non_int_index.ncl
index 3755abad..73b2bc12 100644
--- a/core/tests/integration/inputs/arrays/elem_at_non_int_index.ncl
+++ b/core/tests/integration/inputs/arrays/elem_at_non_int_index.ncl
@@ -2,4 +2,4 @@
 #
 # [test.metadata]
 # error = 'EvalError::Other'
-%elem_at% [1, 2, 3] 0.5
\ No newline at end of file
+%array/at% [1, 2, 3] 0.5
diff --git a/core/tests/integration/inputs/arrays/slice_empty_array.ncl b/core/tests/integration/inputs/arrays/slice_empty_array.ncl
index 60b1e034..10e166e7 100644
--- a/core/tests/integration/inputs/arrays/slice_empty_array.ncl
+++ b/core/tests/integration/inputs/arrays/slice_empty_array.ncl
@@ -2,4 +2,4 @@
 #
 # [test.metadata]
 # error = 'EvalError::Other'
-%array_slice% 1 1 []
+%array/slice% 1 1 []
diff --git a/core/tests/integration/inputs/arrays/slice_non_array.ncl b/core/tests/integration/inputs/arrays/slice_non_array.ncl
index 7576da03..b93e00be 100644
--- a/core/tests/integration/inputs/arrays/slice_non_array.ncl
+++ b/core/tests/integration/inputs/arrays/slice_non_array.ncl
@@ -2,4 +2,4 @@
 #
 # [test.metadata]
 # error = 'EvalError::NAryPrimopTypeError'
-%array_slice% 0 1 {}
+%array/slice% 0 1 {}
diff --git a/core/tests/integration/inputs/contracts/contracts.ncl b/core/tests/integration/inputs/contracts/contracts.ncl
index 7dc66a6f..fdddca02 100644
--- a/core/tests/integration/inputs/contracts/contracts.ncl
+++ b/core/tests/integration/inputs/contracts/contracts.ncl
@@ -69,12 +69,12 @@ let Assert = std.test.Assert in
 
   # polymorphism
   (let id | forall a. { ; a} -> { ; a} = fun x => x in
-    # Note that we have to use `%record_insert%` and `%record_remove%` here, as
+    # Note that we have to use `%record/insert%` and `%record/remove%` here, as
     # `record.insert` and `record.remove` enforce the `$dyn_record` contract on
-    # their record arguments. `$dyn_record` uses `%record_map%` internally, and
+    # their record arguments. `$dyn_record` uses `%record/map%` internally, and
     # mapping over a sealed record is currently forbidden.
-    let extend | forall a. { ; a} -> {foo: Number ; a} = fun x => %record_insert% "foo" x 1 in
-    let remove | forall a. {foo: Number ; a} -> { ; a} = fun x => %record_remove% "foo" x in
+    let extend | forall a. { ; a} -> {foo: Number ; a} = fun x => %record/insert% "foo" x 1 in
+    let remove | forall a. {foo: Number ; a} -> { ; a} = fun x => %record/remove% "foo" x in
 
     (id {} == {} | Assert) &&
     (id {a = 1, b = false} == {a = 1, b = false} | Assert) &&
@@ -162,9 +162,9 @@ let Assert = std.test.Assert in
   # this tests that shadowing forall variables is treated correctly
 
   let g | forall r. { ; r } -> { z : Number ; r }
-        = fun r => %record_insert% "z" r 1 in
+        = fun r => %record/insert% "z" r 1 in
   let f | forall r. { ; r } -> { g : forall r. { ; r } -> { z : Number ; r }; r }
-        = fun r => %record_insert% "g" r g in
+        = fun r => %record/insert% "g" r g in
   let res = f { z = 3 }
   in std.seq res true,
 
diff --git a/core/tests/integration/inputs/contracts/poly_record_contract_map_sealed_tail.ncl b/core/tests/integration/inputs/contracts/poly_record_contract_map_sealed_tail.ncl
index 1048d275..59565036 100644
--- a/core/tests/integration/inputs/contracts/poly_record_contract_map_sealed_tail.ncl
+++ b/core/tests/integration/inputs/contracts/poly_record_contract_map_sealed_tail.ncl
@@ -3,5 +3,5 @@
 # [test.metadata]
 # error = 'EvalError::IllegalPolymorphicTailAccess'
 let f | forall r. { a : Number; r } -> { a : String; r } =
-  fun r => %record_map% r (fun x => %to_str% x)
-in f { a = 1, b = 2 }
\ No newline at end of file
+  fun r => %record/map% r (fun x => %to_string% x)
+in f { a = 1, b = 2 }
diff --git a/core/tests/integration/inputs/contracts/poly_record_contract_sealed_tail_remove_field.ncl b/core/tests/integration/inputs/contracts/poly_record_contract_sealed_tail_remove_field.ncl
index 6ecf0a4e..8a2dd32c 100644
--- a/core/tests/integration/inputs/contracts/poly_record_contract_sealed_tail_remove_field.ncl
+++ b/core/tests/integration/inputs/contracts/poly_record_contract_sealed_tail_remove_field.ncl
@@ -2,5 +2,5 @@
 #
 # [test.metadata]
 # error = 'EvalError::IllegalPolymorphicTailAccess'
-let remove_x | forall r. { ; r } -> { ; r } = fun r => %record_remove% "x" r in
+let remove_x | forall r. { ; r } -> { ; r } = fun r => %record/remove% "x" r in
 remove_x { x = 1 }
diff --git a/core/tests/integration/inputs/contracts/record_contract_constraint.ncl b/core/tests/integration/inputs/contracts/record_contract_constraint.ncl
index 66b580c9..a6c96800 100644
--- a/core/tests/integration/inputs/contracts/record_contract_constraint.ncl
+++ b/core/tests/integration/inputs/contracts/record_contract_constraint.ncl
@@ -3,5 +3,5 @@
 # [test.metadata]
 # error = 'EvalError::BlameError'
 let f | forall r. { ; r } -> { x : { y : Number ; r } ; r }
-      = fun r => %record_insert% "x" r (%record_insert% "y" r 1) in
+      = fun r => %record/insert% "x" r (%record/insert% "y" r 1) in
 f { y = 3 }
diff --git a/core/tests/integration/inputs/core/arrays.ncl b/core/tests/integration/inputs/core/arrays.ncl
index 262f5684..841d5561 100644
--- a/core/tests/integration/inputs/core/arrays.ncl
+++ b/core/tests/integration/inputs/core/arrays.ncl
@@ -19,17 +19,17 @@
   in std.array.sort cmp [3, 42, -1, -5] == [-5, -1, 3, 42],
 
   # Test case added after https://github.com/tweag/nickel/issues/154
-  let x = 1 in let l = [x] @ [2] in (%elem_at% l 0) == 1,
+  let x = 1 in let l = [x] @ [2] in (%array/at% l 0) == 1,
 
   let Y = fun f => (fun x => f (x x)) (fun x => f (x x)) in
     let foldr_ =
       fun self => fun f acc array =>
-        let length = %length% array in
+        let length = %array/length% array in
         if length == 0 then
           acc
         else
-          let head = %elem_at% array 0 in
-          let tail = %array_slice% 1 length array in
+          let head = %array/at% array 0 in
+          let tail = %array/slice% 1 length array in
           let next_acc = self f acc tail in
           f next_acc head in
     let foldr = Y foldr_ in
@@ -39,7 +39,7 @@
                 if y then true else false
             else false
     in
-    let all = fun pred array => foldr and true (%map% array pred) in
+    let all = fun pred array => foldr and true (%array/map% array pred) in
     let isZ = fun x => x == 0 in
     all isZ [0, 0, 0, 1] == false,
 
diff --git a/core/tests/integration/inputs/merging/metavalues.ncl b/core/tests/integration/inputs/merging/metavalues.ncl
index 8820d9ce..d0704cf6 100644
--- a/core/tests/integration/inputs/merging/metavalues.ncl
+++ b/core/tests/integration/inputs/merging/metavalues.ncl
@@ -127,9 +127,9 @@ let Assert = std.test.Assert in
     # (std.record.has_field "foo" value | Assert) &&
     # (std.record.has_field "opt" value | Assert) &&
     # (std.record.fields value == ["foo", "opt"] | Assert)
-    (%has_field% "foo" value | Assert) &&
-    (%has_field% "opt" value | Assert) &&
-    (%fields% value == ["foo", "opt"] | Assert)
+    (%record/has_field% "foo" value | Assert) &&
+    (%record/has_field% "opt" value | Assert) &&
+    (%record/fields% value == ["foo", "opt"] | Assert)
   ),
 ]
 |> std.test.assert_all
diff --git a/core/tests/integration/inputs/records/record_insert.ncl b/core/tests/integration/inputs/records/record_insert.ncl
index 0a451cc4..bddfd843 100644
--- a/core/tests/integration/inputs/records/record_insert.ncl
+++ b/core/tests/integration/inputs/records/record_insert.ncl
@@ -4,12 +4,12 @@
 # with respect to optional fields
 let my_insert = fun field content r =>
   let r =
-    if %has_field% field r then
-      %record_remove% field r
+    if %record/has_field% field r then
+      %record/remove% field r
     else
       r
   in
-  %record_insert% field r content
+  %record/insert% field r content
 in
 let foo | { value | optional, .. } = { bar = "hello" } in
 my_insert "value" "world" foo == { bar = "hello", value = "world" }
diff --git a/core/tests/integration/inputs/typecheck/dynamic_type_error_with_wildcard_annotation.ncl b/core/tests/integration/inputs/typecheck/dynamic_type_error_with_wildcard_annotation.ncl
index b4289e20..2fbbf3f9 100644
--- a/core/tests/integration/inputs/typecheck/dynamic_type_error_with_wildcard_annotation.ncl
+++ b/core/tests/integration/inputs/typecheck/dynamic_type_error_with_wildcard_annotation.ncl
@@ -7,4 +7,4 @@
 # [test.metadata.expectation]
 # expected = 'Array _a'
 # inferred = 'Number'
-(let head = fun l => (%elem_at% l 0) in (head 10)) : _
+(let head = fun l => (%array/at% l 0) in (head 10)) : _
diff --git a/core/tests/integration/inputs/typecheck/dynamic_type_error_without_wildcard.ncl b/core/tests/integration/inputs/typecheck/dynamic_type_error_without_wildcard.ncl
index 82d1aa39..342fe24f 100644
--- a/core/tests/integration/inputs/typecheck/dynamic_type_error_without_wildcard.ncl
+++ b/core/tests/integration/inputs/typecheck/dynamic_type_error_without_wildcard.ncl
@@ -1,4 +1,4 @@
 # test.type = 'pass'
 # eval = 'typecheck'
-let head = fun l => (%elem_at% l 0)
+let head = fun l => (%array/at% l 0)
 in head 10
diff --git a/core/tests/integration/inputs/typecheck/mismatch_array_elem_at.ncl b/core/tests/integration/inputs/typecheck/mismatch_array_elem_at.ncl
index 53527b94..f14488a2 100644
--- a/core/tests/integration/inputs/typecheck/mismatch_array_elem_at.ncl
+++ b/core/tests/integration/inputs/typecheck/mismatch_array_elem_at.ncl
@@ -7,4 +7,4 @@
 # [test.metadata.expectation]
 # expected = 'b'
 # inferred = 'a'
-(fun l => %elem_at% l 0) : forall a b. (Array a -> b)
+(fun l => %array/at% l 0) : forall a b. (Array a -> b)
diff --git a/core/tests/integration/inputs/typecheck/mismatch_array_map.ncl b/core/tests/integration/inputs/typecheck/mismatch_array_map.ncl
index d5378a38..35d9745f 100644
--- a/core/tests/integration/inputs/typecheck/mismatch_array_map.ncl
+++ b/core/tests/integration/inputs/typecheck/mismatch_array_map.ncl
@@ -7,4 +7,4 @@
 # [test.metadata.expectation]
 # expected = 'Dyn -> _a'
 # inferred = 'a -> b'
-(fun f l => %map% l f) : forall a b. (a -> b) -> Array Dyn -> b
+(fun f l => %array/map% l f) : forall a b. (a -> b) -> Array Dyn -> b
diff --git a/core/tests/integration/inputs/typecheck/typechecking.ncl b/core/tests/integration/inputs/typecheck/typechecking.ncl
index 4d8afbc2..f9820a08 100644
--- a/core/tests/integration/inputs/typecheck/typechecking.ncl
+++ b/core/tests/integration/inputs/typecheck/typechecking.ncl
@@ -60,7 +60,7 @@ let typecheck = [
 
   # enums_complex
   ((fun x => x |> match {'bla => 1, 'ble => 2}) : [|'bla, 'ble |] -> Number),
-  ((fun x => %embed% bli x |> match {'bla => 1, 'ble => 2, 'bli => 4})
+  ((fun x => %enum/embed% bli x |> match {'bla => 1, 'ble => 2, 'bli => 4})
     : [|'bla, 'ble |] -> Number),
   ((fun x =>
       (x |> match {'bla => 3, 'bli => 2})
@@ -122,9 +122,9 @@ let typecheck = [
   (fun x => [x]) : forall a. a -> Array a,
 
   # arrays_ops
-  (fun f l => %map% l f) : forall a b. (a -> b) -> Array a -> Array b,
+  (fun f l => %array/map% l f) : forall a b. (a -> b) -> Array a -> Array b,
   (fun l1 => fun l2 => l1 @ l2) : forall a. Array a -> Array a -> Array a,
-  (fun i l => %elem_at% l i) : forall a. Number -> Array a -> a,
+  (fun i l => %array/at% l i) : forall a. Number -> Array a -> a,
 
   # recursive_records
   {a : Number = 1, b = a + 1} : {a : Number, b : Number},
diff --git a/core/tests/integration/pretty.rs b/core/tests/integration/pretty.rs
index 0892fe79..3e523e93 100644
--- a/core/tests/integration/pretty.rs
+++ b/core/tests/integration/pretty.rs
@@ -68,7 +68,7 @@ fn negative_numbers() {
     eval(format!(
         "{}",
         mk_app!(
-            Term::Op1(UnaryOp::StaticAccess("is_number".into()), make::var("std")),
+            Term::Op1(UnaryOp::RecordAccess("is_number".into()), make::var("std")),
             Term::Num((-5).into())
         )
     ))