newsfilter.io
Lecture, Presentation

Effective ML 2011 Harvard CS51 Part 2

  • Error Handling Strategy

    • ML code at Jane Street distinguishes between implicit and explicit error handling to make failure modes obvious without sacrificing performance.
    • Exceptions are considered "messy" control flow structures that can leave code in confusing intermediate states and obscure the semantic meaning of error conditions.
    • The convention for functions that may fail based on input (rather than bugs) involves two variants:
      • A primary function that returns an option type, forcing the caller to explicitly handle success or failure via the type system.
      • An _<exn> variant (e.g., find_exn) that throws an exception, explicitly signaling via naming that an error may occur.
    • The default practice is to use the option-returning version to leverage static type checking, reserving the <exn> version for contexts where the user is certain the value exists.
    • Designing error handling requires context: compiler errors (e.g., a stack trace) are less critical than trading system failures where the system might die mid-day.
    • Functions like fold are designed to be inherently safe (not requiring non-empty lists) and do not return options or support exceptions, allowing usage with higher confidence.
  • Boilerplate and Abstraction

    • Boilerplate code is defined as repetitive patterns copied with minor modifications, often necessitated by a lack of language abstraction capabilities.
    • Avoiding boilerplate is critical for two reasons:
      • Maintenance Risk: Patterns are part of the system; if the pattern evolves, scattered copies may be missed, leading to inconsistent behavior.
      • Human Error: Humans cannot reliably verify large blocks of dull, repetitive code, leading to higher error rates during review.
    • In OCaml, there is "no excuse" for boilerplate due to the availability of functors and higher-order functions, which can reify patterns into single, reusable definitions.
    • A specific rule of thumb applied is "two times is too many," even if creating an abstraction slightly increases line count, as it clarifies what is constant versus what varies in a pattern.
  • Complexity Management and Purity

    • The primary enemy in software engineering is complexity, not a lack of static checking; overly clever type-level invariants can obscure functionality and fail to be understood by human colleagues.
    • Developers are advised to restrain themselves from embedding too many complex invariants if it renders the interface unintelligible to experienced programmers.
    • Code should prioritize being "as simple as you can make it" rather than maximizing abstraction.
    • Side Effects and Mutation:
      • Mutation should be avoided where possible because it acts as a communication channel that increases coupling and hinders self-contained reasoning.
      • However, mutation is a natural part of programming; a purely functional language (like Haskell) would be useless without side effects for I/O.
      • The goal is disciplined separation (as in Haskell) but not to the point of excessive complexity; sometimes an imperative style is easier to understand and more performant.
      • Pushing side effects "as far out as possible" can sometimes result in more complex code than a straight-ahead imperative approach.
  • Development Processes and Team Structure

    • Code Review: A formal process is used for the "critical path" (transaction decision to market execution).
      • Every code change is reviewed by at least two, often three, people.
      • A custom system tracks reviews to ensure all necessary reviewers have signed off before production.
    • Testing: Unit tests and regression tests are conducted, but code review is identified as the core mechanism for system safety.
    • Team Organization:
      • Teams avoid large, monolithic groups working on the same infrastructure, following lessons from The Mythical Man Month.
      • Projects are segmented into small, orthogonal pieces handled by groups of one to six people to maintain coherence.
      • This structure ensures individuals can keep the entire project in their heads, facilitating faster and more reliable development.
  • Human-Machine Interaction in Trading

    • The business model is not fully automated; it involves a "highly integrated human-technological system" with significant human interaction.
    • Humans are actively required to monitor markets, negotiate trades, and identify "fish" (less knowledgeable market participants) to prevent the system from making unprofitable trades based on flawed assumptions.
    • Trading is described fundamentally as a game-theoretic activity where understanding the behavior of other market participants is as important as the algorithm itself.
  • Language Selection (OCaml vs. Haskell/Java)

    • Historical Context: The switch to OCaml occurred roughly nine years ago, moving away from Excel/VBA, Java, and C#.
    • Performance Predictability: OCaml was chosen because it offers a "straight-ahead" compilation strategy that makes space and time performance highly predictable, a significant advantage over Haskell at the time.
    • Haskell Limitations: Haskell was rejected due to the difficulty in reasoning about its space performance and the non-straight-ahead compilation strategy required to achieve efficiency.
    • OCaml Advantages: The language sits in a "sweet spot" providing simple, declarative code with efficient, predictable compilation.
    • Selection Factors: The choice was partially influenced by familiarity with OCaml and the availability of an implementation suitable for the specific high-performance trading needs of the time.