惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
P
Palo Alto Networks Blog
月光博客
月光博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
I
InfoQ
aimingoo的专栏
aimingoo的专栏
腾讯CDC
T
Threatpost
D
DataBreaches.Net
Vercel News
Vercel News
F
Fortinet All Blogs
Engineering at Meta
Engineering at Meta
C
Cybersecurity and Infrastructure Security Agency CISA
Forbes - Security
Forbes - Security
U
Unit 42
C
Check Point Blog
Blog — PlanetScale
Blog — PlanetScale
O
OpenAI News
量子位
TaoSecurity Blog
TaoSecurity Blog
Microsoft Azure Blog
Microsoft Azure Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
V
Visual Studio Blog
Recorded Future
Recorded Future
云风的 BLOG
云风的 BLOG
Security Archives - TechRepublic
Security Archives - TechRepublic
The Last Watchdog
The Last Watchdog
S
Security Affairs
Attack and Defense Labs
Attack and Defense Labs
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
小众软件
小众软件
S
SegmentFault 最新的问题
www.infosecurity-magazine.com
www.infosecurity-magazine.com
W
WeLiveSecurity
AI
AI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 聂微东
I
Intezer
Know Your Adversary
Know Your Adversary
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
博客园_首页
NISL@THU
NISL@THU
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
From Braces to Pipes
Mat Weiss · 2026-05-26 · via DEV Community

The previous article argued that compilers could check more than they currently do — and that the agentic coding era makes this urgent. The articles that follow demonstrate specific features in Ruuk, a language designed around that idea. But Ruuk's syntax is based on F#, and if you've never seen an ML-family language, the examples will be harder to follow than they need to be.

This article is a warp-speed tour of F# syntax — just enough to read the Ruuk code in the rest of the series. If you've written C#, Java, or TypeScript, nothing here is conceptually alien. The ideas have direct parallels; the notation is just different. And to keep things interesting, we'll model our examples around the operational systems of a certain starship.

Why Fsharp

The choice of F# as Ruuk's syntactic foundation wasn't personal preference. Before I'd decided on paradigm or syntax style, I studied how programmers actually read code — drawing on cognitive linguistics research. Two findings shaped the design. First, people tend to read code the same way they read prose: linearly, left to right, top to bottom. Nested function calls like toUpper(trim(getName(user))) force inside-out reading — you start at the innermost call and work outward. Fluent method chaining — user.getName().trim().toUpper() — solves the reading-order problem, but it ties each step to a method defined on the preceding type. A pipeline like user |> getName |> trim |> toUpper reads in the same order things happen, and the functions are standalone — they compose freely without needing to live on a class. ML-family languages build around this style, and that made them a natural place to start.

Second, natural language encodes relationships through prepositions — from, to, by, in — and humans process these almost reflexively. That observation led directly to Ruuk's parameter roles, which you'll see in the next article: beamUp riker from planetSurface to padOne by laForge reads the way you'd describe the operation out loud. The syntax isn't ML-flavored because ML is elegant (though it is). It's ML-flavored because that family gave Ruuk a strong foundation for readable, linear code.

Values and Functions

F# uses let to bind a name to a value. If you're coming from a C-family language, think const — bindings are immutable by default.

let shipName = "Enterprise"
let registry = "NCC-1701-D"
let maxWarpFactor = 9.6

Enter fullscreen mode Exit fullscreen mode

No type annotations. F# infers types from usage — shipName is a string, maxWarpFactor is a float. You can annotate explicitly, but idiomatic F# lets the compiler do the work:

let shipName: string = "Enterprise"   // valid, but unnecessary here

Enter fullscreen mode Exit fullscreen mode

Functions use the same let keyword. There's no function or func or fn — a binding that takes parameters is a function:

let warpSpeed factor =
    factor * 299792.458

let greeting name rank =
    $"{rank} {name}, reporting for duty."

Enter fullscreen mode Exit fullscreen mode

warpSpeed takes one argument and returns a float. greeting takes two and returns a string. The compiler infers all of it. No return keyword either — the last expression in a function is its return value.

And if you call warpSpeed with the wrong type, the compiler catches it immediately:

warpSpeed "fast"
// Error: This expression was expected to have type 'float'
//        but here has type 'string'

Enter fullscreen mode Exit fullscreen mode

No runtime surprise. The compiler saw that warpSpeed multiplies its argument by a float, inferred the parameter must be a float, and rejected "fast" at compile time.

If you're used to braces and semicolons: whitespace is significant in F#. Indentation defines scope, similar to Python. The body of warpSpeed is indented under its declaration — that's what makes it the body, not a pair of curly braces.

These three properties — immutable bindings, type inference, and functions defined with let — are the foundation everything else in this tour builds on.

Records

The type keyword defines new types. A record is a named collection of fields — the closest analog is a C# record or a TypeScript object literal with a fixed shape:

type CrewMember = {
    Name: string
    Rank: string
    Department: string
    ClearanceLevel: int
}

Enter fullscreen mode Exit fullscreen mode

Creating a record instance looks like this:

let picard = {
    Name = "Jean-Luc Picard"
    Rank = "Captain"
    Department = "Command"
    ClearanceLevel = 10
}

Enter fullscreen mode Exit fullscreen mode

Notice there's no new CrewMember(...). F# infers that picard is a CrewMember because the field names match — only one record type in scope has that exact set of fields. Type inference extends beyond simple values.

Field access uses dot notation:

let name = picard.Name   // "Jean-Luc Picard"

Enter fullscreen mode Exit fullscreen mode

Records are immutable. You don't modify a record — you create a copy with specific fields changed using the with keyword:

let promoted = { picard with Rank = "Admiral" }

Enter fullscreen mode Exit fullscreen mode

picard still has Rank = "Captain". promoted is a separate value with Rank = "Admiral" and everything else copied. If you've used spread syntax in JavaScript ({ ...picard, rank: "Admiral" }), it's the same idea with a compile-time guarantee that the field names and types are correct.

Immutable records with copy-on-write are the default data model in F# — and in Ruuk. You define the shape, the compiler tracks it, and transformations produce new values instead of mutations.

Lists and Pipelines

F# lists use square brackets. Items are separated by semicolons, or by newlines if each item is on its own line:

let bridge = [
    { Name = "Picard"; Rank = "Captain"; Department = "Command"; ClearanceLevel = 10 }
    { Name = "Riker"; Rank = "Commander"; Department = "Command"; ClearanceLevel = 9 }
    { Name = "La Forge"; Rank = "Lt. Commander"; Department = "Engineering"; ClearanceLevel = 8 }
    { Name = "Crusher"; Rank = "Commander"; Department = "Medical"; ClearanceLevel = 8 }
    { Name = "Worf"; Rank = "Lieutenant"; Department = "Security"; ClearanceLevel = 7 }
    { Name = "Data"; Rank = "Lt. Commander"; Department = "Operations"; ClearanceLevel = 8 }
]

Enter fullscreen mode Exit fullscreen mode

F# lists are immutable. Adding an element produces a new list; the original doesn't change.

The real power of lists in F# is how you process them. The pipe operator |> takes the result of the left side and passes it as the last argument to the function on the right. This lets you write data transformations as a top-to-bottom pipeline:

let seniorStaff =
    bridge
    |> List.filter (fun m -> m.ClearanceLevel >= 8)
    |> List.sortByDescending (fun m -> m.ClearanceLevel)
    |> List.map (fun m -> m.Name)
// ["Picard"; "Riker"; "La Forge"; "Crusher"; "Data"]

Enter fullscreen mode Exit fullscreen mode

Read it top to bottom: start with the bridge crew, keep only those with clearance 8 or above, sort by clearance descending, extract their names. Each step feeds into the next.

fun m -> m.ClearanceLevel >= 8 is a lambda — same idea as m => m.ClearanceLevel >= 8 in C# or JavaScript, different arrow. List.filter, List.map, and List.sortByDescending are direct counterparts to LINQ's Where, Select, and OrderByDescending, or Java's filter, map, and sorted.

If you've chained LINQ methods or Java streams, pipelines will feel natural. The difference is that pipes compose standalone functions rather than calling methods on an object. Data flows through; functions don't need to know about each other.

Discriminated Unions

We're at warp now — this is the feature that matters most for the rest of the series.

Records describe things that have multiple fields. Discriminated unions describe things that can be one of several cases. The type keyword does both jobs.

A simple union with no associated data:

type Department =
    | Command
    | Engineering
    | Medical
    | Science
    | Security
    | Operations

Enter fullscreen mode Exit fullscreen mode

Each case is a distinct value. This replaces the Department: string we used earlier in CrewMember — instead of hoping someone types "Engineering" correctly, the compiler knows exactly which values are valid:

type CrewMember = {
    Name: string
    Rank: string
    Department: Department
    ClearanceLevel: int
}

let worf = {
    Name = "Worf"
    Rank = "Lieutenant"
    Department = Security
    ClearanceLevel = 7
}

Enter fullscreen mode Exit fullscreen mode

No quotes around Security — it's a value of type Department, not a string. Try assigning Department = "Security" and the compiler rejects it. Try assigning Department = Tactical and the compiler rejects that too — Tactical isn't one of the cases.

If you're coming from Java or C#, this looks like an enum, and for simple cases it works the same way. The difference is that each case can carry its own data:

type AlertStatus =
    | Green
    | Yellow of reason: string
    | Red of threat: string * shieldsUp: bool

Enter fullscreen mode Exit fullscreen mode

Green carries nothing. Yellow carries a reason. Red carries a threat description and whether shields are raised. These aren't three variations of the same data shape — each case has its own structure. Try modeling this with a C# enum and you'll end up with a class hierarchy or nullable fields. The union makes it one type.

let currentAlert = Yellow "Unidentified vessel on long-range sensors"
let battleStations = Red ("Romulan warbird decloaking", true)

Enter fullscreen mode Exit fullscreen mode

Ruuk's outcomes — which we'll see in the next article — are discriminated unions. When an operation can succeed, fail, or partially fail in domain-specific ways, each outcome is a case with its own data. The compiler knows all of them and can verify you've handled every one.

Option and Result

Two discriminated unions show up so often in F# that they're built into the standard library. Rust has the same pair (Option and Result); if you've used those, this is identical in intent.

Option represents a value that might not exist. It has two cases: Some with a value inside, or None. This replaces null — and unlike null, you can't use the value without first deciding what absence means.

let findCrewMember name =
    bridge |> List.tryFind (fun m -> m.Name = name)

let found = findCrewMember "Worf"          // Some { Name = "Worf"; ... }
let missing = findCrewMember "Kirk"        // None

Enter fullscreen mode Exit fullscreen mode

List.tryFind returns an Option<CrewMember>, not a CrewMember. You can't call .Name on the result directly — the compiler knows it might be None. You have to unwrap it first — which means deciding what happens when nothing is found. No NullReferenceException three layers away from the actual problem.

Result represents an operation that can succeed or fail, with data in both cases: Ok carries the success value, Error carries the failure value.

type TransporterFailure =
    | SignalLost of lastCoords: string
    | PatternDegradation of integrity: float
    | TargetShielded

type TransporterTarget = {
    Name: string
    SignalStrength: float
    LastKnownCoords: string
}

let beamUp target =
    if target.SignalStrength > 0.8 then
        Ok target.Name
    elif target.SignalStrength > 0.4 then
        Error (PatternDegradation target.SignalStrength)
    else
        Error (SignalLost target.LastKnownCoords)

Enter fullscreen mode Exit fullscreen mode

beamUp returns Result<string, TransporterFailure> — either a name on success or a typed failure explaining what went wrong. If you're coming from Java or C#, your instinct might be to throw a SignalLostException, a PatternDegradationException, and so on — one exception class per failure mode. That encodes the failures, but it doesn't make them visible in the function's signature. Callers compile fine whether they catch anything or not. Java's checked exceptions tried to fix this, but the syntactic overhead led to so much catch-and-swallow boilerplate that C#, Kotlin, and most modern Java frameworks chose not to adopt the pattern. With Result, the failure cases live in the return type — they're part of the contract, not a side channel — and the compiler won't let you ignore them.

Ruuk inherits Option and Result from this lineage and extends the idea further. Where F# gives you two slots — Ok or Error — Ruuk's operations declare arbitrary domain-specific outcomes as first-class members of the signature. The next article shows what that looks like.

Pattern Matching

Once you inspect an Option or Result, F# gives you a precise tool for handling its cases: match/with, which branches on the shape of data.

let transportReport result =
    match result with
    | Ok name ->
        $"Transport complete. {name} is aboard."
    | Error (SignalLost coords) ->
        $"Signal lost at {coords}. Dispatching shuttle."
    | Error (PatternDegradation integrity) ->
        $"Pattern integrity at {integrity}. Boosting signal."
    | Error TargetShielded ->
        "Cannot beam through shields. Hailing target."

Enter fullscreen mode Exit fullscreen mode

Each branch matches a specific shape and destructures its data. Error (SignalLost coords) doesn't just check that the result is an error — it checks that it's specifically a SignalLost error and pulls out the coordinates in one step. No casting, no instanceof, no nested if chains.

Here's the important part: the compiler checks that every case is handled. Remove the TargetShielded branch and the compiler warns you:

Incomplete pattern matches on this expression.
For example, the value 'Error TargetShielded' may indicate
a case not covered by the pattern(s).

Enter fullscreen mode Exit fullscreen mode

This is exhaustive checking. The compiler knows every case in the union and verifies you've accounted for all of them. Add a new case to TransporterFailure — say WarpFieldInterference — and every match expression that doesn't handle it gets flagged. F# isn't alone here — Java's switch expressions on sealed types and Rust's match enforce the same guarantee. But F# has had it from the start, and it's one of the language's strongest properties: a closed, compiler-verified contract between the type definition and every piece of code that consumes it. It's the property the rest of this series depends on. When Ruuk declares that an operation has four outcomes, this is the mechanism that ensures every caller handles all four.

What's Next

That's enough F# to read Ruuk. Six concepts: let bindings, records, lists and pipelines, discriminated unions, Option/Result, and pattern matching with exhaustive checking. If you followed the transporter example — a function that returns a typed result, where pattern matching lets the compiler check every handled failure mode — you have the mental model for what comes next.

The next article introduces Ruuk's op keyword and its outcomes block — where these F# foundations meet domain-specific compiler enforcement. The compiler takes it from there.

For deeper F# coverage, Scott Wlaschin's F# for Fun and Profit and Isaac Abraham's Get Programming with F# are both excellent starting points for developers coming from OOP languages.

Ruuk is pre-alpha. If these ideas resonate, follow along on GitHub and weigh in on the discussions.