From f8bcba086f39f2ba107462f6faf8f7598397faf3 Mon Sep 17 00:00:00 2001 From: "Sean T. Allen" Date: Thu, 2 Apr 2026 23:32:46 -0400 Subject: [PATCH] Document iftype in the control structures section iftype has been around for a long time but was never documented beyond a keyword table entry. This adds a full section covering basic syntax, type narrowing, elseif chaining, capability conditions, tuple conditions, and limitations, with 5 compilable code samples. Also updates the keywords table, symbol lookup cheat sheet, and adds a cross-reference from the generics constraints page. Closes #393 --- .cspell/pony-terms.txt | 1 + .../control-structures-iftype-basic.pony | 15 ++++ .../control-structures-iftype-capability.pony | 23 +++++++ .../control-structures-iftype-elseif.pony | 19 ++++++ .../control-structures-iftype-narrowing.pony | 20 ++++++ .../control-structures-iftype-tuple.pony | 15 ++++ docs/appendices/keywords.md | 8 +-- docs/appendices/symbol-lookup-cheat-sheet.md | 5 +- docs/expressions/control-structures.md | 68 +++++++++++++++++++ docs/generics/generic-constraints.md | 2 + 10 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 code-samples/control-structures-iftype-basic.pony create mode 100644 code-samples/control-structures-iftype-capability.pony create mode 100644 code-samples/control-structures-iftype-elseif.pony create mode 100644 code-samples/control-structures-iftype-narrowing.pony create mode 100644 code-samples/control-structures-iftype-tuple.pony diff --git a/.cspell/pony-terms.txt b/.cspell/pony-terms.txt index 223e22a2..52b4504e 100644 --- a/.cspell/pony-terms.txt +++ b/.cspell/pony-terms.txt @@ -27,4 +27,5 @@ quiescence quiescent unmuted unmutes +iftype Zulip diff --git a/code-samples/control-structures-iftype-basic.pony b/code-samples/control-structures-iftype-basic.pony new file mode 100644 index 00000000..516f2fc2 --- /dev/null +++ b/code-samples/control-structures-iftype-basic.pony @@ -0,0 +1,15 @@ +trait Animal +class Cat is Animal +class Dog is Animal + +actor Main + new create(env: Env) => + greet[Cat](env) + greet[Dog](env) + + fun greet[A: Animal](env: Env) => + iftype A <: Cat then + env.out.print("meow") + else + env.out.print("woof") + end diff --git a/code-samples/control-structures-iftype-capability.pony b/code-samples/control-structures-iftype-capability.pony new file mode 100644 index 00000000..51474307 --- /dev/null +++ b/code-samples/control-structures-iftype-capability.pony @@ -0,0 +1,23 @@ +trait Animal + +class Cat is Animal + var _name: String = "Cat" + + fun box name(): String => _name + fun ref set_name(name': String) => _name = name' + +actor Main + new create(env: Env) => + let cat: Cat ref = Cat + maybe_rename[Cat ref](cat, env) + + let cat2: Cat val = Cat + maybe_rename[Cat val](cat2, env) + + fun maybe_rename[A: Animal](a: A, env: Env) => + iftype A <: Cat ref then + a.set_name("Kitty") + env.out.print(a.name()) + elseif A <: Cat box then + env.out.print(a.name()) + end diff --git a/code-samples/control-structures-iftype-elseif.pony b/code-samples/control-structures-iftype-elseif.pony new file mode 100644 index 00000000..89df5830 --- /dev/null +++ b/code-samples/control-structures-iftype-elseif.pony @@ -0,0 +1,19 @@ +trait Animal +class Cat is Animal +class Dog is Animal +class Fish is Animal + +actor Main + new create(env: Env) => + describe[Cat](env) + describe[Dog](env) + describe[Fish](env) + + fun describe[A: Animal](env: Env) => + iftype A <: Cat then + env.out.print("I'm a cat") + elseif A <: Dog then + env.out.print("I'm a dog") + else + env.out.print("I'm something else") + end diff --git a/code-samples/control-structures-iftype-narrowing.pony b/code-samples/control-structures-iftype-narrowing.pony new file mode 100644 index 00000000..a08da143 --- /dev/null +++ b/code-samples/control-structures-iftype-narrowing.pony @@ -0,0 +1,20 @@ +trait Animal + fun name(): String + +class Cat is Animal + fun name(): String => "Cat" + fun purr(): String => "prrr" + +class Dog is Animal + fun name(): String => "Dog" + +actor Main + new create(env: Env) => + describe[Cat val](Cat, env) + describe[Dog val](Dog, env) + + fun describe[A: Animal val](a: A, env: Env) => + env.out.print(a.name()) + iftype A <: Cat val then + env.out.print(a.purr()) + end diff --git a/code-samples/control-structures-iftype-tuple.pony b/code-samples/control-structures-iftype-tuple.pony new file mode 100644 index 00000000..4c59f991 --- /dev/null +++ b/code-samples/control-structures-iftype-tuple.pony @@ -0,0 +1,15 @@ +trait Animal +class Cat is Animal +class Dog is Animal + +actor Main + new create(env: Env) => + check[Cat, Dog](env) + check[Cat, Cat](env) + + fun check[A: Animal, B: Animal](env: Env) => + iftype (A, B) <: (Cat, Dog) then + env.out.print("cat and dog") + else + env.out.print("some other combination") + end diff --git a/docs/appendices/keywords.md b/docs/appendices/keywords.md index 3e08eb5d..e9a18641 100644 --- a/docs/appendices/keywords.md +++ b/docs/appendices/keywords.md @@ -16,10 +16,10 @@ This listing explains the usage of every Pony keyword. | `consume` | move a value to a new variable, leaving the original variable empty | | `digestof` | create a `USize` value that summarizes the Pony object, similar to a Java object's `hashCode()` value. | | `do` | loop statement, or after a with statement | -| `else` | conditional statement in if, for, while, repeat, try (as a catch block), match | -| `elseif` | conditional statement, also used with `ifdef` | +| `else` | conditional statement in if, iftype, for, while, repeat, try (as a catch block), match | +| `elseif` | conditional statement, also used with `ifdef` and `iftype` | | `embed` | embed a class as a field of another class | -| `end` | ending of: `if then`, `ifdef`, `while do`, `for in`, `repeat until`, `try`, `object`, `recover`, `match` | +| `end` | ending of: `if then`, `ifdef`, `iftype`, `while do`, `for in`, `repeat until`, `try`, `object`, `recover`, `match` | | `error` | raises an error | | `for` | loop statement | | `fun` | define a function, executed synchronously | @@ -45,7 +45,7 @@ This listing explains the usage of every Pony keyword. | `repeat` | loop statement | | `return` | to return early from a function | | `tag` | reference capability – neither readable nor writeable, only object identity | -| `then` | (1) in if conditional statement | +| `then` | (1) in if and iftype conditional statements | | | (2) as a (finally) block in try | | `this` | the current object | | `trait` | used in nominal subtyping: `class Foo is TraitName` | diff --git a/docs/appendices/symbol-lookup-cheat-sheet.md b/docs/appendices/symbol-lookup-cheat-sheet.md index 046261d1..16a30f27 100644 --- a/docs/appendices/symbol-lookup-cheat-sheet.md +++ b/docs/appendices/symbol-lookup-cheat-sheet.md @@ -14,7 +14,7 @@ Pony, like just about any other programming language, has plenty of odd symbols | `~` | Partial application | | `?` | Partial function | | `'` | Prime | -| `<:` | Subtype | +| `<:` | Subtype, iftype | Here is a more elaborate explanation of Pony's use of special characters: (a line with (2) or (3) means an alternate usage of the symbol of the previous line) @@ -67,4 +67,5 @@ Here is a more elaborate explanation of Pony's use of special characters: (a lin | `->` | (1) arrow type | | | (2) viewpoint | | `._i` | where `i = 1,2,…` means the item at position i in the tuple | -| `<:` | "is a subtype of" or "can be substituted for" | +| `<:` | (1) "is a subtype of" or "can be substituted for" | +| | (2) used in `iftype` conditions to check subtype relationships | diff --git a/docs/expressions/control-structures.md b/docs/expressions/control-structures.md index f103d8c3..c0730b7f 100644 --- a/docs/expressions/control-structures.md +++ b/docs/expressions/control-structures.md @@ -193,3 +193,71 @@ Suppose we're trying to create something and we want to keep trying until it's g Just like `while` loops, the value given by a `repeat` loop is the value of the expression within the loop on the last iteration, and `break` and `continue` can be used. __Since you always go round a repeat loop at least once, do you ever need to give it an else expression?__ Yes, you may need to. A `continue` in the last iteration of a `repeat` loop needs to get a value from somewhere, and an `else` expression is used for that. + +## Iftype + +`iftype` is a compile-time conditional that selects a code path based on whether a type parameter satisfies a subtype constraint. It uses the `<:` operator to check the subtype relationship: `iftype A <: B` asks "is `A` a subtype of `B`?" + +Because the condition is resolved at compile time, only the matching branch is included in the generated code. Both branches are type-checked, but the non-matching one is discarded from the output. + +__How is iftype different from match?__ A `match` expression dispatches on the runtime type of a value. `iftype` operates on type parameters as they are known at compile time. If you call a generic function `foo[Animal](Cat)`, the type parameter is `Animal`, not `Cat` — so `iftype A <: Cat` would be false, even though the runtime value is a `Cat`. + +Here's a simple example with a generic function that behaves differently depending on whether the type parameter is a specific class: + +```pony +--8<-- "control-structures-iftype-basic.pony" +``` + +This prints "meow" and then "woof". When `greet` is called with `Cat`, the compiler sees that `Cat <: Cat` is true and takes the `then` branch. When called with `Dog`, `Dog <: Cat` is false, so it takes the `else` branch. + +### Type narrowing + +Inside the `then` branch, the compiler knows the subtype relationship holds. This means you can call methods on a value that are only available on the constraining type: + +```pony +--8<-- "control-structures-iftype-narrowing.pony" +``` + +In the `then` branch the compiler knows `A <: Cat val`, so calling `a.purr()` is valid even though the method doesn't exist on the `Animal` trait. + +### Elseif + +You can chain multiple type checks with `elseif`, just like a regular `if`: + +```pony +--8<-- "control-structures-iftype-elseif.pony" +``` + +### Capabilities in conditions + +The condition can include a reference capability. This lets you write different code depending on what the caller can do with a value: + +```pony +--8<-- "control-structures-iftype-capability.pony" +``` + +Here the `ref` branch can call `set_name` because it knows it has write access. The `box` branch can only read. + +### Tuple conditions + +When a function has multiple type parameters, you can check them together using a tuple condition. Both sides of `<:` must be tuples of the same size: + +```pony +--8<-- "control-structures-iftype-tuple.pony" +``` + +The condition `(A, B) <: (Cat, Dog)` is true only when `A` is a subtype of `Cat` __and__ `B` is a subtype of `Dog`. + +### Limitations + +__Can I use iftype outside of a generic function?__ No. The subtype (the left side of `<:`) must be a type parameter or a tuple of type parameters. You cannot use concrete types. For example, this does not compile: + +```pony +iftype String <: Stringable then + ... +end +``` + +Like other control structures in Pony, `iftype` is an expression. Its value is the value of whichever branch is taken. If the `then` and `else` branches produce different types, the `iftype` expression produces a union of those types. + +__What if my iftype doesn't have an else?__ Any `else` branch that doesn't exist gives an implicit `None`, just like `if`. diff --git a/docs/generics/generic-constraints.md b/docs/generics/generic-constraints.md index 47a28747..9e13eec8 100644 --- a/docs/generics/generic-constraints.md +++ b/docs/generics/generic-constraints.md @@ -23,3 +23,5 @@ In the previous section, we went through extra work to support `iso`. If there's ```pony --8<-- "generic-constraints-foo-any-read.pony" ``` + +Constraints restrict what types a caller can provide. If you need different behavior depending on the actual type used, see [iftype](/expressions/control-structures.md#iftype) — a compile-time conditional that branches on subtype relationships.