Why must Result be used with two different types?

← Generics · Ref: Q652

EK9 requires Result to be parameterised with two DIFFERENT types. Using 'Result of (String, String)' is an error because the compiler cannot distinguish success from failure when both have the same type.

DIFFERENT TYPES REQUIRED

The ok type and error type must differ:

  Result of (Integer, String)  // correct: Integer ok, String error
  Result of (String, String)   // ERROR E06190: same types

WHY REQUIRED

Result's purpose is to carry EITHER a success value OR an error value. If both are the same type, there is no way to distinguish which state the Result is in. The type system loses its ability to guarantee safety.

CORRECT PATTERNS

Use distinct types for success and error:

  Result of (UserRecord, String)     // success: record, error: message
  Result of (Integer, ErrorCode)     // success: count, error: enum
  Result of (Float, Exception)       // success: value, error: exception

CREATING RESULTS

Use the ok or error constructor:

  okResult <- Result(42, String())   // ok result
  errResult <- Result(Integer(), "failed")  // error result

See Q48 for Result basics. See Q198 for built-in generics. See Q104 for error handling patterns.

Example

defines module qa.genericsdeep.resultdifferenttypes

  defines function

    <?-
      Correct: Result with different ok and error types.
      Integer for success value, String for error message.
    -?>
    safeDivide() as pure
      ->
        numerator as Integer
        denominator as Integer
      <- rtn as Result of (Integer, String): Result(Integer(), "not calculated")

      if denominator == 0
        rtn: Result(Integer(), "division by zero")
      else
        rtn: Result(numerator / denominator, String())

    <?-
      Correct: Result with custom type and String error.
    -?>
    parseScore() as pure
      -> input as String
      <- rtn as Result of (Float, String): Result(Float(), "not parsed")

      if input?
        rtn: Result(Float(input), String())

  defines program

    ResultDifferentTypesDemo()
      stdout <- Stdout()

      // === SUCCESSFUL DIVISION ===

      divResult <- safeDivide(numerator: 10, denominator: 3)
      stdout.println(`Division result set: ${divResult?}`)

      // === DIVISION BY ZERO ===

      zeroResult <- safeDivide(numerator: 10, denominator: 0)
      stdout.println(`Zero division set: ${zeroResult?}`)

      // === PARSE RESULT ===

      scoreResult <- parseScore("95.5")
      stdout.println(`Parse result set: ${scoreResult?}`)

Common mistakes

E06190 — Result must be parameterised with two different types so the compiler can distinguish success from error. Using the same type for both (e.g., Result of (String, String)) triggers E06190. See ek9 -h E06190 for details.

Incorrect:

rtn as Result of (String, String): Result(String(), "not calculated")

Correct:

rtn as Result of (Integer, String): Result(Integer(), "not calculated")
Other ways to ask this
  • What is E06190 Result must have different types?
  • Can I use Result with the same type for success and error?
  • How does EK9 prevent Result type ambiguity?

Coming from another language?

Java: no built-in Result type. Rust: Result<T, E> allows same types (Result<String, String> is valid). Go: multiple return values (value, error). Kotlin: Result<T> wraps only success, uses exceptions for errors. EK9: Result of (OK, ERR) requires OK and ERR to be different types for unambiguous state.

Keywords: ambiguity, E06190, ok, success, type-parameter, result, error, types, generic, guard, different