How do I extract the base type value from a constrained type?

← Advanced Type System · Ref: Q718

The promote operator (#^) extracts the base type value from a constrained type, providing an explicit escape hatch for crossing type boundaries.

PROMOTE RETURNS THE BASE TYPE

The #^ operator extracts the underlying value:

  rawInt <- #^ age        //DrivingAge -> Integer

The result is the base type, not the constrained type. Only types whose base type defines promote (#^) support this operator.

WHICH CONSTRAINED TYPES HAVE PROMOTE

Promote is available when the base type defines it. Integer defines promote (to Float), so DrivingAge has #^. String does NOT define promote, so a String-constrained type like Name cannot use #^. Use $ (string conversion) instead for String-constrained types.

CONSTRAINED ENUMS BLOCK PROMOTE

Constrained enumerations deliberately prevent promotion back to the base enum:

  redSuit <- RedCardSuit("Hearts")
  //Cannot do: cardSuit <- #^ redSuit   //No promote to CardSuit

This is by design: constrained enums are fully disconnected types. If you could promote RedCardSuit to CardSuit, it would bypass the type boundary.

WHY PROMOTE EXISTS

Constrained types are disconnected: DrivingAge is NOT an Integer subtype. You cannot pass DrivingAge where Integer is expected. Promote is the explicit conversion:

  processInteger()
    -> val as Integer
    //...
  age <- DrivingAge(25)
  rawInt <- #^ age
  processInteger(rawInt)     //extract then pass

PROMOTE VS STRING

Two different conversions:

  text <- $age          //String representation: "25"
  raw <- #^ age         //Base type value: Integer 25

Both are useful but serve different purposes.

See Q257 for constrained type overview. See Q25 for promote operator basics. See Q722 for type hierarchy details.

Example

defines module qa.advancedtypes.constrainedpromote

  defines type

    DrivingAge as Integer constrain as
      >= 16 and <= 100

    Name as String constrain as
      matches /^[a-zA-Z -]+$/

  defines function

    <?-
      Accepts an Integer — cannot take DrivingAge directly.
    -?>
    processInteger() as pure
      -> intValue as Integer
      <- result as String: `Raw integer: ${intValue}`

  defines program

    ConstrainedPromoteDemo()
      stdout <- Stdout()

      age <- DrivingAge(25)

      // === PROMOTE TO BASE TYPE ===
      rawInt <- #^ age
      stdout.println(`Promoted DrivingAge to Integer: ${rawInt}`)

      // === PASS TO FUNCTION EXPECTING BASE TYPE ===
      extracted <- #^ age
      output <- processInteger(extracted)
      stdout.println(output)

      // === STRING CONVERSION VS PROMOTE ===
      asText <- $age
      stdout.println(`String conversion: ${asText}`)

      asInteger <- #^ age
      stdout.println(`Promote to Integer: ${asInteger}`)

      // === STRING-CONSTRAINED: use $ not #^ ===
      //String has no promote operator, so Name does not either.
      //Use string conversion ($) instead:
      name <- Name("Alice Smith")
      nameAsStr <- $name
      stdout.println(`Name as String: ${nameAsStr}`)

      // === ISSET STILL WORKS ===
      isValid <- age?
      stdout.println(`DrivingAge is set: ${isValid}`)

      // === HASHCODE ===
      hashVal <- #? age
      stdout.println(`Hashcode: ${hashVal}`)

Common mistakes

E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.

Incorrect:

output <- processInteger(extracted).toUpperCase()

Correct:

output <- processInteger(extracted)

E50060 — Name has no getValue() method in EK9. Use the $ prefix operator to convert to String. See ek9 -h E50060 for details.

Incorrect:

nameAsStr <- name.getValue()

Correct:

nameAsStr <- $name
Other ways to ask this
  • What does the #^ promote operator do with constrained types?
  • How do I convert a DrivingAge back to an Integer?
  • Why can't I promote a constrained enumeration to its base enum?

Coming from another language?

Java: no constrained types, no promote concept. Explicit casting or wrapper.getValue() for custom wrappers. Python: no type constraints, no promote. Rust: newtype pattern uses .0 field access or Into trait for conversion. Go: type conversion syntax T(value) converts between compatible types. Kotlin: value classes use underlying property access. EK9: promote (#^) is the explicit operator for crossing constrained type boundaries — returns the base type value, available on value-constrained types but blocked on constrained enumerations.

Keywords: constrained, boundary, base-type, explicit, extract, convert, disconnected, promote, enum