Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update lecture&course of course7 #1

Merged
merged 19 commits into from
Mar 29, 2024
36 changes: 36 additions & 0 deletions course7/course_en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Modern Programming Concepts Overview: Imperative Programming

DawnMagnet marked this conversation as resolved.
Show resolved Hide resolved
## Imperative vs. Functional Programming

- **Imperative**: Focuses on how to change program state through commands and variables.
- **Functional**: Views computation as the evaluation of expressions and avoids changing state.

## Core Concepts

- **Referential Transparency**: Functions and expressions can be replaced with their values without affecting program behavior.
- **Side Effects**: Actions that alter the program's state or external environment, which can include I/O operations.
- **Single-Value Types**: Functions that perform actions without returning a value, often used for side effects.

## Data Handling

- **Variables**: Stores data that can be mutable or immutable.
- **Structs**: Complex data structures with mutable fields, allowing for aliasing and direct manipulation of data.

## Control Structures

- **Loops**: Repeatedly execute code based on a condition, with control over iteration and termination (break and continue).
DawnMagnet marked this conversation as resolved.
Show resolved Hide resolved
- **Recursion**: Functions calling themselves to solve problems, equivalent to certain types of loops.

## Debugging and Checking

- **Debugger**: A tool for real-time runtime data inspection to aid in understanding and correcting code.
- **Moonbit Checks**: Automatic checks for variable modifications and matching function return types to declarations.

DawnMagnet marked this conversation as resolved.
Show resolved Hide resolved
## Mutable Data

- **Usage**: Manipulating external environments, enhancing performance, constructing complex data structures, and space reuse.
- **Considerations**: Careful handling to maintain referential transparency and avoid conflicts.

## Summary

This document introduces imperative and functional programming paradigms, emphasizing the importance of understanding state changes, side effects, and referential transparency. It covers data handling with variables and structs, control structures like loops and recursion, and the role of debugging tools. Mutable data is presented as a versatile but cautious approach to programming.
346 changes: 346 additions & 0 deletions course7/lec7_en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
---
marp: true
math: mathjax
paginate: true
backgroundImage: url('../pics/background_moonbit.png')
headingDivider: 1
---

# Modern Programming Concepts

## Imperative Programming

### Hongbo Zhang

# Functional Programming

- Up to this point, what we have introduced can be categorized under functional programming.
- For each input, there is a fixed output.
- For identifiers, we can directly substitute them with their corresponding values—referential transparency.
- To develop practical programs, we need some "side effects" beyond computation.
- Perform input and output.
- Modify data in memory, etc.
- These side effects can lead to inconsistent results upon multiple executions.

# Referential Transparency

- We can define data bindings and functions as follows:

```moonbit
let x: Int = 1 + 1
fn square(x: Int) -> Int { x * x }
let z: Int = square(x) // 4
```

- We can directly replace `square` and `x` with their corresponding values without changing the result:

```moonbit
let z: Int = { 2 * 2 } // 4
```

- Referential transparency makes understanding easier.

# Commands

- The function `print` allows us to output a string, for example, `print("hello moonbit")`.
- In Moonbit, we can define initialization instructions through an `init` code block.
- This can be simply understood as the main entry point of the program.

```moonbit
fn init {
println("hello moonbit") // The 'ln' in the function name represents a newline.
}
```

![height:300px](../pics/run_init.png)

# Commands and Side Effects

- Output commands may break referential transparency.

```moonbit
fn square(x: Int) -> Int { x * x }
fn init {
let x: Int = {
println("hello moonbit") // <-- We first execute the command, perform output
1 + 1 // <-- Then, we take the last value of the expression block as the value of the block
}
let z: Int = square(x) // 4, output once
}
```

![height:200px](../pics/print_once.png)

# Commands and Side Effects

- We may not be able to safely replace, thus increasing the difficulty of understanding the program.

```moonbit
fn init {
let z: Int = {
println("hello moonbit"); // <-- First output
1 + 1 // <-- Value obtained: 2
} * {
println("hello moonbit"); // <-- Second output
1 + 1 // <-- Value obtained: 2
} // 4, output twice
}
```

![height:200px](../pics/print_twice.png)

# Single-Value Types

- We have previously introduced the single-value type `Unit`.
- It has only one value: `()`.
- Functions or commands that have `Unit` as the result type generally have side effects.
- `fn print(String) -> Unit`
- `fn println(String) -> Unit`
- The type of a command is also a single-value type.

```moonbit
fn do_nothing() { // When the return value is a single-value type, the return type declaration can be omitted
let _x = 0 // The result is of single-value type, which is consistent with the function definition
}
```

# Variables

- In Moonbit, we can define temporary variables within a code block using `let mut`.

```moonbit
let mut x = 1
x = 10 // The assignment operation is a command.
```

- In Moonbit, the fields of a struct are immutable by default, and we also allow mutable fields, which need to be marked with `mut`.

```moonbit
struct Ref[T] { mut val : T }

fn init {
let ref: Ref[Int] = { val: 1 } // ref itself is just a data binding
ref.val = 10 // We can modify the fields of the struct
println(ref.val.to_string()) // Output 10
}
```

# Variables

- We can consider a struct with mutable fields as a reference.

![](../pics/ref.drawio.svg)

# Aliases

- Two identifiers pointing to the same mutable data structure can be considered aliases.

```moonbit
fn alter(a: Ref[Int], b: Ref[Int]) {
a.val = 10
b.val = 20
}

fn init {
let x: Ref[Int] = { val : 1 }
alter(x, x)
println(x.val.to_string()) // The value of x.val will be changed twice.
}
```

# Aliases

- Two identifiers pointing to the same mutable data structure can be considered aliases.

![](../pics/alias.drawio.svg)

- Mutable variables need to be handled carefully.

# Loops

- Using variables, we can define loops.

```moonbit
<Define variables and initial values>
while <Check if the condition for continuing the loop is met>, <Iterate the variable> {
<Commands to be repeated>
}
```

- For example, we can perform n output operations repeatedly.

```moonbit
let mut i = 0
while i < 2, i = i + 1 {
println("Output") // Repeat the output twice.
}
```

# Loops

- When we enter the loop,
- We check if the condition for continuing the loop is met.
- Execute commands.
- Iterate the variable.
- Repeat the above process.
- For example:

```moonbit
let mut i = 0 // <-- At this point, i is equal to 0
while i < 2, i = i + 1 { // <-- Here, we check if i < 2 is true.
println("Output") // <-- 0 < 2, so continue execution, output the first time.
} // <-- At this point, we execute i = i + 2.
```

# Loops

- When we enter the loop,
- We check if the condition for continuing the loop is met.
- Execute commands.
- Iterate the variable.
- Repeat the above process.
- For example:

```moonbit
// At this point, i is equal to 1
while i < 2, i = i + 1 { // <-- Here, we check if i < 2 is true.
println("Output") // <-- 1 < 2, so continue execution, output the second time.
} // <-- At this point, we execute i = i + 2.
```

# Loops

- When we enter the loop,
- We check if the condition for continuing the loop is met.
- Execute commands.
- Iterate the variable.
- Repeat the above process.
- For example:

```moonbit
// At this point, i is equal to 2
while i < 2, i = i + 1 { // <-- Here, we check if i < 2 is true, which is false.
DawnMagnet marked this conversation as resolved.
Show resolved Hide resolved
} // <-- Skip.
// <-- Continue with subsequent execution.
```

# Debugger

- Moonbit's debugger allows us to see real-time runtime data during execution, better understanding the process.
![](../pics/debugger.png)

# Loops and Recursion

- In fact, loops and recursion are equivalent.

```moonbit
let mut <variable> = <initial value>
while <Check if the loop should continue>, <Iterate the variable> {
<Commands to be repeated>
}
```

- With mutable variables, it can be written as:

```moonbit
fn loop_(<parameter>) {
if <Check if the loop should continue> {
<Commands to be repeated>
loop_(<Parameter after iteration>)
} else { () }
}
loop_(<initial value>)
```

# Loops and Recursion

- For example, the following two pieces of code have the same effect.

```moonbit
let mut i = 0
while i < 2, i = i + 1 {
println("Hello!")
}
```

```moonbit
fn loop_(i: Int) {
if i < 2 {
println("Hello!")
loop_(i + 1)
} else { () }
}
loop_(0)
```

# Control of Loop Flow

- During a loop, you can prematurely terminate the loop or skip the execution of subsequent commands.
- The `break` instruction can terminate the loop.
- The `continue` instruction can skip the rest of the execution and go directly to the next loop.

```moonbit
fn print_first_3() {
let mut i = 0
while i < 10, i = i + 1 {
if i == 3 {
break // Skip from 3 onwards.
} else {
println(i.to_string())
}
}
}
```

# Control of Loop Flow

- During a loop, you can prematurely terminate the loop or skip the execution of subsequent commands.
- The `break` instruction can terminate the loop.
- The `continue` instruction can skip the rest of the execution and go directly to the next loop.

```moonbit
fn print_skip_3() {
let mut i = 0
while i < 10, i = i + 1 {
if i == 3 {
continue // Skip 3.
} else { () }
println(i.to_string())
}
}
```

# Moonbit's Checks

- Moonbit checks whether a variable has been modified, which can help avoid infinite loops caused by forgetting to increment a condition.
![height:150px](../pics/infinite_loop.png)
- Moonbit also checks if the function's return result matches the type declaration, which can help avoid incorrect return type declarations.
![height:200px](../pics/forget_type.png)

# Mutable Data

- Widely used in various scenarios.
- Directly manipulate the external environment of the program, such as hardware, etc.
- Sometimes better performance, like random access arrays.
- Can construct some complex data structures, like graphs.
- Reuse space (modify in place).
- Mutable data does not always conflict with referential transparency.

```moonbit
fn fib_mut(n: Int) -> Int { // Always the same output for the same input
let mut acc1 = 0; let mut acc2 = 1; let mut i = 0
while i < n, i = i + 1 {
let t = acc1 + acc2
acc1 = acc2; acc2 = t
}
acc1
}
```

# Summary

This chapter has introduced imperative programming, including:

- How to use commands.
- How to use variables.
- How to use loops, etc.