{
  "id": 7874547,
  "title": "I wrote a programming language where you code in Polish. Here are 7 things it taught me about Ruby.",
  "url": "https://urgent.news/2026/09/16/i-wrote-a-programming-language-where-you-code-in-polish-here-are-7",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-09-16T21:18:41.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/konstanty_koszewski/i-wrote-a-programming-language-where-you-code-in-polish-here-are-7-things-it-taught-me-about-ruby-51on"
  },
  "original_language": "en",
  "account": "1. Using exceptions for control flow is expensive. Implementing return in a tree-walking interpreter can be done by raising an exception and catching it at the call site. However, this approach is inefficient because Ruby builds an exception object and captures a backtrace every time. On recursive code, where return statements are frequently used, this can become a significant performance issue. A more efficient alternative is to use throw/catch, a feature that Rubyists may be familiar with but rarely use. While throw/catch works only for non-local exits controlled by the programmer, it is well-suited for functions with known return destinations. It eliminates the need for exception objects, backtraces, and stack unwinding, making it a cleaner and more efficient solution.\n\n2. Indexing into a UTF-8 string is O(n). The author's lexer originally scanned through the string character by character using source[position], which worked fine for ASCII strings. However, Ruby strings are UTF-8 encoded, and determining the nth character requires traversing the entire string from the beginning, counting codepoints. This led to quadratic tokenization performance when processing files with Polish diacritics. Switching to using getbyte and byteslice addressed this issue, as getbyte operates in constant time (O(1)) without any string allocation. Lexemes can then be constructed using byteslice and forced to UTF-8 encoding, ensuring accurate tokenization regardless of the string's character composition.\n\n3. MRI keeps C methods and Ruby methods in the same table, and you should too. The standard library of the author's project was written in Ruby, while user code was written in AlexScript. Initially, the author implemented two separate registries: one for native methods and another for user-defined ones. Dispatch checks both registries, leading to slower performance. A more efficient approach is to merge the native and user methods into the same method table, tagged with a flag to distinguish between them. By doing so, native classes become first-class citizens, allowing them to be subclassed, utilize super, and be reflected upon seamlessly. This approach not only improves performance but also streamlines development by treating native classes and user classes uniformly. The author discovered that MRI, the standard Ruby implementation, already employs this technique, sharing a method table for C methods and Ruby methods. Studying how MRI handles method dispatch proved to be an invaluable learning experience for the author.\n\n4. Fibers are a real concurrency primitive, not a curiosity. Most Ruby developers are aware of Fibers but seldom utilize them. The author ended up building an entire async runtime system on top of Fibers. Using fibers, the author created a custom reactor that manages a ready queue, a sorted timer list, and an IO.select loop. When a promise settles, it pushes its waiters back onto the queue. The reactor also installs itself as Fiber.set_scheduler to handle fibers while they run. This setup allows stdlib operations inside fibers to follow the scheduler interface seamlessly. With fibers, the author gained the ability to suspend and resume execution, providing a powerful tool for concurrent programming. Fibers offer a lightweight alternative to traditional thread-based concurrency, with less overhead and fewer complexities. By leveraging Fibers effectively, the author was able to achieve scalable and efficient asynchronous operations in their project.\n\n5. WeakRef will ruin your week. The author initially planned to use WeakRef for managing closure environments in the interpreter's standard library, hoping it would help the garbage collector clean up unused scopes. However, the implementation of WeakRef turned out to be a significant source of headaches. InvalidReference errors appeared sporadically, causing the code to behave unexpectedly and sometimes failing with no reliable way to reproduce the issue. The problem lay in the fact that the garbage collector was erroneously collecting environments that were logically reachable but not through references that the GC could easily track. To avoid further complications, the author decided to adopt a more conservative approach and eliminated the reliance on WeakRef. This change proved to be beneficial in terms of stability and predictability, even if it meant sacrificing some potential memory optimization.\n\n6. Ruby's exceptions are not always the best choice for flow control. While exceptions are a natural choice for handling errors and exceptional situations, they should not be used as a primary means of controlling the flow of execution. Relying on exceptions for normal control flow can lead to performance issues, as the system needs to build exception objects and capture backtraces every time an exception is raised. This overhead can be significant, particularly in recursive code where exceptions are frequently thrown and caught. In cases where you need to transfer control to a different point in the code flow, such as in function return statements, using throw/catch instead of exceptions is a more efficient approach. Throw/catch allows for non-local exits and avoids the associated overhead of exception objects and backtraces. The author discovered that using throw/catch for function returns provided a cleaner and more performant solution compared to using exceptions. It is essential to understand the appropriate use cases for exceptions and to employ alternative mechanisms when flow control is concerned.",
  "summary": "I write Ruby for a living, and about two years ago it started bothering me that I had no real idea what happens between typing x = 5 and the machine doing something about it. So I wrote a toy interpreter over a weekend. Got it evaluating arithmetic. Felt clever. Then instead of moving on I kept adding things to it, and eighteen months later it's AlexScript: an interpreted, object-oriented…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}