๐Ÿ 

types are sets

the single idea that unlocks the whole type system.

the usual mental model

In most languages, a type is something your compiler enforces:

def greet(name: str) -> None: ...    # compile/check-time rule

The type exists in a different layer from the values. You can't say "is 42 a positive-int-less-than-100 between odd numbers?" because your type system doesn't let you express that kind of thing.

the zef mental model

a type is just
a set of values.
membership is a runtime question.
42 | is_a(Int)           # True
'hi' | is_a(Int)          # False
42 | is_a(Any)           # True โ€” Any is the universe

"Is this value a member of that set?" That's it. No static analysis phase. No generics infrastructure. Just sets.

set operations on types

Once you think of types as sets, the usual operators make sense:

IntOrString = Int | String            # union โˆช
BoundedInt  = Int & (Z < 100)         # intersection โˆฉ
NotString   = ~String                 # complement

Test them:

5 | is_a(IntOrString)               # True
'hi' | is_a(IntOrString)            # True
3.14 | is_a(IntOrString)            # False

50 | is_a(BoundedInt)               # True
999 | is_a(BoundedInt)              # False

refinement types โ€” constraints as types

The Z placeholder lets you build predicates that are types:

Positive      = Int & (Z > 0)
Adult         = Int & (Z >= 18) & (Z < 120)
NonEmptyStr   = String & (Z | length > 0)
LongRunningID = String & (Z | length == 22)

Now you can filter, test, pattern-match with them:

ages = [15, 17, 20, 150, 99]
ages | filter(Adult) | collect    # [20, 99]

21 | match([
    (Adult,       'welcome in'),
    (Int,         'sorry kid'),
    (Any,         'what?'),
]) | collect
mental model โ€” the venn diagram
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Any โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Int โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Positive โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ (Int & Z>0)โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ each ring is a set of values. nesting = subset. & is intersection, | is union, ~ is complement.

Z-expressions that return types

Here's the wild one. You can write a predicate as a normal-looking Python expression with Z, and it literally becomes a type:

InsideUnitSphere = (Z[0]**2 + Z[1]**2 + Z[2]**2 <= 1)

[0.2, 0.5, 0.8] | is_a(InsideUnitSphere)   # True

points | filter(InsideUnitSphere) | collect

That's a 1-line, in-lined, runtime-checkable, composable, first-class type.

container types

Collection types can be refined by element type:

[1, 2, 3] | is_a(Array)                # True
[1, 2, 3] | is_a(Array[Int])           # True
[1, 3.14] | is_a(Array[Int])            # False
[1, 3.14] | is_a(Array[Int | Float])   # True

And tuple-shaped:

IntAndString = Tuple[Int, String]
[1, '๐ŸŒฟ'] | is_a(IntAndString)      # True
[1, 2] | is_a(IntAndString)         # False

And dict-shaped:

IntKeyedStr = Dict[Int, String]
{1: 'a', 2: 'b'} | is_a(IntKeyedStr)     # True
{1: 'a', 2: 2} | is_a(IntKeyedStr)       # False

refined entity types

The most expressive form. Put constraints on entity fields:

Adult = ET.Person[
    'name': String,
    'age':  Int & (Z > 18),
    ...                          # allow other fields
]

jack  = ET.Person(name='Jack', age=21)
alice = ET.Person(name='Alice', age=10)

jack  | is_a(Adult)       # True
alice | is_a(Adult)       # False

"what is the type of 42?"

In Zef, this question has many correct answers. A value of 42 is a member of all these sets:

Any
Int
Int32
Int & (Z > 0)
Int & (Z == 42)
{42, 100, 1000}

There's no anointed "the" type. If you really need one answer, Zef provides two:

opwhat it returns
layout_type(x)the physical byte layout (e.g. Int32)
primary_type(x)the common-sense "category" (e.g. Int)
42 | layout_type | collect       # Int32
42 | primary_type | collect      # Int
UInt8(42) | layout_type | collect # UInt8
UInt8(42) | primary_type | collect # Int  โ€” same category

why this design wins

Traditional type systems are closed: you enumerate types in advance, and the compiler knows them all. Set-theoretic types are open: you can combine them with Boolean operators and predicates to get any shape you need, on the fly. The language of types and the language of values become one.

recap

build a type

Write a type for "a dict with keys 'email' (string containing '@') and 'age' (integer 0-150)":

answer
Email = String & ('@' | Z.contains)   # rough approximation
Age   = Int & (Z >= 0) & (Z <= 150)
User  = Dict['email': Email, 'age': Age, ...]

Next up: entities โ€” ET, UIDs, and those cute ๐Ÿƒ prefixes. โ†’