Tail — calling Python
Rat's dispatch primitive — name.method(args) — doesn't care whether the callee is a builtin, a service, a DB handle, or a function in another runtime. Tail is the populator that wires a Python (or Node, R, anything) module into the global namespace, so a page can call weather.predict(city) exactly the way it calls auth.login(...). Every example below is running for real — the server has a Python worker open on site/lang/python/.
Live quote
wisdom.quote() runs Python on every render
site/lang/python/wisdom.py exposes a quote() function backed by random.choice. Refresh this page and the value changes — proof that the call hits the live worker, not a cached snapshot.
# site/lang/python/wisdom.py
import random
_QUOTES = ['...', '...', '...']
def quote():
return random.choice(_QUOTES) # site/pages/.../tail.rat
> server
quote_now: wisdom.quote()
> page
<blockquote> [quote_now] The bet: one dispatch primitive collapses five mechanisms.
Live text analysis
text.analyze runs at SSR time
text.analyze(s) returns a dict from Python; Rat receives it as a regular object. Member access ([sample_analysis.chars]) reads the result inline. The whole call took one JSON round-trip into the Python subprocess.
# site/lang/python/text.py
from collections import Counter
def analyze(s):
letters = [c.lower() for c in s if c.isalpha()]
top = Counter(letters).most_common(3)
return { "chars": len(s), "words": len(s.split()), "vowels": sum(1 for c in letters if c in 'aeiou'), "top": [{"letter": l, "count": n} for l, n in top], } > server
sample: 'Hello from Python, dispatched through Rat.'
sample_analysis: text.analyze(sample)
> page
<p> chars: [sample_analysis.chars]
<p> words: [sample_analysis.words]
<p> vowels: [sample_analysis.vowels] input: "Hello from Python, dispatched through Rat."
chars: 42
words: 6
vowels: 10
top letters:
- h — 5 times
- o — 4 times
- t — 4 times
Interactive: analyze any string
Service-backed round trip per click
A text_demo service in main.rat wraps the Python call and stores the result on its own frame. The button below hits run_text_analysis(demo_text) via __ratCall; the top-level wrapper invokes text_demo.analyze(s) which calls into Python and writes the result onto the service singleton. Refresh to see the values below update — service mutations persist across requests but aren't pushed back as a reactive patch yet.
# main.rat
> service [text_demo]
result: {chars: 0, words: 0, vowels: 0, top: []}
analyze[s]
result << text.analyze(s)
# this page
> page
demo_text: 'rat is small but mighty'
out >> text_demo.result
<input value[demo_text] on_input[demo_text << event.target.value]>
<button on_click[text_demo.analyze(demo_text)]> analyze
<p> chars: [out.chars] words: [out.words] vowels: [out.vowels] chars: 0 — words: 0 — vowels: 0
Two more helpers
reverse() and slugify() — pure Python
Each function in text.py becomes a member on the text namespace. No registration ceremony, no decorator — the Tail worker discovers them via a one-shot discover message at boot.
# site/lang/python/text.py
def reverse(s):
return s[::-1]
def slugify(s):
# trim, lowercase, hyphenate non-alnum runs
... > server
sample_reversed: text.reverse(sample)
slug_demo: text.slugify('My Brilliant Post #42!') reversed: .taR hguorht dehctapsid ,nohtyP morf olleH
slug of "My Brilliant Post #42!": my-brilliant-post-42
How it composes
No new grammar, no compiler change
When the engine evaluates text.analyze(s), the lookup hits Globals["text"] just like auth or main_db. The only thing Tail adds is a Callable.Go closure that ships args to a long-lived Python subprocess and reads the result back over a JSON pipe. Eval doesn't know — and doesn't need to know — that the callee lives across an IPC boundary.
Setup
One-time, per project
Rat scans lang/python/ at boot, spawns a worker, sends a discover message, and registers each module as a namespace. Requirements (if any) install from lang/python/requirements.txt via rat install. The two demo modules used here are stdlib-only so no install step was needed.
site/
lang/
python/
requirements.txt # optional, pip format
text.py # def analyze(s): ...
wisdom.py # def quote(): ...