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.

DEFAULT OPERATORS FROM ANY

All types inherit six default operators from Any:

  ? (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

These operators are always available on every type and can be overridden with 'override operator' in your own types.

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
  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()

      // Heterogeneous list creates List of Any
      items <- [42, 3.14, "hello", true]
      for listItem in items
        stdout.println(describer.describe(listItem))

      // Any fallback for types without specific overloads
      mixedItems <- [Date(), Duration()]
      for mixedItem in mixedItems
        stdout.println(describer.describe(mixedItem))

      // Default operators available on every type via Any
      greeting <- "Hello"
      stdout.println(`isSet: ${greeting?}`)
      stdout.println(`string: ${greeting}`)
      stdout.println(`hashcode: ${#?greeting}`)

Common mistakes

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) with six default operators (?, ==, <=>, $, $$, #?), HIGH_COST (20.0) matching ensures specific types always preferred in dispatchers.

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