Skip to content

Containers

Element types inferred from first use, and the difference between mutating a container the function made and one it was given.

Run it

python  containers.ppy
ppy     containers.ppy
ppy run containers.ppy

What it prints

python containers.ppy, ppy containers.ppy, ppy run containers.ppy

[(1, 1), (2, 2), (3, 3)]
[0, 1, 4, 9, 16, 25]
3
[1, 2, 3, 4, 5]

Element types from first use

@ppy.pure
def grow(count: int) -> list[int]:
    out = []
    for i in range(count):
        out.append(i * i)
    return out
  • out = [] has no element type until out.append(i * i) makes it a list[int].
  • seen = set() becomes a set[str] at seen.add(value).
  • counts is declared dict[int, int], and counts.get(value, 0) + 1 checks against it.

None of these functions is native, because a dict or a set has no native form. All of them are strict, typed, and pure.

Local mutation is pure, shared mutation is not

flatten extends a list it created, so it is pure. Had it extended rows, the write would be an effect on an argument and @ppy.pure would fail with E1601.

The distinction is by alias, not by name: ys = xs; ys.append(1) mutates xs whatever it is called, and the analysis follows the alias to say so. The same alias map is what lets ppy convert declare a read-only parameter as Sequence[T] rather than list[T].

Where the code comes from

containers.ppy is hand-written; there is no .py source and no conversion step.

Read on: Conversion and inference ยท Effects and purity

17_containers/containers.ppy

import ppy


@ppy.pure
def histogram(values: list[int]) -> dict[int, int]:
    counts: dict[int, int] = {}
    for value in values:
        counts[value] = counts.get(value, 0) + 1
    return counts


@ppy.pure
def grow(count: int) -> list[int]:
    out = []
    for i in range(count):
        out.append(i * i)
    return out


@ppy.pure
def distinct(values: list[str]) -> int:
    seen = set()
    for value in values:
        seen.add(value)
    return len(seen)


@ppy.pure
def flatten(rows: list[list[int]]) -> list[int]:
    out: list[int] = []
    for row in rows:
        out.extend(row)
    return out


def main() -> None:
    print(sorted(histogram([1, 2, 2, 3, 3, 3]).items()))
    print(grow(6))
    print(distinct(["a", "b", "a", "c"]))
    print(flatten([[1, 2], [3], [], [4, 5]]))


main()

Source: examples/17_containers.