Blog › ICP guides

Boo developer on retainer: duck typing, MissingMemberException, interface-based static dispatch, and compile-time macro systems on monthly retainer

October 1, 2026 · ~15 min read

A Boo developer was building a plugin system and declared the central plugin variable as duck plugin — using Boo’s first-class duck type keyword. The developer expected that calling a method that did not exist on the variable would produce a compile-time error, similar to how Boo’s normal static types catch missing methods at compile time. Instead, Boo’s duck type performs all method dispatch at runtime via .NET reflection: the call plugin.execute(context) compiled without complaint, ran without complaint at first, and then threw MissingMemberException at runtime when the object assigned to plugin turned out not to have an execute method. The developer had assumed duck worked similarly to Python’s type hints — informational annotations checked at runtime with clear errors when violated. Boo’s duck means something specific and different: “allow any method call on this variable at compile time, dispatch via reflection at runtime, throw MissingMemberException at runtime if the method is missing.” The fix: change the variable declaration from duck plugin to plugin as IPlugin. Boo then performed compile-time type checking: two assignments in the call chain that assigned objects not implementing IPlugin were immediately caught by booc before any runtime execution. Compile-time errors caught: 0 → 2. Runtime MissingMemberException: 1 → 0.

The work log said “fixed plugin dispatch crash, 5h.” It cannot explain what duck type semantics are in Boo versus Python duck typing, why MissingMemberException is different from Python’s AttributeError, or how replacing one keyword with an interface type declaration moves an entire class of errors from runtime to compile time. A client reading that entry sees a five-hour fix for what appears to be a one-word change: duck → as IPlugin. What is invisible is the diagnostic cost: confirming that plugin.execute(context) compiled without error (it did, because duck suppresses compile-time method resolution), determining which of the several assignments in the plugin loading pipeline introduced the wrong object type, designing the IPlugin interface with the correct method signatures, updating all class declarations to explicitly implement IPlugin, running booc to surface the two type-mismatch assignments that were previously invisible, and confirming that no remaining duck variables were masking additional type errors. The five hours were not spent typing one word; they were spent performing an analysis that moved error detection two stages earlier in the development cycle.

Boo language overview: statically typed Python syntax for .NET

Boo was created by Rodrigo B. De Oliveira and first released around 2004, targeting the .NET and Mono platforms. Its design goal is the ergonomics of Python — indentation-based syntax, list comprehensions, string interpolation, concise class and method declarations — combined with .NET performance characteristics and static type safety. A Boo program compiles via booc to standard .NET IL (Intermediate Language) and runs on any .NET or Mono runtime. The resulting assemblies are compatible with any other .NET language; Boo code can call C# libraries and C# code can call Boo-compiled assemblies. The compiler itself is written in Boo, enabling the bootstrapped macro system in which Boo macros are written in Boo and processed during compilation.

Boo gained real-world adoption in two notable contexts. Unity Technologies used Boo as one of three scripting languages (alongside C# and UnityScript/JavaScript) for Unity game engine scripts before C# became the dominant and eventually sole supported language. This history means a non-trivial number of older Unity projects contain Boo scripts that require maintenance, migration, or debugging. The second major deployment context is Grasshopper, the parametric design plugin for Rhinoceros 3D (Rhino), the widely used CAD application for architecture, industrial design, and computational design. Grasshopper uses Boo as a scripting language for custom node logic, and the Grasshopper community has a body of Boo scripts that interact with Rhino’s geometry library, RhinoCommon. Grasshopper Boo scripts are a live production context: architects and computational designers use them to drive generative geometry, and retainer work in this domain involves maintaining and extending those scripts as Rhino and Grasshopper versions evolve.

Boo’s syntax is close enough to Python that Python programmers can read it immediately, but the semantic differences matter and are a consistent source of bugs in retainer work. Where Python variables are untyped and resolved at runtime, Boo variables are statically typed by default: the compiler infers the type from the assignment if no explicit declaration is provided. x = 42 in Boo makes x an int at compile time, not a dynamically typed binding. The as keyword provides explicit type annotations: plugin as IPlugin, count as int, name as string. String interpolation uses ${...} inside double-quoted strings: "hello ${name}" evaluates name and inserts its string representation. List comprehensions use [x for x in myList if condition], syntactically close to Python but producing strongly typed .NET lists. The unless condition construct is a negative conditional: unless plugin is null: plugin.execute(ctx). The null keyword is Boo’s null reference, tested with is null or is not null rather than Python’s is None.

Boo’s duck type: what it is, what it is not, and why MissingMemberException happens

The duck keyword in Boo is a first-class type that opts a variable into late binding. A variable declared as duck has all of its method calls and property accesses resolved at runtime via .NET reflection rather than at compile time via the type system. When you write plugin as duck (or equivalently in older Boo syntax, duck plugin), the Boo compiler does not validate that method calls on plugin refer to methods that exist on its actual type. The compiler emits a reflection-based dispatch call instead of a direct vtable call. At runtime, .NET’s reflection system looks up the method by name on the actual object’s Type. If the method exists, it is invoked. If it does not exist, .NET throws MissingMemberException with a message such as MissingMemberException: Method 'execute' not found on type 'ConcretePlugin'.

This is intentional. Boo’s duck type exists specifically for scenarios where static type resolution is not possible at compile time: COM interop (where COM object interfaces may not have .NET wrapper types with the correct method signatures), interoperability with dynamic .NET objects (such as objects returned from reflection or from dynamic language runtimes), and plugin systems where the developer genuinely does not know the concrete type at compile time and does not have a common interface to unify against. In those legitimate cases, duck is the right tool. The key distinction from Python is conceptual: Python’s duck typing is not a type annotation at all — Python simply has no compile-time type enforcement, and calling a method that does not exist raises AttributeError at runtime regardless of any annotations. Boo’s duck is an explicit declaration that says “I am choosing to opt out of static dispatch for this specific variable.” It is a deliberate trade-off, not an absence of the type system. Developers who use duck assuming it means “Python-style dynamic typing with better error messages” are making a semantic mistake that consistently leads to MissingMemberException in production.

The performance cost of duck is real and measurable. A normal .NET method call dispatches through a vtable: the object’s type pointer is known at compile time, the method slot is computed at compile time, and the dispatch is a single indirect branch. A duck-dispatched method call goes through System.Reflection.MethodInfo.Invoke: the runtime must look up the Type of the object, search the method table by name and parameter types, validate the signature, box value-type arguments into object parameters, and then execute the call. The overhead is typically one to three orders of magnitude greater than direct vtable dispatch. For a plugin method called once per user action, this is irrelevant. For a plugin method called inside a geometry processing loop over thousands of mesh vertices — a common pattern in Grasshopper scripts — the reflection overhead can be the dominant cost in the profile. A retainer session that diagnoses a slow Grasshopper script may trace the bottleneck to a duck-dispatched call inside the inner loop and replace it with an interface-typed variable, yielding a 50x to 100x speedup for that operation.

Boo interfaces and compile-time static dispatch

Boo’s interface declaration syntax follows its Python-like indentation model. An interface is declared with the interface keyword followed by the interface name, a colon, and then indented method signatures using def:

interface IPlugin:
    def execute(ctx as Context) as bool
    def initialize(config as PluginConfig) as void
    def getName() as string

Any class that declares these three methods with compatible signatures satisfies the IPlugin interface. To declare explicit implementation, the class name is followed by the interface name in parentheses: class LoggingPlugin(IPlugin):. The Boo compiler then verifies at compile time that LoggingPlugin implements all three methods with the correct signatures. If a method is missing or has the wrong return type, booc emits a compile error before any IL is generated. This is the standard .NET interface pattern, identical in semantics to C# interfaces, and it is the correct tool for plugin systems where callers need to call a set of methods on any of several concrete types.

When the plugin variable is changed from duck plugin to plugin as IPlugin, the Boo compiler now resolves all method calls on plugin against the IPlugin interface at compile time. plugin.execute(context) compiles only if execute is declared in IPlugin with a compatible signature. Assignments to plugin from any expression are type-checked: plugin = loader.load(name) is a compile error if loader.load returns a type that does not implement IPlugin. In the plugin system case, the two assignments that were silently assigning wrong types — one assigned a configuration object (type PluginConfig) to plugin when a condition was false, the other assigned a null-coalescing result that could return an unrelated helper class — were both caught by booc immediately after the type change. Neither would have been found by any amount of testing that did not exercise the specific branches that triggered those assignments. They existed as latent type errors in the codebase for months before the duck→interface refactor exposed them.

The general pattern for Boo interface-based dispatch: declare an interface with the methods all callers need, declare each implementing class with class Name(IInterface):, declare all caller-side variables and parameters as as IInterface, and let the compiler enforce the contract. Boo supports multiple interface implementation: class MyPlugin(IPlugin, ILoggable, IConfigurable):. Boo also supports abstract base classes using the abstract modifier: abstract class BasePlugin: can provide default implementations of some methods while leaving others abstract, allowing subclasses to inherit the defaults and only implement what is different. The trade-off between pure interfaces and abstract base classes follows standard .NET design guidelines: interfaces for external contracts (especially across assembly boundaries), abstract base classes for internal hierarchies with shared implementation.

Boo’s compile-time macro system: quasi-quotation and AST transformation

Boo’s macro system is one of its most distinctive features and the primary reason some teams choose Boo over C# for domain-specific language work. Macros in Boo are written in Boo and operate on the compiler’s abstract syntax tree during compilation. This is not C-style textual substitution (like C’s #define) and not syntactic templates (like Rust’s macro_rules!): it is full AST transformation, where the macro author has access to the compiler’s complete parse tree for the macro’s argument expression and can construct, inspect, and transform AST nodes programmatically.

A macro is declared with the macro keyword:

macro assert:
    condition = assert.Arguments[0]
    message = assert.Arguments[1]
    yield [|
        if not $condition:
            raise System.AssertionException($message)
    |]

The quasi-quotation operator [| ... |] (bracket-pipe) is the mechanism for constructing AST nodes by writing Boo code as a template. The expression inside [| ... |] is parsed by the Boo compiler as Boo source code, but instead of compiling it to IL, the compiler produces an AST node representing that code. Dollar-sign interpolation inside [| |] splices in a computed sub-node: $condition splices in the AST node that was extracted from assert.Arguments[0]. The result of the quasi-quotation is an AST node that represents the if statement, with the actual condition expression (not a text placeholder, but the actual parsed condition from the macro’s invocation site) embedded in the correct position. The yield keyword inside a macro returns this AST node to the compiler, which inserts it at the macro call site in place of the assert condition, message expression.

The canonical example — the assert macro — is more capable than Python’s assert statement precisely because the macro has access to the AST of the condition. The macro can call condition.ToCodeString() to get the source text of the condition and include it in the assertion error message: if the condition is x > 0 and y < 100, the error message can say “Assertion failed: x > 0 and y < 100” by embedding the source text of the condition. Python’s assert statement does this via the -O optimization flag trick or via pytest’s rewriting, but in Boo it is a straightforward macro that any developer can author without special compiler support.

Programmatic AST construction is used when the desired output node cannot be expressed as a simple quasi-quotation template. The compiler context provides factory methods: context.ReferenceExpression("name") constructs a reference expression node (equivalent to writing a bare name in Boo source); context.IntegerLiteralExpression(42) constructs an integer literal node; context.StringLiteralExpression("text") constructs a string literal. More complex constructions use the AST node types directly: IfStatement, MethodInvocationExpression, BinaryExpression. A macro that generates a loop over a collection must construct a ForStatement node with the correct iterator, loop variable, and body sub-tree. Combining quasi-quotation for the outer structure with programmatic construction for the inner parts — where the inner parts depend on computed values that cannot be expressed as static templates — is the standard macro authoring pattern.

The using macro implements the C# using statement for IDisposable resources. In C#, using (var conn = new SqlConnection(cs)) { ... } guarantees that conn.Dispose() is called even if an exception is thrown. In Boo, this is not a built-in keyword; it is a macro in the standard library that transforms using conn = SqlConnection(cs): ... into a try/ensure block that calls conn.Dispose() in the ensure clause. The lock macro similarly implements thread synchronization: lock obj: ... expands to System.Threading.Monitor.Enter(obj) / System.Threading.Monitor.Exit(obj) wrapped in try/ensure. These macros are not special-cased in the compiler; they are compiled from Boo source into an assembly that booc loads during compilation. This architecture means that any Boo developer can write macros with the same capabilities as the standard library macros.

Macro compilation is a two-pass process. In the first pass, booc must compile the macro assembly (or load an already-compiled macro assembly referenced via the project configuration). Macros are therefore compiled separately from the code that uses them, or they must be in a separate assembly that is already compiled when the main code is processed. This constraint is a common source of confusion in retainer work: a developer adds a macro to the same file as its usage, runs booc, and receives a “macro not found” error because the macro has not been compiled yet. The fix is to separate macros into their own assembly compiled in a first step, then reference that assembly during the main compilation. The booc command-line flag -r:MacroAssembly.dll references a compiled macro assembly. The standard library macros like assert, using, and lock are pre-compiled and automatically available; user-defined macros require this explicit two-pass compilation setup.

Boo language features: list comprehensions, string interpolation, and .NET interop

Boo’s feature set is designed to minimize syntactic overhead for common patterns. List comprehensions produce typed .NET lists: filtered = [x for x in values if x > 0] produces a List[of int] (Boo’s syntax for List<int> in C# notation). The for loop uses the same syntax without the surrounding brackets: for item in collection:. The range built-in generates integer sequences: for i in range(10):. Multiple assignment unpacking works for tuples: x, y, z = point.getCoordinates(). Method definitions use the def keyword: def execute(ctx as Context) as bool:. Constructors are declared as def constructor(param as Type):. Properties use the Boo property syntax with get: and set: blocks. Events use event MyEvent as MyDelegate.

String interpolation in Boo is handled at the compiler level: "Position: ${x}, ${y}, ${z}" is compiled to a string.Format call with the interpolated expressions as arguments. This is syntactically cleaner than C# 5 and earlier where string concatenation was required; it predates C# 6’s $"..." interpolated strings by over a decade. The null keyword behaves identically to C#’s null. Reference type comparisons use is and is not for reference equality, and == and != for value equality (calling Equals). This is syntactically Python-like but semantically .NET-like: a is b tests whether a and b are the same object reference, not whether their values are equal.

.NET interop is a core use case for Boo. The import keyword brings in .NET namespaces: import System.Collections.Generic, import System.IO. All .NET Framework or .NET Standard types are available. Generic types use Boo’s of keyword: List[of string], Dictionary[of string, IPlugin]. .NET exception handling uses try/except/ensure (where C# uses try/catch/finally): except e as MissingMemberException: catches a specific exception type and binds it to the variable e. The ensure block executes whether or not an exception was thrown, equivalent to C#’s finally. NUnit test integration is direct: Boo class files can be annotated with [TestFixture], methods with [Test], and the standard NUnit runner processes the compiled assembly without any special Boo-aware tooling.

The interactive Boo interpreter is invoked as boo.exe (or boo on Mono). It supports incremental evaluation of Boo expressions and statements at a REPL prompt, useful for exploring APIs and testing individual expressions before integrating them into a larger program. The compiler executable is booc (or booc.exe). Build system integration is typically via NAnt build scripts or MSBuild project files with the Boo task. booc -r:SomeAssembly.dll -o:output.dll input.boo is the basic compilation invocation. booc -t:exe produces an executable; booc -t:library produces a DLL. The Boo compiler can also be used programmatically via the Boo.Lang.Compiler API, which is how Grasshopper and other host applications compile Boo scripts at runtime: the host application instantiates the compiler, provides the Boo source code as a string, supplies references to the host’s own assemblies (RhinoCommon, GH_IO, etc.), and receives the compiled assembly in memory for immediate execution.

Typical Boo retainer work and what it looks like in a work log

A Boo retainer typically covers three recurring categories of work. The first is type system work: identifying where duck is used legitimately (COM interop, genuine runtime polymorphism without a common interface) versus where it was introduced as a shortcut, and replacing the shortcut uses with properly typed interfaces or abstract classes. This work surfaces latent type errors that have been accumulating silently — the typical ratio is one to three previously unknown type mismatches per duck variable replaced, depending on how heavily the variable is assigned through multiple code paths. The work log entry reads “replaced duck variable in plugin loader, found 2 type mismatches, 5h.” Without a more detailed log — naming the variable, the interface that replaced it, the two mismatches that were caught, and the before/after exception rate — this is invisible work.

The second category is macro system work. Teams that use Boo’s macro system for DSL authoring — commonly in Grasshopper-adjacent tooling where the domain has strong conventions that benefit from custom syntax — require ongoing macro maintenance as the DSL evolves. Adding a new production rule to the grammar means writing a new macro, designing its MacroStatement argument structure, authoring the quasi-quotation template or programmatic AST construction, handling edge cases in the input (malformed invocations, optional arguments, variable argument lists), and integrating the new macro into the two-pass compilation setup. The work log entry reads “added geometry loop macro, 8h.” Without explanation of what the macro does (generate type-safe iteration over Grasshopper geometry trees with correct branch access), what AST nodes it produces, and why a macro is preferable to a helper method (because the macro can generate compile-time type-specific dispatch that a generic helper method cannot), the entry is unintelligible to any client who has not spent time with Boo’s macro system.

The third category is .NET platform and assembly work: resolving namespace import errors that arise when a new Boo version changes how certain .NET namespaces are exposed, debugging type mismatches between .NET types and Boo’s type aliases, handling Boo’s null semantics in code ported from Python (where None comparisons use is and Boo’s null comparisons use is null / is not null), and dealing with the Grasshopper/Rhino version upgrade cycle where RhinoCommon API changes break Boo scripts that directly call geometry methods. The work log entry reads “resolved RhinoCommon upgrade breaks, 6h.” Without documenting which methods changed signatures, which duck variables masked the signature changes, and which interface types were introduced to make future upgrades surface as compile errors instead of runtime crashes, this entry appears to be straightforward maintenance with an unclear hourly cost.

Across all three categories, the defining characteristic of Boo retainer work is the migration of errors from runtime to compile time. The primary quality metric for a Boo engagement is not lines of code delivered, not features shipped, not tests written: it is the ratio of compile-time errors to runtime exceptions, and how that ratio changes over the course of the retainer. A healthy Boo codebase catches type errors at compile time; an unhealthy one catches them as MissingMemberException, InvalidCastException, and NullReferenceException in production. Retainer work that moves the error detection point earlier in the cycle is the highest-leverage work available, and it produces no artifact proportional to the hours — no feature, no visible output, just a different distribution of error discovery across the development timeline.

Track Boo developer retainer hours without the status emails

When a five-hour session moves two type errors from runtime to compile time, the work log needs to say that — not just “fixed plugin crash.” HourTab gives your Boo retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log that names the duck variable that was replaced, the interface that replaced it, and the compile-time errors that were caught. No client login. No status emails. CSV in, URL out.

See HourTab pricing →

How HourTab tracks Boo developer retainer hours

Boo retainer work is particularly invisible for the same reason that moving errors from runtime to compile time is valuable: the output of a successful session is the absence of a future crash, not the presence of a new feature. A client who sees “5h — refactored plugin variable type” in a work log cannot assess whether five hours was a reasonable cost for changing one word in a type declaration. The work log needs to say: the variable was duck plugin (late-bound, no compile-time checking); it was changed to plugin as IPlugin (static dispatch, compile-time enforcement); booc immediately caught two assignments that were silently assigning wrong types; the runtime MissingMemberException that had been occurring on one code path was eliminated; no other duck variables in the plugin loading path require the same treatment. That log entry is auditable: a client can verify that the change was made, can verify that the interface was defined, can verify that the two compile-time errors were real type mismatches that would have caused runtime failures. The five hours are justified by the diagnostic trail, not by the size of the diff.

HourTab gives Boo developers a public retainer-hours URL they send to clients — typically development teams maintaining Grasshopper parametric design scripts for architecture or industrial design firms, studios maintaining legacy Unity Boo scripting assets, .NET platform teams using Boo’s macro system to build compile-time DSLs, and organizations with Boo-based plugin systems that were originally prototyped with duck typing and need systematic replacement with interface-typed static dispatch. For Boo retainers, each work log entry should name the mechanism at the level of the type analysis: which variable was changed, from what type to what type, how many compile-time errors were surfaced, and what the before and after runtime exception rate is. Comparative context for scope discussions: Boo retainer work has conceptual overlap with retainer work on other .NET languages and on Python-adjacent statically typed systems. Teams evaluating Boo alongside other statically typed languages designed for .NET ergonomics will find that Boo’s unique combination of Python-like syntax, first-class macros, and full .NET compatibility creates a specific skills profile that is not well-served by general .NET developers or by Python developers without .NET experience. The retainer rate structure reflects this: Boo expertise at the senior level commands rates in the $145 to $260 per hour range because the combination of deep AST macro knowledge, .NET type system mastery, and Grasshopper or Unity platform experience is rare.

FAQ: Boo developer retainers

What does a Boo developer on retainer typically do?

A Boo developer on monthly retainer covers Boo’s duck typing system (the duck keyword declares a late-bound variable dispatched via .NET reflection at runtime; all method calls on a duck-typed variable compile without error regardless of whether the method exists on the actual object; a MissingMemberException is thrown at runtime when the method is absent; duck is an explicit opt-in for dynamic dispatch, not an inferred type; it has significant performance cost because each method call goes through reflection rather than a vtable); static type and interface enforcement (replacing duck variables with specific interface types like IPlugin or IRenderer; Boo then performs compile-time method resolution and emits errors for missing interface methods; the class declaration class MyPlugin(IPlugin) asserts at compile time that MyPlugin implements every method declared in IPlugin); compile-time macro authoring (macros receive a MacroStatement; quasi-quotation with [| expr |] templates let the macro author write Boo syntax that becomes an AST node; the assert, using, and lock macros are standard-library macros, not built-in keywords); .NET interop (Boo compiles to standard .NET IL and can reference any .NET assembly; COM interop is one legitimate use case for duck typing where the COM object’s interface is not known at compile time); and work in Boo’s deployment contexts including Grasshopper parametric design plugins for Rhino and legacy Unity game engine scripting.

What Boo work is most commonly underlogged in a retainer?

Duck type diagnosis and replacement (determining whether a MissingMemberException originated from a duck-typed variable; identifying which assignment introduced the wrong object type; designing the replacement interface; running booc to surface previously invisible type mismatches; 4 to 8 hrs per incident, invisible in a log that says “fixed plugin crash”); interface hierarchy design (determining whether one interface or several is needed; whether a base class with default implementations is more appropriate; which method signatures should use interfaces vs concrete types; 3 to 7 hrs invisible); macro system work (writing a macro with correct quasi-quotation; debugging the two-pass compilation setup; confirming macro output compiles cleanly in the second pass; 5 to 10 hrs invisible); .NET assembly integration (resolving namespace import errors; debugging type mismatches between .NET types and Boo’s type aliases; handling nullable reference type semantics; 2 to 5 hrs invisible); and Grasshopper/Rhino plugin adaptation (porting Boo scripts to updated RhinoCommon APIs; handling geometry type dispatch where duck was used to avoid interface complexity; 3 to 7 hrs invisible).

What are typical Boo developer retainer rates?

Entry-level Boo developers with 1 to 2 years of experience covering Boo syntax, basic .NET interop, static type declarations, and standard control flow typically bill at $60 to $110 per hour. Mid-level Boo programmers with 2 to 4 years of experience covering duck type diagnosis, interface hierarchy design, basic macro authoring with quasi-quotation, Grasshopper plugin development, and .NET assembly integration typically bill at $95 to $175 per hour. Senior Boo language developers with 4 or more years of experience covering advanced macro system work including multi-pass macro compilation, AST transformation pipelines, compile-time code generation for DSLs, deep duck-vs-interface trade-off analysis, and complex plugin system architecture in Grasshopper or Unity legacy scripting contexts typically bill at $145 to $260 per hour. Monthly retainer ranges: $1,800 to $3,500 per month for advisory engagements (roughly 15 to 25 hours per month); $4,000 to $11,000 per month for full engagement Boo development covering ongoing plugin development, macro library authoring, and active feature delivery.

What should a Boo developer retainer agreement include?

A Boo developer retainer agreement should specify: duck type scope (which parts of the codebase use the duck keyword; whether it is used legitimately for COM interop or as a shortcut; whether the engagement includes a duck type audit; what the acceptable MissingMemberException rate is during the engagement); interface design scope (whether the engagement includes designing new interfaces vs implementing existing ones; whether interface versioning is in scope for long-lived plugin APIs; whether abstract base classes are permitted alternatives); macro system scope (whether the engagement includes authoring new compile-time macros; whether existing macros need debugging or extension; whether quasi-quotation or programmatic AST construction is needed; whether the two-pass compilation setup is in scope); .NET platform scope (which .NET or Mono version the deployment targets; whether the engagement covers Grasshopper plugins, Unity legacy scripting, or standalone executables; whether COM interop is in scope); and hour logging format (duck: variable name, expected method, exception message, replacement type, compile-time errors caught before and after; interface: interface name, methods declared, classes updated, compile-time errors surfaced; macro: macro name, input structure, quasi-quotation template, resulting AST node type, compilation pass).

How should Boo developer retainer hours be logged?

Log each Boo retainer session with: duck type category (variable name and declaration: duck plugin vs plugin as IPlugin; method called: plugin.execute(context); exception at runtime: MissingMemberException on method execute; replacement type: IPlugin interface; compile-time errors surfaced after replacement: 0 before, 2 after; runtime MissingMemberException rate: 1 before, 0 after); interface design category (interface name and declaration; number of methods declared; classes updated with explicit implementation; compile-time errors caught during update; whether any method signatures were adjusted; before and after state of compile-time error count vs runtime exception rate); macro system category (macro name; input MacroStatement structure; [| |] quasi-quotation blocks used; programmatic AST construction calls made; output AST node type; whether the macro output compiled cleanly in the second pass; before and after state of any runtime errors the macro was designed to eliminate); .NET interop category (assembly imported; namespace resolved; type mismatch encountered; how the mismatch was resolved; whether a duck variable was introduced to work around a type mismatch and whether it should be replaced; before and after compile error and runtime error counts); and for all categories, the total hours and the before/after ratio of compile-time errors to runtime exceptions, since that ratio is the primary quality metric for Boo retainer work.