Using Options

Option Types describes the definition of the Option type. The Option type can be used for error handling instead of exceptions, or in conjunction with them.

For example, if the value of the argument of the getOrThrow function is Some(v) in the following code, the value of v is returned. If the value of the argument is None, an exception is thrown.

func getOrThrow(a: ?Int64) {
    match (a) {
        case Some(v) => v
        case None => throw NoneValueException()
    }
}

Because Option is a very common type, Cangjie provides multiple deconstruction methods to facilitate its use, including pattern matching, getOrThrow functions, coalescing operators (??), and question mark operators (?). The following describes these methods one by one.

  • Pattern matching: Because the Option type is an enum type, enum pattern matching can be used to deconstruct an Option value, as the sample code above has already shown. In the following example, the getString function receives a parameter of the ?Int64 type, which is a shorthand for Option<Int64>. When the value of the argument is Some(x), the function returns the string representation of the value of x. When the value of the parameter is None, the function returns the string "none".

    func getString(p: ?Int64): String{
        match (p) {
            case Some(x) => "${x}"
            case None => "none"
        }
    }
    main() {
        let a = Some(1)
        let b: ?Int64 = None
        let r1 = getString(a)
        let r2 = getString(b)
        println(r1)
        println(r2)
    }
    

    The execution result of the above code is as follows:

    1
    none
    
  • The coalescing operator ??: For an expression e1 of the type ?T, if you want to substitute the value e2 of the type T when the value of e1 is None, you can use the ?? operator. If the value of e1 is equal to that of Some(v), the expression e1 ?? e2 evaluates to v, and e2 is not evaluated at all. Otherwise, e2 is evaluated and the result is used as the value of the entire coalescing expression. The following is an example:

    main() {
        let a = Some(1)
        let b: ?Int64 = None
        let r1: Int64 = a ?? 0
        let r2: Int64 = b ?? 0
        println(r1)
        println(r2)
    }
    

    The execution result of the preceding code is as follows:

    1
    0
    
  • Question mark operators (?): ? must be used with ., (), [], or {} (in the scenario where the trailing lambda is called) to enable the Option type to support ., (), [], and {}. Take . as an example (the same applies to (), [], and {}). For an expression e of the ?T1 type, when the value of e is Some(v), the value of e?.b is Option<T2>.Some(v.b). Otherwise, the value of e?.b is Option<T2>.None. In both cases, T2 is the type of v.b. The following is an example:

    struct R {
        public var a: Int64
        public init(a: Int64) {
            this.a = a
        }
    }
    
    let r = R(100)
    let x = Some(r)
    let y = Option<R>.None
    let r1 = x?.a   // r1 = Option<Int64>.Some(100)
    let r2 = y?.a   // r2 = Option<Int64>.None
    

    Question mark operators (?) support multi-layer access. The following uses a?.b.c?.d as an example (the same applies to (), [], and {}). The type of the expression a must be Option<T1>, and T1 contains the instance member b. The type of b contains the instance member variable c, and the type of c is Option<T2>. T2 contains the instance member d. The type of the expression a?.b.c?.d is Option<T3>, where T3 is the type of the instance member d of T2. When a is equal to Some(va) and va.b.c is equal to Some(vc), a?.b.c?.d is equal to Option<T3>.Some(vc.d). When a is equal to Some(va) and va.b.c is equal to None, a?.b.c?.d is equal to Option<T3>.None (d is not evaluated). When the value of a is equal to None, the value of a?.b.c?.d is equal to Option<T3>.None (b, c, and d are not evaluated).

    struct A {
        let b: B = B()
    }
    
    struct B {
        let c: Option<C> = C()
        let c1: Option<C> = Option<C>.None
    }
    
    struct C {
        let d: Int64 = 100
    }
    
    let a = Some(A())
    let a1 = a?.b.c?.d // a1 = Option<Int64>.Some(100)
    let a2 = a?.b.c1?.d // a2 = Option<Int64>.None
    
  • getOrThrow functions: The expression e of the ?T type can be deconstructed by calling the getOrThrow function. If the value of e is equal to Some(v), e.getOrThrow() returns the value of v. Otherwise, it throws an exception. The following is an example:

    main() {
        let a = Some(1)
        let b: ?Int64 = None
        let r1 = a.getOrThrow()
        println(r1)
        try {
            let r2 = b.getOrThrow()
        } catch (e: NoneValueException) {
            println("b is None")
        }
    }
    

    The execution result of the preceding code is as follows:

    1
    b is None