Dynamic Features

This section introduces the dynamic features of Cangjie, which enable developers to implement functions more elegantly. The dynamic features of Cangjie consist of reflection and dynamic loading.

Introduction to Cangjie Reflection

Reflection is a mechanism by which a program can access, detect, and modify its own state or behavior.

The dynamic feature of reflection provides the following benefits:

  • It brings higher program flexibility and scalability.

  • A program can use reflection to obtain the types of objects and perform operations such as enumerations and calls on object members during execution.

  • It allows new types to be created at runtime without hard coding in advance.

However, a reflection call typically underperforms a direct call. Therefore, the reflection mechanism is applied primarily to a system framework that demands high flexibility and scalability.

How to Obtain TypeInfo

The TypeInfo class is a must-know when it comes to the reflection feature of Cangjie. This core class holds the information of any type and defines methods for obtaining type information and setting values. To facilitate user operations, we provide a series of information types such as ClassTypeInfo, PrimitiveTypeInfo, and ParameterInfo.

Three static of methods can be used to generate a TypeInfo class.

public class TypeInfo {
    public static func of(a: Any): TypeInfo
    public static func of(a: Object): ClassTypeInfo
    public static func of<T>(): TypeInfo
}

If the of function with input parameters Any and Object types is used, the runtime type information of the instance is output. If the of function with generic parameters is used, the static type information of the input parameters is returned. The two methods generate the same information, but the object may vary.

For example, we can use reflection to obtain the information of a custom type.

import std.reflect.*

class Foo {}

main() {
    let a: Foo = Foo()
    let info: TypeInfo = TypeInfo.of(a)
    let info2: TypeInfo = TypeInfo.of<Foo>()
    println(info)
    println(info2)
}

Compiling and executing the preceding code outputs the following information:

default.Foo
default.Foo

Furthermore, TypeInfo provides the static function get for use in conjunction with the dynamic loading feature. This interface can obtain TypeInfo based on the name of an input type.

public class TypeInfo {
    public static func get(qualifiedName: String): TypeInfo
}

Note that the input parameters must comply with the rules of module/package.type in fully qualified mode. For the types pre-imported by the compiler, including those in the core package and those built in the compiler, such as primitive type, Option, and Iterable, their names must be directly used as the character strings to be searched for, excluding the package names and module name prefixes. If the instance of a type cannot be queried during execution, InfoNotFoundException is thrown.

let t1: TypeInfo = TypeInfo.get("Int64")
let t1: TypeInfo = TypeInfo.get("default.Foo")
let t2: TypeInfo = TypeInfo.get("std/socket.TcpSocket")
let t3: TypeInfo = TypeInfo.get("net/http.ServerBuilder")

In this case, an uninstantiated generic type cannot be obtained.

import std.collection.*
import std.reflect.*

class A<T> {
    A(public let t: T) {}
}

class B<T> {
    B(public let t: T) {}
}

main() {
    let aInfo: TypeInfo = TypeInfo.get("default.A<Int64>")// Error,`default.A<Int64>` is not instantiated, will throw InfoNotFoundException
    let b: B<Int64> = B<Int64>(1)
    let bInfo: TypeInfo = TypeInfo.get("default.B<Int64>")// Ok `default.B<Int64>` has been instantiated.
}

How to Use Reflection to Access Members

After obtaining the TypeInfo class, you can access the instance members and static members of classes through interfaces. In addition, the subclass ClassTypeInfo of TypeInfo provides interfaces for accessing the public constructors of types and their member variables, attributes, and functions. Cangjie reflection is designed to access only the public members of a type, which indicates that private, protected, and default members are invisible in the reflection.

The following example describes how to obtain and modify the member variables of a type instance at runtime.

import std.reflect.*

public class Foo {
    public static var param1 = 20
    public var param2 = 10
}

main(): Unit{
    let obj = Foo()
    let info = TypeInfo.of(obj)
    let staticVarInfo = info.getStaticVariable("param1")
    let instanceVarInfo = info.getInstanceVariable("param2")
    println ("Initial values of member variables")
    print ("Static member variable ${staticVarInfo} of Foo = ")
    println((staticVarInfo.getValue() as Int64).getOrThrow())
    print ("Instance member variable ${instanceVarInfo} of obj = ")
    println((instanceVarInfo.getValue(obj) as Int64).getOrThrow())
    println ("Modify member variables")
    staticVarInfo.setValue(8)
    instanceVarInfo.setValue(obj, 25)
    print ("Static member variable ${staticVarInfo} of Foo = ")
    println((staticVarInfo.getValue() as Int64).getOrThrow())
    print ("Instance member variable ${instanceVarInfo} of obj = ")
    println((instanceVarInfo.getValue(obj) as Int64).getOrThrow())
    return
}

Compiling and executing the preceding code outputs the following information:

Initial values of member variables
Static member variable static param1: Int64 of Foo = 20
Instance member variable of obj param2: Int64 = 10
Modify member variables
Static member variable static param1: Int64 of Foo = 8
Instance member variable of obj param2: Int64 = 25

We can also check and modify attributes through reflection.

import std.reflect.*

public class Foo {
    public let _p1: Int64 = 1
    public prop p1: Int64 {
        get() { _p1 }
    }
    public var _p2: Int64 = 2
    public mut prop p2: Int64 {
        get() { _p2 }
        set(v) { _p2 = v }
    }
}

main(): Unit{
    let obj = Foo()
    let info = TypeInfo.of(obj)
    let instanceProps = info.instanceProperties.toArray()
    println ("The instance member attributes of obj include ${instanceProps}.")
    let PropInfo1 = info.getInstanceProperty("p1")
    let PropInfo2 = info.getInstanceProperty("p2")

    println((PropInfo1.getValue(obj) as Int64).getOrThrow())
    println((PropInfo2.getValue(obj) as Int64).getOrThrow())
    if (PropInfo1.isMutable()) {
        PropInfo1.setValue(obj, 10)
    }
    if (PropInfo2.isMutable()) {
        PropInfo2.setValue(obj, 20)
    }
    println((PropInfo1.getValue(obj) as Int64).getOrThrow())
    println((PropInfo2.getValue(obj) as Int64).getOrThrow())
    return
}

Compiling and executing the preceding code outputs the following information:

The instance member attributes of obj include [prop p1: Int64, mut prop p2: Int64].
1
2
1
20

The reflection mechanism also supports function calls.

import std.reflect.*

public class Foo {
    public static func f1(v0: Int64, v1: Int64): Int64 {
        return v0 + v1
    }
}

main(): Unit {
    var num = 0
    let intInfo = TypeInfo.of<Int64>()
    let funcInfo = TypeInfo.of<default.Foo>().getStaticFunction("f1", intInfo, intInfo)
    num = (funcInfo.apply([1, 1]) as Int64).getOrThrow()
    println(num)
}

Compiling and executing the preceding code outputs the following information:

2

Dynamic Loading

As compared to compile-time loading, also known as static loading, dynamic loading allows a running Cangjie program to access a Cangjie dynamic module through specific functions to read and write global variables, call global functions, and obtain the type information. The dynamic loading capability of Cangjie depends on the ModuleInfo and PackageInfo classes.

For example, package0 in module0 contains a public class Foo, and the Cangjie dynamic module path is "./module_package.so." After the program is dynamically loaded, the type information of the Foo can be obtained at runtime.

let m = ModuleInfo.load("./module_package")
let p = m.getPackageInfo("package0").getOrThrow()
let at = TypeInfo.get("module0/package0.Foo")