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
optiontype, 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.
- A primary function that returns an
- 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
foldare 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.
- Code Review: A formal process is used for the "critical path" (transaction decision to market execution).
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.