What is the Any type and when should I use it?

← Advanced Type System · Ref: Q254

Any is the universal base interface in EK9. Every type implicitly inherits from Any, making it the root of the entire type hierarchy. This is similar to Java's Object or Python's object, but Any is an interface, not a class.

OPERATORS AND ANY

Any carries no usable operator except ? (isSet). A value held as Any can be asked x? but not x == y or $x: those are E07620 (operator not defined).
Your own types do NOT inherit operators from Any either. Six operators form the canonical set every type is expected to provide for itself:
- ? (isSet) - check if object has a meaningful value
- == (equality) - compare two objects for equality
- <=> (comparison) - three-way comparison for ordering
- $ (string) - convert to human-readable String representation
- $$ (json) - convert to JSON representation
- #? (hashcode) - compute a hash code for the object
A class provides them with 'default operator' or explicit definitions (sameness rules: E07181/E07184), and a type with fields must define ? (E07235). Until it does, a == b on it is E07620. Built-in types such as String define them already, which is why the program below can use ?, $ and #? on a String.

ANY IN THE DISPATCHER PATTERN

The most common use of Any is as the fallback parameter in a dispatcher method. The dispatcher entry point takes an Any parameter, and specific overloads handle known types:

  describe() as dispatcher
    -> item as Any
    <- rtn as String: "Unknown"
  private describe()
    -> item as Integer
    <- rtn as String: "Integer"

The Any overload catches everything not handled by specific overloads.

COST-BASED MATCHING WITH ANY

When the compiler resolves which dispatcher overload to call, Any matches score HIGH_COST (20.0). This means Any is always the LEAST preferred match. Exact type matches score 0.0, superclass matches score 0.05 per level, trait matches score 0.10 per level, and promotion matches score 0.5. Any's 20.0 cost ensures specific overloads always win.

WHEN TO USE ANY

- Dispatcher entry points - the base method that catches unhandled types
- Generic programming - when you truly need to work with any type
- Polymorphic containers - when mixing types is genuinely required; declare it: items as List of Any: [42, "hello"] (an undeclared [42, "hello"] shares no type but Any and is E50030)
- Framework-level code - infrastructure that must handle arbitrary types

WHEN NOT TO USE ANY

- Specific type handling - use the actual type for type safety
- API parameters - use specific types so callers know what to pass
- Return types - specific return types help callers avoid type checks
- When you know the type - Any loses compile-time type information

Using Any too broadly is a code smell. If you find yourself using Any everywhere and then dispatching on types, consider whether your design could use traits or abstract classes to express the relationship directly.

See Q24 for the dispatcher pattern and type conversion. See Q211 for double dispatch. See Q238 for the finite operator set. See Q255 for method resolution costs. See Q267 for anti-patterns including overusing Any.

Example

defines module qa.advancedtypes.anytype

  defines class

    TypeDescriber

      describe() as dispatcher
        -> item as Any
        <- rtn as String: "Unknown type"

      private describe()
        -> item as Integer
        <- rtn as String: `Integer: ${item}`

      private describe()
        -> item as Float
        <- rtn as String: `Float: ${item}`

      private describe()
        -> item as String
        <- rtn as String: `String: ${item}`

      private describe()
        -> item as Boolean
        <- rtn as String: `Boolean: ${item}`

  defines program

    AnyTypeDemo()
      stdout <- Stdout()

      describer <- TypeDescriber()

      // A heterogeneous list DECLARES its element type: elements that share no type but Any are E50030
      items as List of Any: [42, 3.14, "hello", true]
      for listItem in items
        stdout.println(describer.describe(listItem))

      // Any fallback for types without specific overloads
      mixedItems as List of Any: [Date(), Duration()]
      for mixedItem in mixedItems
        stdout.println(describer.describe(mixedItem))

      // String defines ?, $ and #? itself - Any does not supply them
      greeting <- "Hello"
      stdout.println(`isSet: ${greeting?}`)
      stdout.println(`string: ${greeting}`)
      stdout.println(`hashcode: ${#?greeting}`)

Common mistakes

E50030 — Integer, Float, String and Boolean share no type but Any, and EK9 never invents a List of Any - an undeclared mix is E50030 at the literal. Declare the element type when a heterogeneous list is what you want.

Incorrect:

items <- [42, 3.14, "hello", true]

Correct:

items as List of Any: [42, 3.14, "hello", true]

E50060 — TypeDescriber does not have a process() method. The correct method name is describe(). Using a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.

Incorrect:

stdout.println(describer.process(listItem))

Correct:

stdout.println(describer.describe(listItem))
Other ways to ask this
  • How does the universal base type work in EK9?
  • What is the root of the EK9 type hierarchy?
  • When should I use Any instead of a specific type?
  • How does Any relate to dispatchers?

Coming from another language?

Java: Object is the root class, all classes extend Object, has equals/hashCode/toString. Python: object is the root, all classes inherit from it, has __eq__/__hash__/__str__. C#: object/System.Object is universal base, has Equals/GetHashCode/ToString. Rust: no universal base type, uses trait objects (dyn Any) for type erasure. Go: interface{} (or 'any' in Go 1.18+) is the empty interface, no methods guaranteed. Kotlin: Any is the root type with equals/hashCode/toString. EK9: Any is a universal base interface (not class) that carries no usable operator except ?; each type provides ==, <=>, $, $$, #? itself (default operator or explicit), HIGH_COST (20.0) matching ensures specific types always preferred in dispatchers.

Keywords: advanced, default, type, cost, hierarchy, operator, inherit, interface, root, dispatcher, any, fallback, base, migrate, type-system, universal, object