Skip to content

Instantly share code, notes, and snippets.

@shreeve
Last active February 27, 2026 08:36
Show Gist options
  • Select an option

  • Save shreeve/5c916f1f27ed43200acb3c7638764806 to your computer and use it in GitHub Desktop.

Select an option

Save shreeve/5c916f1f27ed43200acb3c7638764806 to your computer and use it in GitHub Desktop.
Rip and the JavaScript Garden

JavaScript Garden Analysis: How Rip Improves the Situation

JavaScript Garden documents the most notorious quirks and pitfalls of JavaScript. Here's a deep analysis of each section and how Rip eliminates or mitigates these problems.


1. Equality (== vs ===) — Eliminated

JavaScript Garden calls == a source of "hard-to-track-down bugs" due to type coercion. Rip solves this at the language level with two mechanisms:

Rip's == compiles to ===. There is no way to accidentally use loose equality. The entire coercion table from the Garden becomes irrelevant:

0 == ""      # compiles to: 0 === ""     → false
0 == "0"     # compiles to: 0 === "0"    → false
false == "0" # compiles to: false === "0" → false

Rip's is keyword also compiles to ===, giving you a readable English alternative for identity checks:

x is true    # compiles to: x === true
x is "hello" # compiles to: x === "hello"
x isnt null  # compiles to: x !== null

Between == always meaning strict equality and is/isnt for readable prose-style comparisons, Rip makes it structurally impossible to trigger JavaScript's type coercion. The entire weak-equality footgun simply doesn't exist.


2. typeof Operator — Replaced by kind()

The Garden calls typeof "probably the biggest design flaw of JavaScript" — it returns "object" for arrays, null, dates, regexps, and errors. Rip provides kind() as a stdlib global:

kind []        # "array"    (typeof would say "object")
kind null      # "null"     (typeof would say "object")
kind /regex/   # "regexp"   (typeof would say "object")
kind 42        # "number"
kind "hello"   # "string"

kind() returns the correct lowercase type name by using Object.prototype.toString internally — exactly what the Garden recommends as "the only reliable way" to determine types, but Rip makes it a one-word call.


3. this Binding — Tamed by @ and Auto-Fat-Arrows

The Garden devotes a full section to the five ways this can be bound, the "common pitfall" of nested functions losing this, and the self = this workaround. Rip addresses all of this:

  • @ is this.@name compiles to this.name, making this usage explicit and concise
  • Inside components, -> auto-converts to => — You never lose this binding accidentally. The compiler handles it
  • Fat arrows (=>) are first-class — When you need explicit binding outside components, just use =>

The Garden's example of the broken nested function:

// JavaScript — broken
Foo.method = function() {
    function test() { this.value; /* wrong this! */ }
    test();
};

In Rip, this simply works:

# Rip — @value always refers to the outer this
Foo.method = ->
  test = => @value  # fat arrow captures this
  test()

4. Scoping and Hoisting — Eliminated

The Garden warns extensively about var hoisting, missing block scope, and accidental globals. Rip compiles to let and const — never var:

  • Block scoping is the default — Variables declared in if, for, or any block are scoped to that block
  • No hoisting surpriseslet/const have temporal dead zones; use-before-declaration is an error
  • No accidental globals — Rip always emits declarations; there's no way to accidentally create a global by forgetting var

The Garden's classic bug:

// JavaScript — i leaks, subLoop clobbers it
for(var i = 0; i < 10; i++) { subLoop(); }
function subLoop() { for(i = 0; i < 10; i++) { } } // oops, global i

In Rip, for loop variables are always block-scoped. This class of bug is structurally impossible.


5. Closures in Loops — Solved by Block Scoping

The Garden's infamous closure-in-loop problem:

for(var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 1000); // prints 10, ten times
}

Rip compiles for loops with let, which creates a new binding per iteration:

for i in [0...10]
  setTimeout (-> p i), 1000  # prints 0, 1, 2, ... 9

No IIFE wrapper, no .bind() trick — the language just does the right thing.


6. arguments Object — Replaced by Rest Parameters

The Garden describes arguments as "not an Array" requiring Array.prototype.slice.call(arguments) to use. Rip has proper rest parameters:

def foo(first, ...rest)
  rest.map (x) -> x * 2  # rest is a real Array

The arguments object is never needed. Spread (...args) and rest parameters handle all use cases natively.


7. Automatic Semicolon Insertion — Not Applicable

The Garden calls ASI "one of the biggest design flaws in the language." Rip uses significant whitespace — there are no semicolons to insert or forget. The Garden's terrifying example of merged lines:

log('testing!')
(options.list || []).forEach(...)  // merged into one expression!

This is structurally impossible in Rip. Newlines are meaningful. The parser uses indentation, not semicolons, to determine statement boundaries.


8. Prototype Chain and for in — Safer Iteration

The Garden warns that for in traverses the prototype chain and requires hasOwnProperty filtering. Rip's iteration constructs compile to safe ES6+ equivalents:

Rip Compiles to Safe?
for k, v of obj for...of Object.entries(obj) Yes — own properties only
for x in arr Index-based loop Yes — no prototype traversal
for x as iter for...of on iterables Yes — iterator protocol

You never need to write hasOwnProperty checks in Rip.


9. Constructors — .new() Syntax

The Garden warns that forgetting new leads to this being the global object. Rip offers Ruby-style .new():

user = User.new("Alice")  # compiles to: new User("Alice")

While Rip also supports new User("Alice"), the .new() convention makes it read as a method call, reducing the chance of omission.


10. undefined and null — Existence Operators

The Garden discusses the confusion between undefined and null and the need for careful checking. Rip provides a family of operators:

Operator Name Purpose
x? Existence x != null (covers both null and undefined)
x ?? y Nullish coalescing Default if null/undefined
x?.y Optional chaining Safe property access
x!? Defined check True if not undefined
x?! Presence check True if truthy, else undefined
# Instead of: typeof foo !== 'undefined' && foo !== null
if foo?
  p foo

11. eval — Not Available

The Garden says eval "should never be used." Rip simply doesn't have eval. There's no syntax for it, no backdoor. Code is compiled, not evaluated from strings. The setTimeout('foo()', 1000) anti-pattern is impossible.


12. setInterval Stacking — sleep! Alternative

The Garden warns about setInterval stacking calls when the callback blocks. Rip's sleep! (which compiles to await sleep(ms)) encourages the recursive-setTimeout pattern naturally:

loop
  doWork!
  sleep! 1000

This is the exact pattern the Garden recommends — sequential, non-stacking delays — but expressed as a simple loop.


13. The delete Operator — Available but Rarely Needed

The Garden explains delete's inconsistent behavior with DontDelete attributes. Rip exposes delete but its class system and module pattern mean you rarely need to delete properties from objects manually.


Summary

JS Garden Issue Rip's Solution Severity Eliminated
== coercion == compiles to === Critical
typeof broken kind() stdlib Critical
this confusion @, auto => in components Critical
var hoisting Compiles to let/const Critical
Closures in loops Block-scoped let per iteration High
arguments not an Array Rest params ...args High
ASI surprises Significant whitespace High
for in prototype leak Safe for...of/for...in semantics High
Forgetting new .new() syntax Medium
null/undefined confusion ?, ??, ?., !?, ?! Medium
eval misuse Not available Medium
setInterval stacking sleep! + loop pattern Low

Nearly every issue documented in JavaScript Garden is either structurally impossible in Rip (ASI, var hoisting, accidental globals, closures in loops) or eliminated by default (=====, kind() vs typeof, @ for this). The Garden is essentially a catalogue of JavaScript's historical mistakes — Rip compiles to modern JavaScript while making it nearly impossible to trigger any of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment