On this page:
3.1 Comments
3.2 Variables
3.3 Variable Assignments
3.4 Data Types
3.5 Collections
3.6 Path
3.7 Operators
3.8 Flow Control
3.8.1 Branching
3.8.2 Looping
3.8.2.1 重复
3.8.2.2 当 1
3.8.2.3 当 2
3.8.2.4 执行 ... 直到 ...
3.8.2.5 跳出 and 继续
3.9 Iteration
3.10 Functions
3.10.1 Function Invocation
3.11 User-define Functions
3.12 Exception Handling
3.12.1 Intentionally Triggering Exceptions
3.13 Complex Path
3.13.1 Long Path
3.13.2 Expression Evaluation within Paths
3.14 Function Calls within Paths
3.15 Modulization
3.15.1 Requiring
3.15.2 Providing
3.16 Label Type
3.17 Metaprogramming

3 Core Syntax🔗

3.1 Comments🔗

Comments are a unique concept in programming languages; they do not implement the functionality of the code themselves but are used to explain the code, making it easier to read and understand. Therefore, during the execution of a program, comments are skipped and not executed.

In Raqet language, we use the half-width semicolon ; to denote comments.

A single ; indicates the start of a single-line comment, and anything from that point to the end of the line will not be executed. For example:

; Comments that start with a semicolon ; are not executed, and can be used to explain the code

Sometimes, the content we need to explain is too long to fit on a single line, so we can also use multi-line comments.

Multi-line comments start with two consecutive semicolons ;; and end with another pair of semicolons ;; as follows:

;; This is the start of a multi-line comment
Here is a new line, which is still part of the comment content
New lines do not require any special markers
This continues until another pair of consecutive semicolons;;

In the documentation, when we need to explain the code, we will also use comments. Please remember that comments are not executed. In the example code provided later, if you need to copy it into an interactive compiler or editor for execution, you can ignore the comment part, and of course, you can copy it all together, as the parser will automatically ignore the comments.

3.2 Variables🔗

Computers can perform very complex program operations. However, no matter how complex the program is, its underlying structure will be decomposed into simple binary operations. That is to say, the process of program execution is essentially a large number of operations. When we perform complex operations manually, we need scratch paper to record the intermediate values during the operation process. Similarly, during the running process of a computer, it also needs to record these intermediate values. In programming languages, we call this way of recording variables.

Variables are very important syntactic elements in Raqet language. For the convenience of reading and syntactic analysis, we have stipulated syntactically that all variables need to start with the # symbol. So when you see the # symbol in Raqet code, you can determine that this is a variable.

During the process of parsing, once a # symbol is found, it will search forward until a certain blank character or a symbol in Raqet language. The part in between will be regarded as a variable. For example:

#变量1 #甲 #foo

During the analysis, based on the blank characters, the above code will be split into 3 variables.

If a symbol appears, it will also be split. For example:

#变量1+#变量2

Although the code is written incessantly here, because a symbol + appears in the middle, it will be interpreted as #变量1 plus #变量2.

As we mentioned in the Overview, symbols have corresponding Chinese characters. Let’s take a look at the above code after converting to Chinese symbols:

#变量1  #变量2

Here, the + is replaced by the Chinese character . At this time, a problem arises. We have no way to specify whether this is a Chinese symbol or part of the variable name. After all, we cannot require programmers to prohibit the use of the character when naming variables. Otherwise, there would be too many Chinese symbols that need to be prohibited. Therefore, syntactically, we require that there must be visible separation before and after Chinese symbols. So the above code needs to be changed like this:

#变量1  #变量2

By adding blanks before and after, we can clearly distinguish that this is a Chinese symbol, and at the same time, this can also make our code clearer and more understandable.

To summarize, variable names in Raqet language are required to start with the # symbol and be any consecutive non-blank characters other than language symbols.

3.3 Variable Assignments🔗

In the previous section, we learned about the naming conventions for variables in Raqet language. So, the next step is to use variables to record data.

In fact, in Raqet language, the relationship between variables and data is a binding relationship, similar to the correspondence between each of us and our names or ID numbers. A variable itself is just a name and can be bound to any data. After that, we can find the data bound to this variable name.

In programming languages, we refer to such a binding operation as assignment.

The assignment operation in Raqet language is implemented through the equal sign = and the colon : (only English version is acceptable), as shown in the following code examples:

#变量1 = 123 ; Assign the value on the right of the equal sign to the variable on the left
#变量2: 456 ; The assignment symbol does not need to have spaces before and after, but for the sake of readability, we generally add spaces

In Qing language, both methods have achieved the assignment of variables, though with some different meanings. However, unlike Qing, Raqet is lexically scoped. Referencing an undefined variable will result in a compile-time error rather than defaulting to . The = operator can only assign values to variables, and only : can define (aka declare) variables.

The assignment operation provides a way with Chinese symbols, so the above code is equivalent to:

#变量1  123 ; Note that there must be spaces before and after the Chinese symbol
#变量2 设为 456

Here, is equivalent to =, and 设为 is equivalent to :, but to avoid ambiguity in sentence segmentation, there must be spaces before and after the Chinese symbols, which must be paid attention to.

After completing the assignment operation, the variable is bound to the data, so now we can read the data bound to the variable through the variable name, for example:

#变量1 ; The runtime environment will find the bound value 123
 
#变量1 + #变量2 ; The runtime environment will first find the bound values, and then perform the addition calculation, resulting in 579

3.4 Data Types🔗

In Qing language, variables can be bound to any type of data. Data types available in Qing language are inspired by JavaScript and only a few basic data types are offered.

Here are the basic data types in Qing language:

 ; Represents the absence of a value or an empty value.
 ; Logical type, represents logical true.
 ; Logical type, represents logical false.
0xFF ; Binary type, represents binary data.
123 ; This is an integer.
4.56 ; This is a decimal.
"这是一段字符串" ; The part enclosed in double quotes is a string.

Qing language provides six basic data types, which are sufficient to describe everything.

Note that these two types use three special values in Chinese, and care should be taken to leave spaces before and after them when used.

Strings are essentially text, and when the content of a string needs to represent another string, there can be conflicts. Therefore, we need to handle escape issues with strings. Like other mainstream languages, we use the backslash \ to indicate an escape. The parsing logic is that when a \ is found in a string, it will skip the next character. The first thing we need to handle is the end of the string, which means if a double quote " appears in a string, it will cause the string to end prematurely. In this case, we need to add an escape character, turning it into \", so the parser will skip this quote. Qing language will remove the escape character when storing string information, so the string will only have one quote left.

Other escape characters include:

\

Escapes the escape character, represents a backslash

\n

New line

\r

Carriage return

\t

Tab

\f

Form feed

\b

Backspace

\s

Alert

Additionally: Any type can be converted to a logical value, with the following conversion relationships:

Null — Logical false. Integer, decimal — Greater than 0 is true, less than or equal to 0 is false. String — Empty string is false, otherwise true. Array — Empty array is false, otherwise true.

All others are true.

Raqet maps these types to Racket types. In addition, Raqet also supports any Racket types; for details, see Racket Interoperability.

3.5 Collections🔗

In the previous section, we learned about the basic data types in the Qing/Raqet language, which are used to store individual datum. But sometimes, we need to store a group of data, including a large number of data. For example, 10,000. If we still use the previous way of assigning values to variables for storage, then 10,000 pieces of data would require 10,000 variable names, which is obviously impossible. At this time, we have to consider whether this group of data can be operated as a whole. In programming languages, we call it a collection.

In the Raqet language, we provide two types of collections - arrays and objects

First, let’s look at arrays. Arrays in the Raqet language are represented by a pair of square brackets [ ].

#变量1: [1, 2, 3] ; Here, an array is created, containing 3 data elements
#变量2: [1 2 3] ; The semicolon is used for visual separation and is not mandatory
#变量3: [1, , "String in the array"] ; Any type of data can be placed in the array and consistency is not required
#变量4: [[4, 5, 6], 1 + 2 * 3, #变量2] ; An array can also nest another array, and can also hold the result of an operation or another variable

An array is a group of ordered data. In the Raqet language, an array can hold data of any type and then operate on the data through index.

Next, let’s look at objects. Objects in the Raqet language are represented by an @ and a pair of curly brackets @{ }.

#张三: @{
  #年龄: 12,
  #性别: "男"
}

Inside the object, data elements are not stored through number-based index, but in the form of key-value pairs, which we call attributes and attribute values. From the representation point of view, the attribute definitions inside the object are very similar to variable definitions. The attribute names also start with the # symbol, and the naming rules are consistent with ordinary variables.

3.6 Path🔗

After having the collection type of data, we also need to use the corresponding methods to operate on the collection. In the Raqet language, we operate on the collections through paths. Paths is similar to our daily way of forming words. We operate on the collection by combining the variable names to form the path.

For example, the operation method for arrays is as follows:

#数组: [1, 2, 3]
#数组#1 ; Here, there are two # signs in one continuous syntax element, which is the path

In the above example, there are 3 elements in the array, and then we access the internal data through the path. #数组#1 is a continuous syntax element, but there are two # signs, so it can be divided into two parts #数组 and #1 through the # sign.

The path is resolved from left to right. First, the entire array is found through #数组, and then the element with position 1 is found according to #1.

Note that, like mainstream programming languages, the elements in the array in the Raqet language are numbered from 0!

Since the index starts from 0, #1 here represents the second element. Therefore, the final value of #数组#1 is 2.

During the process of path resolving, the variable will be assigned a value. For example:

#序: 0
#数组#[#序]

Here, #序 in #数组#序 will be assigned a value of 0, so here we access the element number 0 in #数组, and the final value is 1.

The path can also be used in assignment statements, for example:

#数组#[#序] = 4

This sentence will set the element number 0 in #数组 to a new value of 4, so #数组 will become [4, 2, 3].

If you have experience in Qing, you may want to write something like #数组#序. However, Raqet is lexically scoped and would interpret it as an attribute access and fail.

Objects also use paths to operate on internal attributes, for example:

#张三: @{
  #年龄: 15,
  #性别: "男",
}
 
#张三#年龄 ; The value is 15
#张三#性别 ; The value is "男"
 
#张三#年龄 = 16 ; The value of attribute #年龄 is modified to 16

The naming of attributes in objects is the same as variable names, so by combining variable names, a path to access the attributes of the object is formed, and the attributes of the object can also be assigned values through the path.

Here, we simply understand the basic concept and usage of paths. In fact, the usage of paths is very rich. It can achieve multi-level paths, expression resolving within the path, function resolving within the path, and so on through combination. We will study these contents in depth in the section Complex Path.

3.7 Operators🔗

Regarding operators, in fact, we have already seen some of them before, including arithmetic operators such as +, -, *, /, and assignment operators such as = and :. In addition to these, the Raqet language also has other operators, including comparison operators and logical operators.

When performing the four basic arithmetic operations, we all learn that multiplication and division should be calculated before addition and subtraction. Similarly, operators in the Raqet language also have different precedence levels. Next, let’s take a look at the relationship between operators and their precedence levels.

*   / % ; Precedence 1
 
*   *  ; Precedence 2
 
< > == <= >= != <> ; Precedence 3
 
 && || ; Precedence 4
 
 = : *= /= %= += -=  ; Precedence 5

The smaller the precedence number, the higher the precedence. Therefore, when there are consecutive infix operations, multiplication will be performed first, followed by addition, then comparison, then logical operations, and finally assignment operations.

Note that assignment operators are right-associative, and augmented assignment operators (such as +=) have the same precedence as them.

Some of the augmented assignment operators that appear here, such as +=, -=, *=, /=, %=, are introduced to simplify the code. Essentially, they combine arithmetic operations and assignment operations.

#甲 += 1
#甲 = #甲 + 1

The above two lines of code have the same effect. The logic of the compound assignment operator is to first perform the arithmetic operation on the two operands on the left and right, and then assign the result to the variable on the left.

In the Raqet language, we provide the corresponding Chinese representation of the operators, and their precedence is consistent. (Once again, when using Chinese operators, spaces should be added before and after them.)

When faced with long operational expressions, relying on memory to determine the order of operations based on operator precedence can become quite difficult. Is there a method to make the order of operations clearer?

Just like in the four basic arithmetic operations, we can use parentheses to increase precedence. In the Raqet language, we use a pair of parentheses ( ) to indicate priority calculation, for example:

(1 + 2) \* 3

Here, the addition operation, which originally had lower precedence, will be calculated first.

Not only for arithmetic operations, but in the Raqet language, any code placed in parentheses indicates priority calculation.

3.8 Flow Control🔗

By default, statements in the Raqet language are executed line by line from top to bottom, starting from the first line of code until the last line, with each line of code being executed once. We refer to this manner of execution as sequential execution.

However, sometimes we need to selectively execute certain portions of code based on certain or multiple conditions during program execution, without executing other portions of code. In such cases, sequential execution is not capable of fulfilling this requirement.

Additionally, there are instances where we need to execute the same segment of code multiple times. While we can achieve this by copying the code, if the number of repetitions is very high, copying the code becomes impractical.

Therefore, we also need to incorporate conditional statements and loop statements to enable this functionality. Conditional statements allow us to execute code based on specific conditions, while loop statements enable us to execute a block of code repeatedly, which is particularly useful when we need to perform an action multiple times under certain conditions.

3.8.1 Branching🔗

In the Qing language, conditional branching statements are constructed using the keywords 如果, 再则, and 否则, allowing us to selectively execute parts of the code. Let’s first look at a simple example:

如果 2 < 1 {
  #displayln $ 1
}

Here, we are using a statement that hasn’t been learned yet, @displayln $ 1. For now, you just need to know that when this line of code is executed, it will display the number within it.

So, if this line of code were executed, the interpreter would display the number 1.

However, when we enter this line of code into the REPL, we do not see the number 1 displayed, but instead, we get an empty execution result. This indicates that the statement was not executed.

Here, the format of the 如果 statement is:

如果 条件表达式 {
  Code block executed when the condition evaluates to true
}

The code block will only be executed when the conditional expression evaluates to logical true; otherwise, it will not be executed.

Sometimes, we want to execute a different block of code when the conditional expression evaluates to logical false. We can then extend the conditional branching statement to:

如果 条件表达式 {
  Code block executed when the condition evaluates to true
} 否则 {
  Code block executed when the condition evaluates to false
}

For example:

如果 2 < 1 {
        #displayln $ 1
    }否则 {
        #displayln $ 2
    }

In this code, since the condition is false, the code block following the 否则 keyword is executed, which will display the number 2.

When there are multiple conditions to be checked, with multiple branches, we can further extend the 如果 statement:

如果 condition1 {
  code block
} 再则 condition 2 {
  code block
} 否则 {
  code block
}

For example:

#甲: 2
如果 #甲 等于 1 {
  #displayln $ 1
} 再则 #甲 等于 2 {
  #displayln $ 2
} 否则 {
  #displayln $ 3
}

This code will sequentially check the branch conditions. Once a true condition is found, the corresponding code block is executed, and the statement is exited, with no further branch conditions being checked. If none of the conditions are met and there is an 否则 branch, the code block following it is executed.

When there are multiple branching statements, we can continue to add 再则 clauses, turning it into:

如果 condition1 {
  code block
} 再则 condition2 {
  code block
} 再则 condition3 {
  code block
} 再则 condition 4 {
  code block
} 否则 {
  code block
}

再则 clauses can appear 0 or multiple times, while the 否则 clause can appear 0 or 1 time and must be at the end. No matter how many code blocks are present, at most only one code block will be executed, or none at all.

When our conditions are very complex, we can also nest another conditional branching statement inside another, allowing us to handle complex condition checks.

3.8.2 Looping🔗

If we need to execute the same block of code multiple times repeatively, using loop statements can greatly simplify our code. Raqet language provides the following types of loop statements:

3.8.2.1 重复🔗

The format for using the 重复 statement is as follows:

重复 number {
  Executed code block
}

Usage example:

重复 3 {
  #displayln $ "你好"
}

Here, the code block will be executed 3 times.

3.8.2.2 当 1🔗

The statement has two usage methods. Let’s first look at the first method, which is formatted as follows:

 Loop control condition {
  Executed code block
}

Here, we add a loop control condition after the when keyword. As long as this loop condition is true, the code block inside will be executed repeatedly. An example:

#序: 1
 #序 小于等于 10 {
  #displayln $ #序
  #序 加等 1
}

In this example, we first define a loop control variable #序 outside the loop statement and assign it a value of 1. As long as the loop test condition #序 小于等于 10 is true, the code block will be executed. After executing the code block once, the loop test condition is re-evaluated. If it is true, the loop continues; if it is false, the loop stops. In this case, we perform an increment operation on #序 each time the loop executes, so the displayed value also increases by 1 each time, until #序’s value increases to 11, and the loop test condition is false, the loop stops. This program will display the numbers from 1 to 10. The loop statement returns the number of loop iterations, so the final result of the loop statement here is 10, indicating that the loop was executed 10 times.

3.8.2.3 当 2🔗

The second usage method of the statement is as follows:

 Define loop control variable statement, Loop control condition, processing after each loop iteration {
  Executed code block
}

The difference between this method and the first one is that the when keyword is followed by three expressions, and then the code block. In this way, the definition of the loop control variable is moved to the statement, and an operation can be performed on the loop control variable after each loop iteration. The example is as follows:

 #序: 1, #序 小于等于 10,#序 自加 1 {
  #displayln $ #序
}

This code has the same result as the previous example, but we define the variable #序 inside the statement and use the : operator, which will allow us to only access #序 inside the loop code block. This also involves the concept of context. Additionally, we move the operation on the loop control variable to the front of the code block, which ensures that this operation is performed after each loop iteration.

3.8.2.4 执行 ... 直到 ...🔗

Both types of when statements first test the loop control condition before executing the loop code block. If the loop control condition is not initially true, the code block will not be executed at all.

Sometimes we want the code block to execute at least once before testing whether to continue the loop, and we can use the 执行 ... 直到 ... statement for this purpose, which is formatted as follows:

执行 {
  Executed code block
} 直到 Loop test condition

Here, the loop code block is executed first, and the condition after 直到 is tested after each execution. If the condition is false, the loop continues; if it is true, the loop stops. An example is as follows:

#序: 1
执行 {
  #displayln $ #序
  #序 加等 1
} 直到 #序 大于 10

This code has the same result as the previous two examples, but ensures that the code block is executed at least once.

3.8.2.5 跳出 and 继续🔗

Currently 跳出 and 继续 are not supported.

3.9 Iteration🔗

When we need to access the elements of a collection in order, we can use the 遍历 statements in Raqet language.

The format of the traversal statement is:

遍历 collection  iteration variable {
  Executed code block
}

The 遍历 statement can be applied to strings, arrays, and object types, and its behavior differs for different types. Here are some examples:

遍历 "ABC"  #项 {
  #displayln $ #项 ; Here, the individual characters of the string are bound to #项 in order, so it will display A B C in order
}
 
遍历 [1,2,3]  #项 {
  at[]displayln $ #项 ; Here, the elements of the array are bound to #项 in order, so it will display 1 2 3 in order
}
 
#对象: { #甲:1, #乙:2, }
 
遍历 #对象  #项 {
  at[]displayln $ #项 ; Here, the property names and values of the object are bound to #项 in order, in the form of an array [property name string, property value]
}
; Here, it displays ["#甲", 1] and ["#乙", 2], which may be out of order due to the inner structure

3.10 Functions🔗

Functions are another important element in the Raqet language. In Raqet language, we use the @ symbol to mark functions. The naming rules for functions are very similar to variables, except for the different marking symbols. During parsing, it starts from the @ symbol and looks forward until it encounters a Raqet language syntax symbol or a whitespace character.

Functions are a very important concept in programming languages, serving as the basic unit for modularizing program functionality.

When we need to use the same block of logic code multiple times, but it is not just a simple loop, and we want to get corresponding outputs by providing some conditions each time, then we can encapsulate this block of logic code using a function. In the Raqet language, functions can receive 0 to multiple parameters and then return 1 result.

In some future versions, built-in functions will be added to Raqet. For now, only Racket functions (procedures) are available.

If we input a function name into the REPL, we will see the description information for that function, for example:

> #displayln
@原生函数-displayln{...}

The ... within braces does not provide much useful information. Later we will see how to make our own functions informative.

You may have noticed that #displayln starts with # rather than @. This is because #displayln is imported from Racket standard library, and does not follow Raqet naming conventions. Unlike Qing, Raqet does not check whether a value is a function at runtime, so starting a name with # or @ is merely a style issue.

Here we can see that #display is a native function built into the Racket environment. It can receive a parameter of any type and always returns nothing. The effect of executing the function is to format the passed parameter as a string and display it.

The format produced by #displayln differs from that by REPL. Here’s how to solve this.

> #displayln[[1]]
#<gvector 1>
> [1]
[1]
> #displayln[#show[[1]]]
[1]

#show is another native function.

When you need to understand how to use a function, you can check the function’s description information in this way.

3.10.1 Function Invocation🔗

After understanding functions, let’s learn how to call functions to achieve their functionality. If a function name is followed by a pair of brackets [ ], it indicates a function call, and the contents inside [ ] are parameters passed to the function, for example:

#exit[] ; This function does not require parameters and will exit the program immediately
#displayln["你好"] ; This function receives 1 parameter and will display the formatted string of the parameter
#(rename-file-or-directory)["1.txt" "2.txt"] ; This function receives 2 parameters and will rename the file corresponding to parameter 1 to the name specified by parameter 2

#(rename-file-or-directory) is a function from Racket that contains an invalid character for Raqet identifier -. The name is grouped into ( ) to avoid ambiguity.

If the number of parameters passed is more or less than what the function needs, the function will raise an exception.

Since many functions require exactly 1 parameter, the Raqet language provides a syntactic sugar to simplify the invocation of such functions, which is to use a dollar sign followed by the function call and pass the value after it as a parameter, for example:

#displayln $ "你好" ; This is equivalent to #displayln["你好"]

3.11 User-define Functions🔗

In addition to the built-in functions of the Raqet language, we can also encapsulate our own logic code into functions when writing code.

Such functions are called user-defined functions.

The format for user-defined functions is as follows:

@function-name: @[parameter-list] {
  ... function code block ...
}

Where the parameter list defines the parameters that the function can receive, the most basic form of which is a variable name, which we also call the mandatory parameter, for example:

@双倍: @[#甲] {
  #甲 * 2
}

Here we define a function called 双倍 that needs to receive one parameter. When calling the function, the first parameter passed will be bound to the variable #甲 within the function. Therefore, within the code block of the function, we can access the passed parameter (the actual value for parameter passed is also called the argument) through #甲. In this code block, we multiply the value of the parameter #甲 by 2. Since this expression is the last expression in the code block, the result of this expression will be returned as the value of the function. Next, we can try to call our function @双倍 and pass in one parameter, 5.

@双倍 $ 5

We can see the behavior in the REPL:

> @双倍: @[#甲] {
    #甲 * 2
  }
> @双倍
@[#甲 ]{...}
> @双倍 $ 5
10

We can see that the result of the function call is 10, which matches our expectation.

We can also define multiple parameters:

@乘积: @[#甲, #乙] {
  #甲 * #乙
}

Here we define a function @乘积 with two parameters, #甲 and #乙. Let’s try to call it:

@product[2, 3]

This will return 6.

If the number of arguments passed is less or more than the number of parameters we set, the function will raise an exception. For excample:

@显示一个值: @[#甲] {
  #displayln $ 
}
 
@显示一个值[]           ; Raises ``application: required argument not supplied''

Sometimes we want to provide default values for parameters, in which case we can use named parameters. Named parameters are different from mandatory parameters in that they are in the form of an assignment expression, for example:

@乘积: @[#甲, #乙=2] {
  #甲 * #乙
}

Here, we still define a function @乘积, and we define a named parameter #乙=2, which means that when we do not pass an argument for #乙, its default value will be 2. Let’s try calling this function:

@乘积[5]

Here we only pass one argument, 5, which binds to #甲, and no value is given for #乙. At this point, #乙 will be assigned the default value of 2, so the result will be 10.

Sometimes, we do not want to use the default value of the named parameter but want to specify its value, in which case we can call it like this:

@乘积[5, #乙=5]

Here we specify a value of 5 for the named parameter ‘#yi‘, so this time the result will be 25.

If we want our function to display a description, which can help users understand the function’s functionality and usage, how should we implement it? When defining a function, if there is a string in the parameter list, it will be used as the function’s description. The string can appear anywhere in the parameter list, and if there are multiple strings, they will be concatenated into one string. For example:

> @双倍: @[#甲,"参数1-数字,返回数字;返回参数乘以2的值"]{
    #甲 * 2
  }
> @双倍
@[#甲 ]{参数1-数字,返回数字;返回参数乘以2的值}

This way, we can see the function’s description through the function name.

Note that the Raqet language is a dynamically typed language, so we cannot check what type of parameters will be passed when the function is called. Therefore, a better approach is to check the type of the parameters within the function and only process them if the type is correct, for example:

@双倍: @[#甲, "参数1-数字,返回数字;返回参数乘以2的值"] {
  如果  #(number?)[#甲] {
    #甲 * 2
  }否则 {
    抛出 "参数类型错误"
  }
}

3.12 Exception Handling🔗

During the execution of a program, various unexpected situations may occur, and errors that happen during execution can trigger exceptions. Typically, exceptions cause the program to interrupt execution and exit abnormally. Sometimes, the errors that occur in the program are trivial and do not significantly affect the entire program. In such cases, we hope to catch the exceptions that occur, handle them according to the situation of the exception, rather than exiting directly. For this, we need to use the exception handling statements in the Raqet language.

The format of exception handling statements in the Raqet language is as follows:

尝试 {
    ... code block to be attempted ...
} 排查 binding-exception-variable-name {
    ... code block to handle exceptions ...
} 例行 {
    ... routine code block ...
}

Code that may cause an exception can be placed in the code block following the 尝试 keyword. If an exception occurs during execution, it will be bound to the variable name following the 排查 keyword, then the code block to handle exceptions will be executed. If there is a routine code block, it will be executed regardless of whether an exception occurs.

Here is an example:

尝试 {
    1 / 0                  ; Division by zero will trigger an exception
} 排查 #异常 {
    #displayln $ #异常
} 例行 {
    #displayln $ "这是例行语句"
}

Executing this code in the Raqet REPL will yield the following result:

> 尝试 {
    1 / 0
} 排查 #异常 {
    #displayln $ #异常
} 例行 {
    #displayln $ "这是例行语句"
}
 #(struct:exn:fail:contract:divide-by-zero /: division by zero #<continuation-mark-set>)
这是例行语句

The expression 1 / 0 triggers an arithmetic exception, which would normally cause the program to terminate. By placing it in the 尝试 block, we catch this exception. In the 排查 block, we can inspect information about the exception and its location. Finally, the routine code block is executed.

Note that the 排查 and 例行 clauses are not mandatory, but at least one must occur

3.12.1 Intentionally Triggering Exceptions🔗

With the exception handling mechanism in place, in some situations, we can also control the execution of code by intentionally triggering exceptions. This is done using the 抛出 keyword, which throws the value following it as an exception. Example:

抛出 "这是一个主动引发的异常"

3.13 Complex Path🔗

Previously, we have learned the concept of paths, but only used them simply. In fact, the usage of paths can be more flexible and complex. Let’s look at the following example:

#对象: @{
  #数组: [1, 2, 3],
  @方法 : @[#自己 #某]{
    #show $ #某
  }
}
3.13.1 Long Path🔗

First, let’s take a look at paths with more than two elements.

#甲: 1

Suppose we now have such a variable #甲 and hope to use it as an index to access the corresponding element in #对象#数组. Then, just as before, we can continue to combine paths to achieve it:

#对象#数组#[#甲] ; The combined long path, here it will produce 2

Similarly, long paths can also be used for setting values.

When paths are used to assign values to elements in a collection, there is not distinction between = and :. No matter which way is used, the assignment will be made within the found collection.

3.13.2 Expression Evaluation within Paths🔗

Next, let’s increase the complexity. Suppose the result of a calculation based on #甲 is needed as the index to access elements. For example, the index we hope to access should be #甲 * 2. How can this be achieved at this time?

#对象#数组#[#甲 * 2]

Here we find that after the last # used to separate the path, there is a [], which contains the expression about #甲. This way is called expression evaluation within the path. Here, after finding #对象#数组 through the path, the expression within [ #甲 * 2 ] will be calculated first, and the resulting value is 2, and then the element with the index is accessed. So here, the element with the index of 2 is accessed, and the resulting value is 3.

...#[#甲] in the previous section is actually also an application of expression evaluation within path. In Qing you may write #对象#数组#甲, but it is prohibited in Raqet

3.14 Function Calls within Paths🔗

In the previous section, we mentioned that functions (methods) can be defined within objects. Then we can also call the functions within objects through paths. For example, above we defined #对象@方法, which will convert any passed value into a string. For example:

#对象@方法[123] ; Here it will return the converted string "123"

The @方法 method has two parameters, but only received one argument here. This is because the first parameter (#自己) implicitly binds the object containing the method. If you do not want such behavior, try the following tricks:

#对象2: @{
  #函数: @[#甲] { ; Bind the function to a # property
    #show $ #甲
  }
}
#对象2#方法[123] ; So the compiler won't recognize this as a method call
 
(#对象@方法)[123] ; Paths can start with a parenthese-grouped expression
 
@临时: #对象@方法
@临时[123]

Then if we still need to access a certain character in the returned string through the index, the path can also be used. For example:

#对象@方法[123]#1 ; First, it is converted to get the string "123", and then access the character with the index of 1, returning "2"

Note that if the function call within the path is not at the end of the path, it cannot be called using the $ method.

Through the combination of paths, we can achieve operations on complex data structures.

3.15 Modulization🔗

The previous examples provided were all relatively short pieces of code. When we need to implement more complex logic, we have to write longer code and save it to files. Furthermore, if the logic we need to implement is extremely complex and the code spans many lines, such as tens of thousands of lines, then having all this code in the same file is not conducive to management. Therefore, we also need to achieve code modularization from the perspective of files.

3.15.1 Requiring🔗

In Raqet language, each file is a module, and modules are linked statically. Requiring other modules is done by a built-in statement 导入.

Assuming we have a Raqet language source file named “模块1.q”, we can import it as follows:

导入 "模块1.q"

A module may be installed as part of a collection to the Racket environment. In this case, a keyword should be placed between 导入 and collection path:

导入  "net/http-client"
3.15.2 Providing🔗

A Raqet module can be reference by other Raqet / Racket modules. Only provided values / functions can be seen by foreign callers. By default, all symbols defined in a Raqet module are provides. This is equivalent to the following Racket statement:

"(provide (all-defined-out))"

Sometimes you may want to encapsulate some values as private. To achieve this, you need 导出 statements:

#甲: 1
#乙: 2
导出 #甲
导出 #乙

Multiple 导出s can be combined:

#甲: 1
#乙: 2
导出 [#甲 #乙]

Once a 导出 is detected, the compiler won’t implicitly provide all defined symbols.

3.16 Label Type🔗

The label type is an extension of the Raqet language data type. Currently, the Qing language compiler does not support its related operations; it merely records the label data recognized by the parser.

This data type is added to facilitate the extension of Raqet language features. Currently GUI extension has not been implemented yet, but in some future versions we will provide experimental functionality using labels.

Here, we briefly introduce the format of the label type in the Raqet language:

<<label-name #attribute1=value-of-attribute1, #attribute2=value-of-attribute2, [... sub-labels ...]>>

The label type is represented by double less / greater than signs << >>, in analogy to Chinese title marks 《》, with the label name following the left title mark, which can be understood as the type of the label. Then, we use pairs of attribute names and corresponding attribute values to set the properties of the label, and multiple attributes can be set. Finally, within the label, we can add sub-labels in the form of [ ]. Sub-labels also need to conform to the label format.

The label name is required, while the list of attributes and sub-labels can be empty.

3.17 Metaprogramming🔗

In the beginning of the document, we mentioned that the kernel of Raqet language references the Lisp language, which is an ancient yet elegant language. The content we are going to learn next involves a core feature of Lisp - homoiconicity and metaprogramming.

Qing, the origin of Raqet language, does not provide any kind of (compile-time) metaprogramming. Syntax inherited from Qing, Raqet initially offers no metaprogramming either. However, Raqet’s backend platform Racket is a genuine successor to Lisp with excellent metaprogramming support. Racket programs are comprised of S-Expressions, which are a recursive data type that may represent lists or trees. All syntax contructs of Raqet would be eventually compiled to S-Expressions. Besides, Raqet provides a way to directly creating S-Expressions — { }. For example:

{#let {{#x} 1}
  {#(+) #x 1}}

The snippet above is equivalent to the following Racket expression:

"(let ([x 1])""\n""  "
  "(+ x 1))"

Here, the #let is in fact a macro; it is not built into Racket language, but rather a transformer that transforms code to using a primitive — "let-values".

Using S-Expressions, you can define your own macros in Raqet language:

{#(define-syntax-rule) {#when #test #body #(...)}
  如果 #test {
    #body #(...)
  }
}
{#when 
  #displayln $ }

This is just a simple demonstration. More complex macros can be built with the aid of and keywords; for details, see Racket Interoperability