# Why handleLs args Exists and handleDir(args) Doesn’t — A Type-Driven Lesson from F#

# Why `handleLs args` — and Not `handleDir(args)`

### A Theory-Driven Explanation from the F# Type System

When building a shell-like system in F#, it’s natural to think in terms of *concepts*.

You see a directory listing command and instinctively reach for a name like:

handleDir args

Then the compiler responds with something like:

FS0039: The value 'handleDir' is not defined. Maybe you want: handleLs, handleTree

At first glance, this feels like a trivial naming issue. Just rename the function, right?

But this error has nothing to do with directories.

It’s about **symbol resolution, function contracts, and how F# encodes meaning through types**.

---

## No Name, No Existence

F# is a statically typed, symbol-driven language.

A function exists **only if it is explicitly defined and in scope**. There is:

* no implicit meaning
    
* no semantic guessing
    
* no late binding
    

If a symbol is not defined, it does not exist — full stop.

So when the compiler says “Maybe you want: `handleLs`, `handleTree`”, it’s not being helpful about spelling. It’s telling you something more important:

> *The abstraction you tried to invent does not exist — but more precise ones already do.*

That’s where the real lesson begins.

---

## In F#, Identity Comes from the Type Signature

In F#, the *identity* of a function is not its name. It is its **type signature**.

The compiler does not reason about intent (“this is a directory command”). It reasons about contracts:

1. How many arguments does the function take?
    
2. What are the types of those arguments?
    
3. What is the result type?
    

This is why the difference between the following is **semantic**, not stylistic:

handleDiskInfo () handleLs args

---

## `unit` vs Arguments Is a Design Statement

The empty parentheses `()` represent the `unit` type.

A function that accepts `unit` is declaring something very specific:

> *This operation takes no information from the caller.*

Its behavior is invariant. The caller cannot influence it.

That maps cleanly to commands like:

* `df`
    
* `uptime`
    

These commands are conceptually **invoke-and-report** operations.

By contrast, a function that accepts `string[]` is explicitly stating:

> *My behavior depends on externally supplied input.*

That maps to commands like:

* `ls`
    
* `du`
    
* `tree`
    

These commands represent a **family of behaviors**, parameterized by paths, flags, or options.

---

## Unix Philosophy, Enforced at the Type Level

This distinction isn’t accidental — it’s Unix philosophy encoded into the type system.

| Command Type | Conceptual Model | F# Signature |
| --- | --- | --- |
| Fixed behavior | Single invariant operation | `unit -> unit` |
| Variable behavior | Parameterized family | `string[] -> unit` |

Trying to collapse everything into a generic:

handleDir args

erases this distinction.

It pushes meaning out of the type system and into runtime logic — where it becomes harder to reason about, easier to misuse, and impossible for the compiler to enforce.

F# resists this *by design*.

---

## The Compiler Is Not Complaining — It’s Reviewing Your Design

When the compiler suggests `handleLs` or `handleTree`, it’s not saying:

> “You misspelled something.”

It’s saying:

> “You already modeled this more precisely than what you’re attempting now.”

This leads to the correct mental model:

F# function signature == command interface contract

* If a command conceptually varies with input, its function must accept parameters.
    
* If it does not, its function must accept `unit`.
    

This is not ceremonial verbosity. It is how correctness is enforced *early*, *mechanically*, and *without ambiguity*.

---

## The Real Takeaway

Static typing is often framed as a way to prevent mistakes.

That’s underselling it.

**Static typing prevents bad abstractions.**

It forces you to commit to a model of behavior — and makes imprecise models impossible to express.

Once you see this, the compiler stops feeling strict and starts feeling *honest*.

The syntax difference is just the surface. The rule underneath is the point.
