Blog › ICP guides
MoonScript developer on retainer: class system, self semantics, Lua interop, backslash call syntax, and MoonScript Lua-targeting programming on monthly retainer
September 26, 2026 · ~15 min read
A MoonScript game engine was built on top of Love2D. The developer had written a Renderer class with a draw method and was calling it from an EntityManager that held a reference to an external renderer object. Inside a method on EntityManager, the developer wrote other.draw(self) to invoke the renderer’s draw method, reasoning that passing self as the argument was the correct way to provide the method receiver — following the mental model of dotcall with explicit instance. In MoonScript, dotcall other.draw(self) desugars to Lua’s other.draw(self), passing self as the first positional argument to the function. The draw method received self — the EntityManager instance — as its receiver, not the Renderer instance it was defined on. Every position, sprite, and color lookup inside draw read from the wrong object. Wrong-receiver calls per method invocation: 3. The developer restructured the calls to use MoonScript’s backslash call syntax: other\draw(), which desugars to Lua’s other:draw() passing other as the implicit receiver. The draw method received the correct Renderer instance and all field lookups resolved correctly. Wrong-receiver calls per method invocation: 3 → 0. The MoonScript developer on retainer diagnosed the call-syntax confusion: in MoonScript, the backslash \ is not cosmetic — it is the operator that selects colon-call semantics, passing the left-hand object as the implicit first argument; the dotcall . produces a plain function call that passes its argument list exactly as written, with no implicit receiver injection.
The work log entry read “fixed renderer dispatch, 6h.” It names the result and duration. It cannot explain why the dotcall-vs-backslash distinction is non-obvious in MoonScript — both forms compile to Lua, both look like method calls at the source level, but their runtime behavior is entirely different: dotcall retrieves the function by field lookup and calls it with the supplied argument list; backslash call retrieves the function and prepends the object to the argument list as the implicit first parameter, matching the convention that MoonScript instance methods expect self as their first argument. It cannot explain when fat-arrow => method binding is necessary — a method defined with thin-arrow -> in a MoonScript class body is a regular function that happens to expect self as its first parameter; when that method is passed as a first-class value (as a callback to a Lua API, an event handler in a game loop, or a timer function), Lua calls it with zero implicit arguments and self is nil; a method defined with fat-arrow => desugars to a closure that captures self at definition time, so it works correctly regardless of how it is invoked. It cannot explain how MoonScript comprehensions differ from imperative Lua table construction — a list comprehension [f x for x in *list] constructs a new sequence table; a table comprehension {k, v for k, v in pairs t} constructs a new key-value table; filter conditions inline into the comprehension form; nested comprehensions flatten naturally; the comprehension form is not just shorter code but a different data flow: each element is computed independently, enabling the MoonScript compiler to emit clean Lua without intermediate variables. The 6 hours of call-syntax audit, fat-arrow conversion, and comprehension refactoring are invisible in the diff.
MoonScript call syntax: backslash call, dotcall, @-shorthand, and fat-arrow method binding
MoonScript compiles to Lua and its class system is built on Lua metatables. MoonScript’s call syntax has three distinct forms, each producing different Lua output. The dotcall obj.method(arg) compiles to Lua’s obj.method(arg) — a table field lookup followed by a plain function call with arg as the sole argument. The backslash call obj\method(arg) compiles to Lua’s obj:method(arg) — a colon-call that prepends obj to the argument list so the method receives (obj, arg) as its parameters. Inside a MoonScript class instance method, the shorthand forms @method(arg) and self\method(arg) are equivalent: both compile to self:method(arg) passing the current instance as the implicit receiver. The shorthand @field compiles to self.field — a field access on the current instance. Understanding these three forms is the core of MoonScript class diagnostics: any place in the source where a method is invoked on a specific object instance should use backslash call \ or the @ shorthand for self-calls; dotcall is reserved for non-method function references stored in table fields (module functions, static utilities, callbacks that are plain functions not instance methods).
Fat-arrow => in MoonScript method definitions is the mechanism for self-binding: draw: => @sprites[1]\render() desugars to a closure that captures self at the time the class instance is created, so passing instance.draw as a callback to any external API works correctly regardless of calling convention. Thin-arrow -> does not capture self: draw: -> @sprites[1]\render() produces a plain function that expects its first positional argument to be the instance; calling it as a plain function with no arguments (as most callback APIs do) sets self to nil and every @field access raises a nil-indexing error. The pattern for game loop and event-driven programming in MoonScript: any method that will be stored in a Lua API (Love2D’s love.update, love.draw, or a timer callback) must be defined with => fat arrow. MoonScript was created by Leaf Corcoran and first released in 2011. Its primary use cases are Love2D game development, Lapis web framework applications (which run on OpenResty/Nginx), and scripting layers in Lua-based games and tools. Its closest retainer neighbors are Lua developer retainers (MoonScript compiles to Lua and every MoonScript retainer involves reading generated Lua output) and Haskell developer retainers (both have comprehension syntax as a core idiom), but MoonScript’s backslash-vs-dotcall class dispatch semantics, fat-arrow self-binding closures, and Lua interop boundary make the retainer work distinct.
MoonScript class system: extend, super, new, and reading compiled Lua for dispatch debugging
MoonScript class inheritance uses the extend keyword: class Renderer extends Drawable sets up the metatable chain so that methods not defined in Renderer are looked up in Drawable. The super() call inside new invokes the parent constructor; missing super() when the parent constructor initializes fields used by child methods is a common source of nil-field errors that manifest several stack frames away from the missing call. MoonScript’s new method is the constructor; it receives self as the first argument (the new, empty instance) followed by any constructor arguments. Class-level fields (defined outside methods in the class body) are shared across all instances and behave as prototype properties, not per-instance state; retainer work often involves identifying class-level mutable state that was intended to be per-instance and moving it into new.
Reading the compiled Lua output is the most reliable debugging technique for MoonScript class dispatch issues. moonc produces a .lua file with readable output that makes every backslash-vs-dotcall decision explicit: backslash calls appear as colon calls, fat-arrow closures appear as explicit closure wrappers, and class inheritance appears as explicit metatable setup code. When a MoonScript retainer engagement involves subtle dispatch errors, the standard approach is: compile the offending file with moonc -p (print-only mode) to inspect the Lua output, identify the exact colon-vs-dot call that produces wrong behavior, and trace back to the MoonScript source to determine whether the fix is a backslash-call conversion, a fat-arrow method rebinding, or an extend chain correction. The compiled output also reveals when MoonScript’s implicit variable handling has captured an outer-scope variable in a closure when a local was intended — a class of bug that is invisible in MoonScript source but visible in the Lua output as an upvalue reference.
How HourTab tracks MoonScript developer retainer hours
MoonScript retainer work carries the invisible-hours problem specific to language-transparent-compilation toolchains: the bug is in the source, the evidence is in the compiled output, and the connection between them requires knowing MoonScript’s desugaring rules. The wrong-receiver dotcall pattern described above is the most common correctness issue in MoonScript programs written by developers coming from languages where object method call syntax is unambiguous: they model obj.method() as an instance method call (as in Python or Ruby), write dotcalls throughout the codebase, and encounter wrong-receiver behavior only when one object is calling a method on a different object — where the distinction between dotcall (no implicit receiver) and backslash call (object as receiver) becomes observable. Diagnosing this requires understanding MoonScript’s compilation to Lua, the Lua colon-call convention, and how the backslash operator selects that convention at the MoonScript source level. A retainer engagement typically involves call-site audit (every method invocation on a named instance verified to use backslash call), fat-arrow audit (every method passed as a callback verified to use =>), and comprehension audit (imperative table-building loops considered for comprehension conversion where the intent is a uniform transformation).
HourTab gives MoonScript developers a public retainer-hours URL they send to clients — typically Love2D game studios building game logic in MoonScript, Lapis web application teams using MoonScript on OpenResty, and scripting engineers embedding MoonScript in Lua-based tools for level design, content pipelines, and configuration systems. For MoonScript retainers, each work log entry should name the mechanism (call syntax: backslash call \ restructuring, @method() self-dispatch, dotcall wrong-receiver diagnosis; fat-arrow: => closure binding, -> to => conversion, callback nil-self fix; class: extend chain, super() parent constructor, new per-instance field initialization; comprehension: list [f x for x in *list], table {k,v for k,v in pairs t}, filter condition), the specific class and method name, and the before/after wrong-receiver or nil-access count. MoonScript retainers are often compared to Lua developer retainers for the shared Lua runtime context, but MoonScript’s backslash-vs-dotcall semantic distinction, fat-arrow self-binding closure model, moonc-compiled output inspection workflow, and comprehension-driven data transformation idioms make the retainer work distinct in call-site dispatch engineering, callback binding design, and class inheritance correctness. HourTab’s work log makes the call-site audit, fat-arrow conversion, and Lua output inspection visible to clients who would otherwise see only the symptom — methods operating on wrong objects with nil field access errors — and not understand why the fix required knowing that MoonScript’s dotcall and backslash call are different Lua call forms, and why the backslash operator is the one that correctly passes the left-hand object as the implicit first argument to its method.
Track MoonScript developer retainer hours without the status emails
HourTab gives MoonScript developers a public URL per client retainer. One link, no login, live burn-down. Your clients stop asking “how many hours do I have left?” and your call-site audit log — backslash call restructuring, fat-arrow conversion, Lua output inspection — becomes the proof of value that gets the retainer renewed.
See HourTab pricing →FAQ: MoonScript developer retainers
What does a MoonScript developer on retainer typically do?
A MoonScript developer on monthly retainer covers MoonScript class system (class declaration with indented method bodies; new constructor; self implicit first parameter; @field as self.field; @method() as self\method(); extend for inheritance; super() for parent constructor; => fat arrow for self-binding; -> thin arrow for plain functions), MoonScript call syntax (obj\method() backslash call desugars to obj:method() passing obj as receiver; obj.method() dotcall passes no implicit receiver; @method() or self\method() for self-dispatch; wrong call form is the most common MoonScript class bug), and comprehensions and Lua interop (list comprehensions [f(x) for x in *list]; table comprehensions {k, v for k, v in pairs(t)}; filter conditions; require() for Lua modules; moonscript.require for MoonScript modules; type() runtime checks; # length operator; _G global table).
What MoonScript work is most commonly underlogged in a retainer?
Wrong-receiver dotcall diagnosis (developer wrote other.draw(self) expecting other’s method to receive other as its instance; dotcall passes self as the positional first argument — wrong instance; wrong-receiver calls: 3/method; restructured to other\draw(); wrong-receiver calls: 3/method → 0; 5–9 hrs invisible); fat-arrow binding (method defined with -> passed as callback; self is nil at call time; converted to => fat arrow to capture self at definition; 4–8 hrs invisible); class-level vs per-instance fields (mutable field in class body shared across instances; moved to new for per-instance initialization; 4–7 hrs invisible); comprehension idiom adoption (imperative for loop replaced with list or table comprehension; filter condition inlined; 3–6 hrs invisible).
What are typical MoonScript developer retainer rates?
Entry-level MoonScript developers (1–2 years, basic class syntax, Lua interop, moonc workflow) bill at $55–$100/hr. Mid-level MoonScript Lua programmers (2–4 years, class hierarchies with extend, fat-arrow callback binding, comprehension-driven data transformation, moonscript.require module design) bill at $90–$165/hr. Senior MoonScript systems developers (4–8 years, metatable manipulation, Lua C API integration, performance optimization via compiled Lua inspection, large-scale Love2D or OpenResty architecture) bill at $130–$245/hr. Monthly retainer ranges: $2,000–$4,000/mo advisory (15–25 hrs), $5,500–$14,000/mo for full MoonScript application engineering.
What should a MoonScript developer retainer agreement include?
A MoonScript developer retainer agreement should specify: call syntax scope (backslash call obj\method() vs dotcall obj.method(); @method() self-dispatch shorthand; fat-arrow => binding for callbacks; thin-arrow -> for plain functions; correct call form selection at each call site); class system scope (class declaration; extend for inheritance; super() parent constructor; new; @field vs local; per-instance vs class-level field placement); comprehension scope (list comprehension; table comprehension; filter condition; nested comprehension); Lua interop scope (require vs moonscript.require; _G global; type() checks; metatables; Love2D or OpenResty platform API); and hour logging format (call-site category: backslash call restructuring, fat-arrow conversion, comprehension refactor, Lua interop; specific method name and before/after wrong-receiver count).
How should MoonScript developer retainer hours be logged?
Log each MoonScript retainer session with: call-site category (backslash call: obj\method() restructuring, @method() dispatch, wrong-receiver dotcall diagnosis; fat-arrow: => binding, -> to => conversion, callback self nil fix; comprehension: list [f x for x in *list], table {k,v for k,v in pairs t}, filter condition; Lua interop: require boundary, _G access, type() check, metatable operation); the specific class name, method name, and before/after wrong-receiver count (class: Renderer; method: draw; original: other.draw(self) — self passed as receiver, other.pos read as self.pos giving nil; wrong-receiver calls: 3/frame; fix: other\draw() — other passed as receiver, other.pos read correctly; wrong-receiver calls: 3/frame → 0); and the before/after metric. Include whether fix required backslash call restructuring, fat-arrow rebinding for callback context, comprehension conversion from imperative loop, or require boundary correction.