Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell/pony-terms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ quiescence
quiescent
unmuted
unmutes
iftype
Zulip
15 changes: 15 additions & 0 deletions code-samples/control-structures-iftype-basic.pony
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions code-samples/control-structures-iftype-capability.pony
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions code-samples/control-structures-iftype-elseif.pony
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions code-samples/control-structures-iftype-narrowing.pony
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions code-samples/control-structures-iftype-tuple.pony
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions docs/appendices/keywords.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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` |
Expand Down
5 changes: 3 additions & 2 deletions docs/appendices/symbol-lookup-cheat-sheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 |
68 changes: 68 additions & 0 deletions docs/expressions/control-structures.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
2 changes: 2 additions & 0 deletions docs/generics/generic-constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading