Saplyn's Notebook

"What is this?"

This is a notebook for me, Saplyn. It acts as a personal knowledge index and cheatsheet. It's primarily intended for my own use. I'm publishing it simply because it makes it easier for me to access it when I'm away from my computer.

However, you may find it useful as well, and that's great! I'm happy to help. Also, if you find any errors or inaccuracies, it's welcome to open an issue or a pull request to correct it.

"What this isn't..."

  • A comprehensive guide, tutorial, or documentation to anything;
  • A complete registry or catalog of all things;
  • An unopinionated or objective source of information;
  • Or anything beyond a personal notebook.

About

This book is created using mdBook.

Understandings

Variable, Function, and Program

  • Variable is the container of data.
  • Function is the container of instructions.
  • Program is the honestest way to instruct computers. It describes how states are manipulated, and how instructions are carried out.

Memory: Stack and Heap

Stack is a segment of memory that is used to store local variables and function call information of one thread. A new stack frame (a contiguous chunk of memory) is created (push) each time a function is called, and destroyed (pop) when returns. Values live on the stack become invalid when its stack frame is destroyed.

Heap is a pool of memory used for dynamic memory allocation. It allows us to allocate a block of memory of a given size and get a pointer to it, the allocated memory stays reserved until it is explicitly deallocated. Values live on the heap are valid until deallocated. The heap is shared among all threads.

C

C, a foundational pillar in the realm of computer programming. Developed in the early 1970s by Dennis Ritchie at Bell Labs, it was designed to provide a powerful and flexible tool for system programming, offering low-level access to memory and hardware while maintaining a high degree of portability.

Features

  • Compiled
  • Statically typed
  • Low-level control
  • Efficient

Sample Program (Guessing Game)

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("Guess the number!\n");

    int secret_number = (rand() % 100) + 1;

    while (1) {
        printf("Please input your guess: ");

        int guess;
        scanf("%d", &guess);

        printf("You guessed: %d\n", guess);

        if (guess < secret_number) {
            printf("Too small!\n");
        } else if (guess > secret_number) {
            printf("Too big!\n");
        } else {
            printf("You win!\n");
            break;
        }
    }

    return 0;
}

Basic Syntax

Program Structure

// optional, when we need external functionality
#include <header>
#include "header.h"

// required, `main()` the entry of every c program.
int main() {
    return 0;
}

Variables & Types

// variable
int age;             // declaration
int score = 90;      // declaration with initialization
float height = 1.75;
char initial = 'J';

// constant
const int MAX = 100;
const double PI = 3.14;

// array
char str[5];                     // declaration
int numbers[] = {1, 2, 3, 4, 5}; // declaration with initialization

// basic types
int, float, double, char, void

// type modifiers
long, short, signed, unsigned

// type conversion
(int) 3.14; // => 3
(float) 3;  // => 3.0

Operators

Note that when doing calculations, compiler will tries to promote the operands to the same type by converting "lower" type to "higher" type.

// arithmetic
+ - * / % ++ --
// `/` is integer division if both operands are integers.
// `%` is the remainder of the division.

// relational
== != > < >= <=

// logical
&& || !
// `&&` and `||` are short-circuit operators.

// bitwise
& | ^ ~ << >>

// assignment
= += -= *= /= %= <<= >>= &= |= ^=
// `x op= y` is equivalent to `x = x op y`

// conditional
?:
// `x ? y : z` is equivalent to `if (x) {y}; else {z};`

Control Flow

Branching

// if-else
if (condition) { /* ... */ }
else if (condition) { /* ... */ }
else { /* ... */ }

// switch-case
switch (enumerable) {
    case variant1: /* ... */ break;
    case variant2: /* ... */ break;
    default: /* ... */
}

Looping

// while loop
while (condition) { /* ... */ }

// do-while loop
do { /* ... */ } while (condition);

// for loop
for (init; condition; update) { /* ... */ }
for (int i = 0; i < 10; i++) { /* ... */ }

// break, continue
break;    // exits the loop
continue; // skips the current iteration.

Jumping

// label
label: // statement

// goto
goto label; // jumps to the label.

Functions

// declaration
int add(int a, int b);

// definition (implementation)
int add(int a, int b) {
    return a + b;
}

// calling
int sum = add(3, 5);

Compound Data Types

Structures

Allowing to group different data types together.

// declaration
struct Point {
    int x;
    int y;
};

// construction
struct Point p1 = {0, 0};

// accessing members
p1.x = 3;
p1.y = 4;

Unions

Allowing storing different data types in the same memory location. With only one variant (member) being accessible at a time.

// declaration
union Data {
    int i;
    float f;
};

// construction
union Data data;

// accessing members
data.i = 10;    // only `i` is accessible now
data.f = 220.5; // only `f` is accessible now
// note that accessing `i` after assigned `f` is undefined behavior.

Enumerations

Allowing to define a type with a set of named values.

// declaration
enum Color { RED, GREEN, BLUE };

// construction
enum Color c = BLUE;

// Using enum values
if (c == RED) { /* ... */ }

Pointer

Basic

Pointers are a special type of variable, it stores the memory address of another variable, instead of the value of the variable itself.

int a = 1;

// `&` is the address-of operator
int *p = &a; // `p` is a pointer to `a`

// `*` is the dereference operator
*p = 2; // `a` is now 2

Pointers support pointer arithmetic, which "shifts" the pointer to another location in memory.

int arr[3] = {1, 2, 3};
int *p = arr; // `p` points to `arr[0]`

p++; // now points to `arr[1]`
p++; // now `arr[2]`

Const

Resources

Since C and C++ are closely related, the resources for C++ are also applicable to C. Here lists only the resources specific to C.

Libraries Memo

Standard Libraries

  • stdio.h: standard input/output
  • stdlib.h: standard library (memory allocation, random number generation, etc.)
  • string.h: string manipulation
  • math.h: mathematical functions
  • time.h: date and time functions
  • ctype.h: character handling functions
  • stdbool.h: boolean type and values

Other Libraries

  • SQLite: A small, fast, self-contained, high-reliability, full-featured, SQL database engine.
  • fmt: A modern formatting library

C++

C++, or CPP, is an extension of the C programming language. Bjarne Stroustrup developed C++ in the early 1980s at Bell Labs, aiming to enhance C's capabilities with features such as object-oriented programming (OOP), generic programming, and improved type safety.

Features

  • Compiled
  • Statically typed
  • Low-level control
  • Efficient
  • Support object-oriented diagram

Sample Program (Guessing Game)

#include <ctime>
#include <iostream>
#include <random>

int main() {
    std::cout << "Guess the number!" << std::endl;

    std::mt19937 rng(time(0));
    int secret_number = (rng() % 100) + 1;

    while (true) {
        std::cout << "Please input your guess: ";

        int guess;
        std::cin >> guess;

        std::cout << "You guessed: " << guess << std::endl;

        if (guess < secret_number) {
            std::cout << "Too small!" << std::endl;
        } else if (guess > secret_number) {
            std::cout << "Too big!" << std::endl;
        } else {
            std::cout << "You win!" << std::endl;
            break;
        }
    }

    return 0;
}

"Plus"-ed Syntax

C++ is a superset of C, and nearly every valid C program is a valid C++ program. For this reason, please refer to the C section for the basic syntax of C++.

Types

// boolean
bool b = true;  // native boolean type, without `cstdbool`

// auto typing (type inference)
auto x = 6;   // x is an int
auto y = 3.14; // y is a double

// decltype typing (type inference)
int i = 114;
decltype(i) j = 514; // j is an int

// using type (type alias)
using Distance = double;
Distance d = 5.0; // d is a double

// nullptr
int* p = nullptr; // p is a pointer to int

// reference
int i = 1919;
int& r = i; // r is a reference to i
r = 810;    // i is now 810, r is just another name for i

Namespaces

namespace lyn {
    int number;  // number is a member of namespace lyn (lyn::number)
}

int main() {
    lyn::number = 32;
}

Functions

// default arguments
void print(int x, int y = 0) {
    std::cout << x << " " << y << std::endl;
}
print(5);     // => 5 0
print(5, 10); // => 5 10

// function overloading
int add(int x, int y) { return x + y; }
double add(double x, double y) { return x + y; }
add(1, 2);     // => 3
add(1.1, 2.2); // => 3.3

// lambda
auto add = [/* captures */](int x, int y) { return x + y; };
add(1, 2); // => 3

Dynamic Memory

// new and delete
int* p = new int; // allocate memory for an int
*p = 1919;
delete p; // release memory

// new[] and delete[]
int* p = new int[5]; // allocate memory for 5 integers
p[0] = 1919;
delete[] p; // release memory

Classes

Class as Object

Like struct in C, class is a user-defined data type that can hold data members. Unlike struct, class can also hold member functions, known as method.

class Point {
// access specifier: only accessible within the class
private:
    double x;
    double y;

// access specifier: accessible outside the class
public:
    void set(double x, double y) {
        // `this` pointer pointes to the caller object
        this->x = x;
        this->y = y;
    }

    // const method promises not to modify the object
    double dist(const Point& that) const {
        return sqrt(
            // `this` is optional
            (x - that.x) * (x - that.x) +
            (y - that.y) * (y - that.y)
        );
    }
};  // don't forget the semicolon

Point p;   // create an object of class Point
p.dist();  // access the member function

Constructor & Destructor

A constructor is a special member function that is called when an object is created, used to initialize the object.

A destructor on the other hand is called when an object is destroyed, used to release allocated resources.

class Vector {
private:
    double* elem;
    int sz;

public:
    // default constructor
    Vector() {
        elem = new double[10];
        sz = s;
    }

    // constructor with parameter
    Vector(int s) : // `:` is the initializer list
        elem{new double[s]},
        sz{s}
    {
        for (int i = 0; i < s; ++i) elem[i] = 0;
    }

    // destructor
    ~Vector() { delete[] elem; }

    // copy constructor
    Vector(const Vector& a) : elem{new double[a.sz]}, sz{a.sz} {
        for (int i = 0; i < sz; ++i) elem[i] = a.elem[i];
    }

    // move constructor
    Vector(Vector&& a) : elem{a.elem}, sz{a.sz} {
        a.elem = nullptr;
        a.sz = 0;
    }
};

class Point {
private:
    double x;
    double y;

public:
    Point() = default;            // default constructor (compiler generated)
    Point(const Point&) = delete; // no copy constructor (prevent copy)
    Point(Point&&) = delete;      // no move constructor (prevent move)
};

Sharing

friend keyword grants access to private members of a class to another class or function.

// forward declaration
class List;
class Node;

class Node {
    friend class List; // List can access private members of Node
private:
    int data;
    Node* next;
};

class List {
private:
    Node* head;
    Node* tail;
};

static keyword makes a member shared by all instances of the class.

class Counter {
private:
    static int count; // shared by all instances of Counter
public:
    Counter() { ++count; }
    ~Counter() { --count; }
    static int getCount() { return count; }
};

int Counter::count = 0; // initialize the static member
Counter a;
Counter::getCount(); // => 1
Counter b, c;
Counter::getCount(); // => 3

Inheritance

// base class
class Animal {
// access specifier: accessible within the class and derived class
protected:
    int age;
public:
    void type() { std::cout << "I'm an animal!" << std::endl; }
    void eat() { std::cout << "yum yum~" << std::endl; }
};

// derived class
class Cat : public Animal {
public:
    // override the base class method
    void type() { std::cout << "I'm a cat!" << std::endl; }
    // implement a new method
    void meow() {
        std::cout << "mew mew~ I'm "
                  << age  // inherited from Animal
                  << " years old!"
                  << std::endl;
    }
};

Cat pet;
pet.eat();  // inherited from Animal
pet.meow(); // defined in Cat

Sometimes multiple classes share a common base class, and they need to access the common base class members. In this case, virtual inheritance is used, to avoid ambiguity (the common base class is inherited only once).

class Animal { /* ... */ };
class Dog : virtual public Animal { /* ... */ };
class Cat : virtual public Animal { /* ... */ };
class SiameseCat : public Dog, public Cat { /* ... */ };

When defining a base class, we can use virtual keyword to make a method overridable by derived classes; override keyword to ensure that the method is indeed overriding a base class method; and final keyword to mark a method as not overridable.

It's also possible to define a pure virtual method, which has no implementation and must be overridden by derived classes.

class Animal {
public:
    // virtual method (with default implementation)
    virtual void info() { std::cout << "I'm an animal!" << std::endl; }
    // pure virtual method (no implementation, must be overridden)
    virtual void eat() = 0;
};

class Feline : public Animal {
public:
    // override the base class method
    void info() override { std::cout << "I'm a feline!" << std::endl; }
    // override the pure virtual method
    void eat() override { std::cout << "I eat meat!" << std::endl; }
};

class Cat : public Feline {
public:
    // make final overrides
    void info() override final { std::cout << "I'm a cat!" << std::endl; }
    void eat() override final { std::cout << "I eat fish!" << std::endl; }
};

Polymorphism

Polymorphism is the ability to treat objects of different derived classes through a common interface (base class).

// dynamic dispatch (runtime polymorphism)
Cat saplyn;
Animal* ani_ptr = &saplyn;
Animal& ani_ref = saplyn;

ani_ptr->type(); // calls Cat::type()
ani_ref.type();  // calls Cat::type()

// however, the following will result in slicing
Animal ani = saplyn; // only the base part of saplyn is copied
ani.type();          // calls Animal::type()

Operator Overloading

The operators that can be overloaded are listed below:

  • Binary Operators: +, -, *, /, %, ^, &, |, =, <<, >>, ,, ->*, ->, []
  • Comparison Operators: <, >, <=, >=, ==, !=
  • Short-Circuit Operators: &&, ||
  • Unary Operators: ~, !
  • Pre/Post Unary Operators: ++, --
  • Function Call Operator: ()

Note that . (dot operator) and ? : (ternary operator) cannot be overloaded. Additionally, the behavior of some operators (like the short-circuit behavior of logical operators (&& and ||)) cannot be changed.

class Point {
private:
    double x;
    double y;
public:
    Point() = default;
    Point(double x = 0, double y = 0) : x(x), y(y) {}

    double get_x() const { return x; }
    double get_y() const { return y; }
};

// overload the `+` operator
Point operator+(const Point& a, const Point& b) {
    return Point{a.get_x() + b.get_x(), a.get_y() + b.get_y()};
}

// overload the `<<` operator
std::ostream& operator<<(std::ostream& os, const Point& p) {
    return os << '(' << p.get_x() << ", " << p.get_y() << ')';
}

In C++, some operators have multiple forms (pre/post-incr, etc.), they are overloaded with different signatures.

// pre-increment (++a)
Type& operator++();
// post-increment (a++)
Type operator++(int);

// l-value []
Type& operator[](int i);
// r-value []
Type operator[](int i) const;

Template

Generics

In C++, templates are a way to make functions and classes generic, so that they can handle any data type. This happens at compile time (static dispatch).

// `T` is a type parameter, it can be any type
template <typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}
int a = 1, b = 2;
swap<int>(a, b);
double c = 1.1, d = 2.2;
swap<double>(c, d);
// multiple type parameters also possible
template <typename T, typename U>
class Pair { /* ... */ };
Pair<int, double> p;
Pair<int, int> q;

It's possible to create specialized versions of a template for specific types.

// generic version
template <typename T>
void print(const T& value) { /* ... */ }

// specialized version for `std::string`
template <>
void print<std::string>(const std::string& value) { /* ... */ }

Variadic Templates

Variadic templates allow a function or class to accept any number of arguments of any type.

// base case
void print() { std::cout << std::endl; }
// recursive case
template <typename T, typename... Args>
void print(const T& first_arg, const Args&... args) {
    std::cout << first_arg << ' ';
    print(args...);
}
print(1, 2.2, "three");
// `sizeof...(args)` gives the number of arguments
template <typename... Args>
void count_args(const Args&... args) {
    std::cout << sizeof...(args) << std::endl;
}
count_args(1, 2.2, "three"); // => 3

Meta-programming

constexpr is a keyword used to declare a constant expression. It indicates that a variable or function can be evaluated at compile-time and its value can be used in other compile-time expressions.

// constexpr variables
constexpr int length = 10;
constexpr int width = 4 + 6;

// constexpr functions
constexpr int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

We can use meta-programming techniques to perform calculations and generate compile-time constants or data structures to optimize code or providing compile-time configuration options.

// compile-time Fibonacci sequence
template <int N>
struct Fibonacci {
    static const int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};
template <> struct Fibonacci<0> { static const int value = 0; };
template <> struct Fibonacci<1> { static const int value = 1; };

// usage
int fib = Fibonacci<10>::value; // => 55

Exception

Exception Handling

When executing a program, error occurs. It's important to handle the error properly. In C++, exception is the idiomatic way to handle error.

// `try` tries to execute the block of code
// and hands over to `catch` if an exception is thrown
try {
    int numerator = 10;
    int denominator = 0;
    int result = numerator / denominator;  // Potential division by zero

    // This line won't be executed if an exception is thrown above
    std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
    // handle the exception
    std::cout << "An exception occurred: " << e.what() << std::endl;
}
// `catch` can be chained to handle specific exceptions
try {
    std::string s = "hello";
    s[10] = 'x';  // Accessing an invalid index throws an out_of_range exception
} catch (const std::out_of_range& e) {
    std::cout << "Out of range exception: " << e.what() << std::endl;
} catch (const std::exception& e) {
    std::cout << "An exception occurred: " << e.what() << std::endl;
} catch (...) {
    std::cout << "An unknown exception occurred." << std::endl;
}
// `throw` can be used to throw an exception manually
void func() {
    // when exception is thrown, the control is transferred to the nearest `catch` block
    throw std::runtime_error("An error occurred in func()");
    // unreachable code
    std::cout << "This line won't be executed" << std::endl;
}
try {
    func();
} catch (const std::exception& e) {
    std::cout << "An exception occurred: " << e.what() << std::endl;
}

noexcept Guarantee

The noexcept keyword is used to declare that a function does not throw any exceptions. If a function marked with noexcept does throw an exception, the std::terminate function is called, terminating the program.

// won't fail guarantee
void func() noexcept { /* ... */ }

// specialized won't fail
template <> void func<int>() noexcept { /* ... */ }
template <> void func() noexcept(sizeof(T) == 4) { /* ... */ }

Custom Exception

We can define custom exception by inheriting from std::exception.

#include <stdexcept>

class MyException : public exception {
private:
    std::string m_message;
public:
    MyException(const std::string& msg) : m_message(msg) {}
    
    std::string what() override { return m_message; }
};

try {
    throw MyException("An error occurred in func()");
} catch (const std::exception& e) {
    std::cout << "An exception occurred: " << e.what() << std::endl;
}
// specialized exception
#include <stdexcept>

class RunOutOfMemory : public std::runtime_error {
public:
    RunOutOfMemory(const std::string& msg) : std::runtime_error(msg) {}
};

Resources

Tools

  • CMake: A cross-platform build system.
  • vcpkg: A C++ library manager

Compilers

Libraries Memo

Standard Libraries

  • iostream: standard input/output stream
  • string: string manipulation
  • chrono: date and time functions
  • vector: dynamic array
  • map: associative array

Other Libraries

  • SQLiteCpp (website): A smart and easy to use C++ SQLite3 wrapper
  • fmt: A modern formatting library
  • spdlog: Fast C++ logging library
  • Dear ImGui: Bloat-free Immediate Mode Graphical User interface for C++ with minimal dependencies

Rust

Rust is a systems programming language that is performant, reliable, and productive. It is a multi-paradigm, general-purpose language that is safe and concurrent by design. Created by Graydon Hoare as a personal project in 2006 and later sponsored by Mozilla, it made its first stable release in 2015.

Features

  • Compiled
  • Statically typed
  • Low-level control
  • Efficient
  • Memory safe
  • Expressive

Sample Program (Guessing Game)

// https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
use rand::Rng; // external crate
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {guess}");

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

Basic Syntax

Program Structure

// optional, when we need external functionality
// notably, rust has a prelude that is used by default.
use std::io;

// required, `main()` the entry of every rust program.
fn main() {
    println!("Hello, world!");
}

Variables & Types

// variable
// most of the time, rustc infers the type.
let score = 90;        // immutable variable
let first_name: char;  // type annotation, optional
let mut height = 1.75; // mutable variable

// constant (computable at compile time)
const MAX: i32 = 100;  // requires type annotation
const PI: f64 = 3.14;  // requires initialization too

// array
let numbers = [1, 2, 3, 4, 5];
let mut str: [char; 5];

// tuple
let tup = (500, 6.4, 1);
let tuple: (i32, f64, char) = (42, 6.28, 'J');

// shadowing
let x = 5;     // immutable (5)
let x = x + 1; // shadowing, new variable (6)
let x = x * 2; // shadowing, another new variable (12)

// basic types
i8, i16, i32, i64, i128
u8, u16, u32, u64, u128
isize, usize // architecture dependent
f32, f64
char // unicode scalar value
bool

// type conversion
let x: i32 = 42;
let y: f64 = x as f64;
let z: i32 = y as i32;

Operators

// arithmetic
+ - * / %
// `/` is integer division if both operands are integers.
// `%` is the remainder of the division.

// relational
== != > < >= <=

// logical
&& || !
// `&&` and `||` are short-circuit operators.

// bitwise
& | ^ ! << >>

// assignment
= += -= *= /= %= <<= >>= &= |= ^=

Control Flow

Branching

Branching in Rust is more powerful than just this, for details, see the Pattern Matching chapter.

// if-else expression (can "return" values)
if condition { /* ... */ }
else if condition { /* ... */ }
else { /* ... */ }

// match expression (can "return" values)
match value {
    pattern1 => /* ... */,
    pattern2 => /* ... */,
    _ => /* ... */,  // catch-all (wildcard)
}

Looping

// loop expression (while true)
loop { /* ... */ }

// while loop
while condition { /* ... */ }

// for loop
for element in iterable { /* ... */ }

// range
1..=5 // 1, 2, 3, 4, 5
1..5  // 1, 2, 3, 4

// break and continue
loop {
    if condition { break; }
    if condition { continue; }
}

// the `loop` can be labeled and `break` to the label.
'outer: loop {
    'inner: loop {
        break 'outer;
    }
}

// it can also `break` with a value
let result = loop {
    if condition { break 1; }
    else { break 2; }
};

Functions

This is only a brief introduction to functions and closures. For details, see the Function chapter.

// function
// fn name(param: type) -> return_type { /* ... */ }
fn add(a: i32, b: i32) -> i32 {
    a + b // last expression implicitly returned
}
let sum = add(3, 5);

// closure (anonymous function)
let closure = |a: i32, b: i32| -> i32 { a + b }; // functions are values.
let add = |a, b| a + b;  // type is inferred.
let sum = add(3, 5);

Structs

struct Point {
    // fields are private by default
    x: i32,
    // `pub` makes the field public
    pub y: i32,
}

// methods
impl Point {
    // associated function, also constructor
    fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }
    // captures `self` by immutable reference
    fn get_x(&self) -> i32 {
        self.x
    }
    // captures `self` by mutable reference
    fn set_x(&mut self, x: i32) {
        self.x = x;
    }
}

// method implementation can be separated
impl Point {
    // consumes `self`
    fn move_to(self, x: i32, y: i32) -> Point {
        Point { x, y }
    }
}

// construction
let p0 = Point::new(0, 0);
let x = p.get_x();
let p1 = Point {
    x, // field init shorthand
    y: 4
};
let p2 = Point { y: 4, ..p1 }; // struct update syntax
// tuple struct
struct Color(i32, i32, i32);
let black = Color(0, 0, 0); // construction
let r = black.0; // accessing fields by index
let g = black.1;
let b = black.2;

// unit-like struct (no fields, marker)
struct PlayerMarker;

Enums

// marker enum
enum IpAddrKind {
    V4,
    V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

// with data
enum AppError {
    NoContext,
    IoError(io::Error),
    InvalidInput {
        encountered: String,
        expected: String
    },
}
let unknown = AppError::NoContext;
let io_err = AppError::IoError(/* ... */);
let invalid = AppError::InvalidInput {
    encountered: "sapling".to_string(),
    expected: "saplyn".to_string()
};

// methods (like structs)
impl AppError {
    fn is_io_error(&self) -> bool {
        match self {
            AppError::IoError(_) => true,
            _ => false,
        }
    }
}

Fundamentals

Macros

Macros in rust are "code that writes code". For simplicity, they can be thought of as:

  • a function call, like: println!()
  • an attribute to add metadata to some item, like: #[derive(Debug)]
  • a way to define new syntax, like: vec![]

Macros are powerful, for in-depth understanding and usage, see the Macros chapter. Here lists some commonly used macros.

// function-like macro
println!("Hello, world!");             // print to stdout
eprintln!("An error occurred: {}", e); // print to stderr
dbg!(Object);                          // debug print

// attribute-like macro
#[derive(Debug)] // derive `Debug` trait for the struct
struct Object;

Common Types

Rust has a powerful ans expressive type system. Here are some commonly used non-primitive types.

String       // growable UTF-8 encoded text
Vec<T>       // growable array
Option<T>    // optional value. Rust's version of `null` or something
Result<T, E> // failable value
Box<T>       // heap-allocated value
&str         // string slice
&[T]         // array slice

Ownership & Borrowing

  • Ownership
    • Every value in Rust has a variable that's its owner.
    • When the owner goes out of scope, the value gets dropped.
    • When copying a value, the ownership is transferred. (if type not Copy)
  • Borrowing
    • A reference to a value, without taking ownership.
    • At any given time, there can be either one mutable reference or any number of immutable references.
    • References must always be valid.

Error Handling

In Rust, there're two ways to handle errors: panic!() and Result<T, E>. panic!() terminates the program, indicating a unrecoverable error; while Result<T, E> is used to handle recoverable errors. For when to panic!(), see The Book.

enum AppError {
    SaplynIsNotCat,
}

// failable function, returns `Result<T, E>`
fn failable() -> Result<String, AppError> {
    if saplyn.is_cat() {
        Ok(String::from("I'm a cat!"))
    } else {
        Err(AppError::SaplynIsNotCat)
    }
}

fn main() {
    let result = failable(); // call failable function
    match result {
        // is value? compute value
        Ok(val) => println!("Got value: {}", val),
        // is error? handle error
        Err(err) => {
            // unrecoverable error, panic!
            panic!("What?! I should be a cat!")
        },
    }
}

Sometimes, we are not handling the error right away, but passing it to the caller. In this case, we use ? to propagate the error.

// https://lib.rs/crates/anyhow

fn compute() -> anyhow::Result<i32> { Ok(114) }

fn action() -> anyhow::Result<i32> {
    let val = compute()?;  // propagate error or extracting value
    Ok(val * 1000 + 514)
}

fn long_chain() -> anyhow::Result<()> {
    let a = action()?;
    let b = action()?;
    let c = action()?;
    Ok(a + b + c)
}

Generic, Trait & Lifetime

Generic

Generic in Rust is implemented as type parameters. It allows us to define functions, structs, enums, and methods that work with arbitrary types. Generic happens at compile time.

// generic struct
struct Point<T> {
    x: T,
    y: T,
}

// generic method
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}
// generic enum
enum Either<L, R> {
    Left(L),
    Right(R),
}

// generic function
fn swap<T>(pair: (T, T)) -> (T, T) {
    (pair.1, pair.0)
}

Trait

A trait defines functionality a particular type has and can share with other types.

// define a trait
trait Animal {
    // method blueprint
    fn eat(&mut self);
    // method with default implementation
    fn info(&self) -> String {
        String::from("I'm an animal")
    }
}

// implement a trait
struct Cat;
impl Animal for Cat {
    // implement method
    fn eat(&mut self) {
        println!("I'm eating fish");
    }
    // override default implementation
    fn info(&self) -> String {
        String::from("I'm a cat")
    }
}

When combined with generic, we can use trait bounds to specify that a type parameter has specific behaviors.

// generic function with trait bound
use std::fmt::Display;
fn print<T: Display>(val: T) {  // `T` implement `Display` trait (`T` is displayable)
    println!("{}", val);
}

use std::fmt::Debug;
use std::clone::Clone;
fn clone_and_print<T>(val: T)
where
    T: Debug + Clone // `T` implement `Debug` and `Clone` trait
{
    let cloned = val.clone();
    println!("{:?}", cloned);
}

They are similar two ways to achieve polymorphism in Rust: impl Trait which happens at compile time, and dyn Trait which happens at runtime.

// `impl Trait` (static dispatch)
fn print_info(animal: impl Animal) {
    println!("{}", animal.info());
}

// `&dyn Trait` (dynamic dispatch)
fn print_info_dyn(animal: &dyn Animal) {
    println!("{}", animal.info());
}

// `Box<dyn Trait>` (dynamic dispatch)
fn print_info_box(animal: Box<dyn Animal>) {
    println!("{}", animal.info());
}

Traits can also be generic, they may also have associated types.

// generic trait
trait Into<T> {
    // type associated with the trait
    type Output = T;
    // method blueprint
    fn Into(self) -> Self::Output;
}

// implement generic trait
struct Cat;
impl Into<String> for Cat {
    fn Into(self) -> String {
        String::from("I'm a cat!")
    }
}

Lifetime

Lifetimes are another kind of generic, but for references. Rather than ensuring that a type has the behavior we want, lifetimes ensure that references are valid as long as we need them to be. For in-depth understanding, see Understanding Lifetimes

Note that lifetime annotations do not define the lifetime of references, but bridge the relationship between them. The actual lifetime is determined by the value and reference flow.

// lifetime annotation
&str         // a reference
&'a str      // a reference with an explicit lifetime
&'a mut str  // a mutable reference with an explicit lifetime
&'static str // a reference with the 'static lifetime (the entire program)

// function with lifetime
// `x` and `y` have the same lifetime `'a`, so does the return value
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Type State Pattern

Type state pattern is a design pattern that allows a type to change its behavior based on its associated state.

use std::marker::PhantomData;

// state type
struct Locked;
struct Unlocked;

// stateful type (state default to `Locked`)
struct Safe<State = Locked> {
    treasure: String,
    _state: PhantomData<State>, // state marker to satisfy the type system
}

// `Locked` specialized impl
impl Safe<Locked> {
    pub fn unlock(self, _pswd: String) -> Safe<Unlocked> {
        Safe {
            treasure: self.treasure,
            _state: PhantomData,
        }
    }
}

// `Locked` specialized impl (state elided)
impl Safe {
    pub fn new() -> Self {
        Self {
            treasure: "treasure".to_string(),
            _state: PhantomData,
        }
    }
}

// `Unlocked` specialized impl
impl Safe<Unlocked> {
    pub fn lock(self) -> Safe<Locked> {
        Safe {
            treasure: self.treasure,
            _state: PhantomData,
        }
    }
    pub fn treasure(&self) -> String {
        self.treasure.clone()
    }
}

// state independent impl (shared)
impl<State> Safe<State> {
    pub fn version(&self) {}
    pub fn encryption(&self) {}
}

fn main() {
    let safe = Safe::new();
    safe.version();
    safe.encryption();
    // no method named `treasure` found for struct `Safe` in the current scope
    // safe.treasure();

    let safe = safe.unlock("secret".to_string());
    safe.version();
    safe.encryption();
    safe.treasure();

    let safe = safe.lock();
}

Pattern Matching

Destructuring

// Destructuring a tuple
let (x, y, z) = (1, 2, 3);
let (first, .., last) = (2, 4, 8, 16, 32);
for (index, value) in v.iter().enumerate() {
    println!("{} is at index {}", value, index);
}

// Destructuring a struct
let Point { x, y } = Point::new(1, 2);   // extract x and y
let Vec3 { z, .. } = Vec3::new(4, 5, 6); // extract z, ignore others

// Destructuring an enum
let Some(x) = compute();

match Expression

// matching a enum
match compute() {
    Some(x) => println!("got a number: {}", x),
    None => println!("got nothing"),
}

// various match patterns
match x {
    1 => println!("one"),
    2 => println!("two"),
    3 | 4 => println!("three or four"),
    5..=10 => println!("five through ten"),
    _ => {
        print!("it ");
        print!("can ");
        print!("be ");
        println!("anything!");
    },
}

// match guards
match x {
    Some(0) => println!("zero"),
    Some(n) if n < 0 => println!("negative"),
    Some(n) if n > 0 => println!("positive"),
    _ => println!("nothing"),
}

// @ bindings
match x {
    Some(n @ 0) => println!("zero: {}", n),
    Some(n) => println!("non-zero: {}", n),
    None => println!("nothing"),
}

// @ bindings with renaming
let msg = Message::Hello { id: 5 };
match msg {
    Message::Hello {
        id: id_variable @ 3..=7,
    } => println!("Found an id in range: {}", id_variable),
    Message::Hello { id: 10..=12 } => {
        println!("Found an id in another range")
    }
    Message::Hello { id } => println!("Found some other id: {}", id),
}

if let and let else

if let Some(x) = compute() {
    println!("got a number: {}", x);
} else {
    println!("got nothing");
}

let x = Some(5) else {
    println!("got nothing");
};

while let Loop

let mut stack = Vec::new();

stack.push(1);
stack.push(2);
stack.push(3);

while let Some(top) = stack.pop() {
    println!("{}", top);
}

Function

Function Item

Function items are zero-sized. Every none generic function has a unique, unnameable function item type.

fn callable() {}
let fn_item = callable; // function item: fn() {callable}

fn generic<T>() {}
let mut fn_i32 = generic::<i32>; // fn() {generic::<i32>}
// fn_i32 = generic::<u32>; // ERROR: mismatched types, fn() {generic::<u32>}

// zero-sized
println!("{}", std::mem::size_of(fn_item))
println!("{}", std::mem::size_of(fn_i32))

Function Pointer: fn

Function pointer are pointers that points to a function, referring to a function whose identity is not known at compile time.

// a function that takes two `i32` and returns one `i32`
type BinOp = fn(i32, i32) -> i32;

fn add(a: i32, b: i32) -> i32 { a + b }
fn minus(a: i32, b: i32) -> i32 { a - b }

// coerce: from "fn item" to "fn pointer"
let mut bo: BinOp = add;
bo = minus;

Closure

Closure is a function that is able to captures its environment.

// none-capturing closure
let add = |a: i32, b: i32| a + b;

// capturing closure (immutable)
let x = 10;
let read_x = |a: i32| a + x;

// capturing closure (mutable)
let mut x = 10;
let mut_x = |a: i32| { x += a; };

// capturing closure (move)
let x = 10;
let drop_x = || { drop (x); };

At compiler implementation level, closures are divided into two types:

  • None-capturing: They are like unnamed function, aka. instructions written in the code.
  • Capturing: They are like a struct that contains the captured variables, which also implements the corresponding function traits.
// consider a capturing closure like this
let x = 10;
let read_x = |a: i32| a + x;
read_x(5);

// it would be interpreted as something like this
let x = 10;
struct ReadX {
    x: i32,
}
impl Fn(i32) -> i32 for ReadX {
    fn call(&self, a: i32) -> i32 {
        a + self.x
    }
}
impl FnMut(i32) -> i32 for ReadX { /* ... */ }
impl FnOnce(i32) -> i32 for ReadX { /* ... */ }
let read_x = ReadX { x };
read_x.call(5);

Function Traits: Fn, FnMut & FnOnce

Fn, FnMut and FnOnce are traits that are implemented on function types.

  • Fn: The function can be called immutably, modifying nothing.
  • FnMut: The function can be called mutably, modifying captured data.
  • FnOnce: The function can be called only once, moving the captured value.
fn add(a: i32, b: i32) -> i32 { a + b }
fn call_it<F: Fn(i32, i32) -> i32>(f: F, a: i32, b: i32) -> i32 {
    f(a, b)
}

call_it(add, 1, 2);

Note that Fn is a super trait of FnMut, and FnMut is a super trait of FnOnce, because every "shared" function can be called "exclusively", and every "exclusive" function can be called "at least once".

fn add(a: i32, b: i32) -> i32 { a + b }
fn call_once<F: FnOnce(i32, i32) -> i32>(f: F, a: i32, b: i32) -> i32 {
    f(a, b)
}
fn call_mut<F: FnMut(i32, i32) -> i32>(f: F, a: i32, b: i32) -> i32 {
    f(a, b)
}

call_once(add, 1, 2);
call_mut(add, 1, 2);

Macros

This page is INCOMPLETE

This page is incomplete and is still being written.

Introduction

Macros are a way to write code that writes other code. The term macro refers to a family of features in Rust: declarative macros with macro_rules! and three kinds of procedural macros:

  • Custom #[derive] macros that specify code added with the derive attribute used on structs and enums
  • Attribute-like macros that define custom attributes usable on any item
  • Function-like macros that look like function calls but operate on the tokens specified as their argument

Declarative Macros

https://veykril.github.io/tlborm/decl-macros.html

Custom #[derive] Macros

https://github.com/dtolnay/proc-macro-workshop

Attribute-like Macros

https://github.com/dtolnay/proc-macro-workshop

Function-like Macros

https://github.com/dtolnay/proc-macro-workshop

Async

This page is INCOMPLETE

This page is incomplete and is still being written.

Future Trait

Pin & Unpin

Concepts

Understanding Lifetimes

When a reference with a lifetime is used, the borrow checker tracks the flow of where it starts to where it's used, and checks along the way to ensure that no conflict happens.

Lifetime often coincide with a scope, but they don't have to, the following code demonstrates this: The lifetime 'a is alive up until the end of the code block, however the if branch needs a &mut x to mutate x. This code compiles because &'a x and &mut x don't cause conflict, as they are in different flows.

let mut x = 114;        // #      
let r = &x;             // ├───── &'a x
if condition {          // ├──┐
    x = 514;            // │  └── &mut x
} else {                // │
    println!("{}", r);  // └───── &'a x
}

Variance: Co/In/Contra-variant

Variance is a concept in type theory, it describes what types are subtypes of other types, and when a subtype can be used in place of a supertype (and vice versa). Specifically, if 'b: 'a ('b outlives 'a), then 'b is a subtype of 'a. There are three kinds of variance: covariant, invariant, and contravariant. For detailed in-depth reference, see The Rust Reference and The Rustonomicon.

A type is covariant if we can use a subtype in place of the type.

// covariant in 'short
fn covariant<'short>(s: &'short str);
let valid: &'static str;
let valid: &'longer str;  // 'longer: 'short  ('longer outlives 'short)

// covariant in T
fn covariant<'short>(v: &Vec<&'short str>);
let valid: &Vec<&'longer str>;  // 'longer: 'short

/*
'short              ----
'longer     ----------------
'static --------------------------------
*/

A type is invariant if we must provide exactly the same type.

fn invariant<'exact>(
    v: &mut Vec<&'exact str>
    str: &'exact str
) {
    v.push(str);
}

// will otherwise allow short-lived str be seen as 'static
let invalid: &mut Vec<&'static str>;

A type is contravariant if we can use a supertype in place of the type.

fn contravariant(s: &'static str);        // strict, accepts fewer types
fn contravariant<'short>(s: &'short str); // flexible, accepts more types

let longer: &'static str; // more useful, lives longer
let short: &'short str;   // less useful, lives shorter

Resources

Official Toolchain

  • rustup: Rust toolchain installer.
  • cargo: Rust's build system and package manager.
  • clippy: A collection of lints to catch common mistakes and improve Rust code.
  • rustfmt: A tool for formatting Rust code according to style guidelines.
  • miri (nightly): An interpreter for Rust's mid-level intermediate representation.

Official Books

IDE Support

  • rust-analyzer: A modular compiler frontend for the Rust language, an LSP.
  • CodeLLDB: A native debugger extension for VSCode based on LLDB.
  • RustRover (paid): JetBrains' dedicated Rust IDE.

Cargo Plugins

  • cargo-audit: Audit Cargo.lock for crates with security vulnerabilities.
  • cargo-expand: Shows the result of macro expansion and #[derive] expansion.
  • cargo-generate: Bootstrap a new project by leveraging a pre-existing git repository as a template.
  • cargo-nextest: A next-generation testing tool for Rust.
  • cargo-update: A cargo subcommand for checking and applying updates to installed executables.
  • cargo-watch: Watches over your Cargo project’s source and runs cargo build when files change.

Books, Articles & Blog Posts

Crates Memo

  • crates.io: The Rust community’s crate registry.
  • docs.rs: The Rust community’s documentation hosting service.
  • lib.rs: A community maintained catalog of Rust crates and programs.
  • Rust Standard Library: The Rust Standard Library API documentation.

Quality of Life

  • thiserror: #[derive(Error)] for defining custom error types.
  • anyhow: Flexible concrete Error type built on std::error::Error.
  • simplelog: Simple and easy-to-use logging facility.
  • fake: An easy to use library for generating fake data.

Ex-Std

  • rand: Random number generators and other randomness functionality.
  • chrono: Date and time library.
  • time: Date and time library.
  • log: A lightweight logging facade.

Proc Macros

  • syn: Parser for Rust source code.
  • quote: Quasi-quoting macro quote!{}.
  • proc-macro2: A substitute implementation of the compiler’s proc_macro API.
  • trybuild: Test harness for ui tests of compiler diagnostics

Async

  • tokio (website): An asynchronous runtime for the Rust programming language.
  • smol: A small and fast async runtime.
  • futures: Foundations and utilities for asynchronous programming.
  • futures-lite: A lightweight async prelude.

Encoding

Rocket

Shell Script

Shell script is a programming language that is used to automate tasks, such as running commands, moving files, and so on. It is a scripting language that is interpreted by the shell, such as bash, zsh and sh.

Features

  • Interpreted
  • Dynamically-typed
  • Scripting
  • Automation

Sample Program (Hello World)

#!/bin/bash
echo "Hello, World!"

Basic Syntax

Script Structure

#!/bin/bash
# above is shebang, the path to the interpreter

# commands to execute
echo "Hello, World!"

Variables & Arguments

# declare a variable
name="Saplyn"

# access the variable
echo "I'm $name!"

$? # exit status of the last command
$$ # process ID of the current script
$! # process ID of the last background command

$0        # name of the script
$1 ... $9 # arguments passed to the script
$#        # number of arguments passed to the script
$*        # all arguments passed to the script

${#name} # length of the variable
${#1}    # length of the first argument

Command & Substitution

# execute a command
echo "Hello, World!"

# command substitution
current_date=$(date)
echo "Today is $current_date. I'm $(whoami)"

Control Flow

Branching

# if-else
if true; then
    echo "It's true"
else
    echo "It's false"
fi

# case
case $1 in
    start)
        echo "Starting..."
        ;;
    stop)
        echo "Stopping..."
        ;;
    *)
        echo "Unknown command"
        ;;
esac

# number comparison
[$x -eq $y] # equal
[$x -ne $y] # not equal
[$x -gt $y] # greater than
[$x -lt $y] # less than
[$x -ge $y] # greater than or equal
[$x -le $y] # less than or equal

# string comparison
[$s =  $t] # equal
[$s != $t] # not equal
[$s <  $t] # less than
[$s >  $t] # greater than
[-z $s]    # empty
[-n $s]    # not empty

# file assertion
[-e $f] # exists
[-f $f] # is regular file
[-d $f] # is directory
[-r $f] # readable
[-w $f] # writable
[-x $f] # executable
[-s $f] # not empty
[-u $f] # SUID is set

# logical operators
[$cond -a $cond]     # and
[$cond -o $cond]     # or
! [$cond]            # not
[$cond1] && [$cond2] # short-circuit and
[$cond1] || [$cond2] # short-circuit or

Looping

# for-in
for i in 1 2 3; do
    echo $i
done
for i in {1..3}; do
    echo $i
done

# c-style for
for ((i=0; i<3; i++)); do
    echo $i
done

# while
i=0
while [ $i -le 5 ]; do
    echo "Executing \`while\` loop: $i"
    i=$((i + 1))
done

# until
i=0
until [ $i -ge 5 ]; do
    echo "Executing \`until\` loop: $i"
    i=$((i + 1))
done
# break
for i in {1..10}; do
    if [ "$i" -eq 5 ]; then
        break
    fi
    echo "Loop: $i"
done

# break multiple loops
for i in {1..10}; do
    for j in {1..2}; do
        if [ "$i" -eq 5 ]; then
            break 2
        fi
        echo "($i, $j)"
    done
done

# continue
for i in {1..10}; do
    if [ "$i" -eq 5 ]; then
        continue
    fi
    echo "Loop: $i"
done

# continue multiple loops
for i in {1..5}; do
    for j in {1..2}; do
        if [ "$i" -eq 3 ]; then
            continue 2
        fi
        echo -n "($i, $j) "
    done
    echo "($i)"
done

Exit

# exit
exit

# exit with status
exit 0 # success
exit 1 # failure (non-zero status)

Functions

# declaration
function greet() {
    echo "Hello, World!"
}
greet # call

# with arguments
function greet() {
    echo "Hello, $1!"
    echo "Hello, $2, too!"
}
greet "World" "Saplyn" # call

Resources

IDE Support

Go

Go is a statically typed, compiled programming language designed at Google. It's simple by design, and it's a great language for writing concurrent programs.

Features

  • Compiled
  • Statically typed
  • Garbage-collected
  • Concurrent
  • Supports object-oriented diagram

Sample Program (Hello World)

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Basic Syntax

Package Structure

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Variables & Types

// declare a variable
var a int
var b int = 10 // with initialization
var c = 10     // type inferred
d := 10        // short hand

// declare multiple variables
var (
    e int
    f int = 10
    g = 10
    h := 10
)

// declare constants
const i int = 10
const j = 10
const (
    k int = 10
    l = 10
)

// array (fixed size)
var m [5]int
n := [5]int{1, 2, 3, 4, 5}
o := [...]int{1, 2, 3, 4, 5}  // size inferred

// slice (dynamic size)
p := []int{1, 2, 3, 4, 5}
q := n[1:3] // slice of n from index 1 to 3
r := n[:3]  // slice of n from index 0 to 3
s := make([]int, 5) // make a slice with length 5

// map (key-value)
t := map[string]int{
    "a": 1,
    "b": 2,
}
for k, v := range t {
    fmt.Println(k, v)
}

// types
int, int8, int16, int32, int64
uint, uint8, uint16, uint32, uint64, uintptr
float32, float64
complex64, complex128
byte // uint8
rune // (int32) a Unicode code point
string

// type conversion
i := 42
f := float64(i)
u := uint(f)

Operators

// arithmetic
+ - * / % ++ --
// `/` is integer division if both operands are integers.
// `%` is the remainder of the division.

// relational
== != > < >= <=

// logical
&& || !
// `&&` and `||` are short-circuit operators.

// bitwise
& | ^ << >>
&^ // bit clear (AND NOT)

// assignment
= += -= *= /= %= <<= >>= &= |= ^=

Control Flow

Branching

// if-else
if a > 10 {
    fmt.Println("a is greater than 10")
} else if a < 10 {
    fmt.Println("a is less than 10")
} else {
    fmt.Println("a is equal to 10")
}

// switch
switch a {
case 1:
    fmt.Println("a is 1")
case 2:
    fmt.Println("a is 2")
default:
    fmt.Println("a is neither 1 nor 2")
}
switch {  // switch true for long-chain if-else
case t.Hour() < 12:
    fmt.Println("Good morning!")
case t.Hour() < 17:
    fmt.Println("Good afternoon.")
default:
    fmt.Println("Good evening.")
}

Looping

// for (while-style)
x := 0
for x < 5 {
    fmt.Println(x)
    x++
}

// for (C-style)
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// for (range)
y := []int{1, 2, 3, 4, 5}
for i, v := range y {
    fmt.Println(i, v)
}
for _, v := range y {  // ignore index
    fmt.Println(v)
}

// infinite loop
for {
    fmt.Println("infinite loop")
}

break    // exit loop
continue // skip to next iteration

Defer

A defer statement defers the execution of a function until the surrounding function returns.

// defer
func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
} // output: hello world

// defer stack
func main() {
    for i := 0; i < 5; i++ {
        defer fmt.Println(i)
    }
} // output: 4 3 2 1 0

Functions

// declaration
func add(a int, b int) int {
    return a + b
}
// call 
add(1, 2)

// multiple return values
func swap(a int, b int) (int, int) {
    return b, a
}
func map(str []string, f func(string) string) []string {
    result := make([]string, len(str))
    for i, v := range str {
        result[i] = f(v)
    }
    return result
} 

// named return values
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}

Pointers

// declaration
var a int = 10
var b *int = &a // b is a pointer to a
var nothing *int // `nil` pointer

// dereference
fmt.Println(*b) // 10

// pass by reference (pointer's value)
func change(a *int) {
    *a = 20
}
change(&a)     // `&` address of
fmt.Println(a) // 20

Structs

// declaration
type person struct {
    name string
    age  int
}

// initialization
p := person{"Alice", 20}
q := person{
    name: "Bob",
    age:  30,
}
fmt.Println(p.name) // access fields

// receiver function (method)
func (p person) greet() {
    fmt.Println("Hello, my name is", p.name)
}
p.greet() // call method

// pointer receiver function
func (p *person) grow() {
    p.age++  // auto dereferenced
}
p.grow()

Interfaces

// declaration
type drawable interface {
    info() string
    draw()
}

// implementation
type circle struct {
    radius int
}
func (c circle) info() string {
    return "Circle"
}
func (c circle) draw() {
    fmt.Println("Drawing circle")
}

// polymorphism
func drawShape(d drawable) {
    fmt.Println(d.info())
    d.draw()
}
drawShape(circle{10})

// empty interface
func describe(i interface{}) {  // any type
    fmt.Printf("(%v, %T)\n", i, i)
}
describe(42)
describe("hello")

Essentials

Error Handling

Go programs express error state with error values, a built-in interface.

// https://go.dev/tour/methods/19
// the error type
type error interface {
    Error() string
}

// failable & handling error
import "fmt"
i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

// custom error
import "time"
type MyError struct {
    When time.Time
    What string
}
func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s", e.When, e.What)
}
func run() error {
    return &MyError{ time.Now(), "it didn't work", }
}

Generics

Go functions can be written to work on multiple types using type parameters. The type parameters of a function appear between brackets, before the function's arguments.

// https://go.dev/tour/generics/1
// generic function
func Index[T comparable](s []T, x T) int {
    for i, v := range s {
        // v and x are type T, which is `comparable`
        if v == x {
            return i
        }
    }
    return -1
}

// generic type
type Stack[T any] struct {
    data []T
}

Goroutines

A goroutine is a lightweight thread managed by the Go runtime.

// https://go.dev/tour/concurrency/1
func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}
// `go` starts a new goroutine
func main() {
    go say("world")
    say("hello")
}

Channels

Channels are a typed conduit through which we can send and receive values with the channel operator <-. By default, sends and receives block until the other side is ready.

// https://go.dev/tour/concurrency/2
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}

    // make a channel of int
    c := make(chan int)
    // start two goroutines
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)
}

A sender can close a channel to indicate that no more values will be sent. Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression.

// https://go.dev/tour/concurrency/4
func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)  // close the channel
    // can be tested by `v, ok := <-c`
}

func main() {
    // make a buffered channel
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    // receive from the channel until it's closed
    for i := range c {
        fmt.Println(i)
    }
}

Select

The select statement lets a goroutine wait on multiple communication operations. A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

// https://go.dev/tour/concurrency/6
func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}

Resources

Official Documentation

Packages Memo

Java

Java is a high-level, class-based, object-oriented programming language that is designed to have as few implementation dependencies as possible.

Features

  • Object-oriented
  • Garbage-collected
  • Statically typed
  • Platform-independent by runtime
  • Compiled and interpreted

Sample Program (Hello World)

// hello world in Java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

Basic Syntax

Program Structure

In Java, a program is made up of one or more classes, each file is a class. The class with the main method acts as the entry point of the program.

// HelloWorld.java
public class HelloWorld {
    // other methods
    public static void sayHello() { /* ... */ }
    // main method
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

Variables & Types

// variable
int score = 90;
char first_name;
double height = 1.75;

// constant
final int MAX = 100;
final double PI = 3.14;

// array
int[] numbers = {1, 2, 3, 4, 5};
char[] str = new char[5];

Operators

// arithmetic
+ - * / % ++ --

// relational
== != > < >= <=

// bitwise
& | ^ ~ << >>
>>> // Unsigned right shift

// logical
&& || !
// `&&` and `||` are short-circuit operators.

// assignment
= += -= *= /= %= 

Control Flow

Branching

if (condition) {
    // ...
} else if (condition) {
    // ...
} else {
    // ...
}

switch (expression) {
    case value1:
        // ...
        break;
    case value2:
        // ...
        break;
    default:
        // ...
}

Looping

// while loop
int i = 0;
while (i < 10) {
    System.out.println(i);
}

// do-while loop
int i = 0;
do {
    System.out.println(i);
} while (i < 10);

// for loop
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

// for each loop
int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
    System.out.println(number);
}

break;    // exits the loop
continue; // skips the current iteration.

// label jump
outerLoop:
for (int i = 0; i < 5; i++) {
    innerLoop:
    for (int j = 0; j < 5; j++) {
        if (i == 1 && j == 1) {
            // continue with the next iteration of the inner loop
            continue innerLoop;
        }
        if (i == 2 && j == 2) {
            // break out of the outer loop
            break outerLoop;
        }
        System.out.println(i);
    }
}

Functions

// declaration
int add(int num1, int num2) {
    int sum = num1 + num2;
    return sum;
}
add(3, 5); // call

// lambda
Function<Integer, Integer> add = (a, b) -> a + b;
add.apply(3, 5);

Classes

Class as Object

In Java, every item has an access specifier:

  • public: accessible from anywhere
  • protected: accessible within the package and by subclass
  • (default): accessible within the package
  • private: accessible within the class
public class Point {
    // data members
    private double x;
    private double y;

    // method
    public void set(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double dist(Point that) {
        return Math.sqrt(
            (x - that.x) * (x - that.x) +
            (y - that.y) * (y - that.y)
        );
    }
}

Point p = new Point();
p.set(3.0, 4.0);

Classes may have static members, which are shared by all objects (instances) of the same class (type).

public class Student {
    // fields
    String name;
    int age;

    // constructor
    Student(String name, int age) {
        /* ... */
    }

    // method
    void displayInfo() {
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
    }
}

Copy & Reference

In Java, everything that is not a primitive type is a reference type. When an object is assigned to another object, the reference is copied, not the object itself.

// shallow copy
// `obj1` and `obj2` now share the same memory address (reference)
Object obj1 = new Object();
Object obj2 = obj1;

// deep copy constructor
public class MyClass {
    private int number;
    private String text;

    // deep copy constructor
    public MyClass(MyClass original) {
        this.number = original.getNumber();
        this.text = original.getText();
    }
}

Finalize Method

Java is garbage-collected, so there is no need to explicitly release resources. We can, however, @Override the finalize method to release resources before the object is garbage-collected.

public class Student {
    /* ... */

    @Override
    protected void finalize() throws Throwable {
        // Perform cleanup actions before object is garbage collected
        super.finalize();  // call the finalize method of the superclass
        System.out.println("Finalizing Student object: " + name);
    }
}

Inheritance

Every class may extend another class, inheriting its members and making it its superclass. A subclass may @Override a method of its superclass. A subclass may access the members of its superclass via super keyword.

// Animal.java
public class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public void makeSound() {
        System.out.println("The animal makes a sound.");
    }
}

// Dog.java
public class Dog extends Animal { // extends superclass
    public Dog(String name) {
        super(name);  // call superclass constructor
    }

    // override superclass method
    @Override
    public void makeSound() {
        super.makeSound();  // access superclass member
        System.out.println("Woof woof!");
        System.out.println("My name is " + super.name + "!");
    }
}

They can be abstract classes with abstract methods, which cannot be instantiated. They serve as blueprints for other classes.

// Animal.java
// abstract class, cannot be instantiated
public abstract class Animal {
    // abstract method, must be overridden by subclass
    public abstract void makeSound();

    // concrete method as default implementation
    public void sleep() {
        System.out.println("The animal is sleeping.");
    }
}

the final keyword prevents a class or method from being overridden.

// final class, cannot be extended
public final class NotExtendable {
    /* ... */
}

public class SomeClass {
    // final method, cannot be overridden
    public final void norOverridable() {
        System.out.println("Not overridable.");
    }
}

Interface

Interface is a contract defined for a class. It defines a set of abstract methods that a class can implement. A class may implement multiple interfaces.

// interface
interface Stringify {
    // method blueprint
    String toString();
}
interface Comparable {
    // default implementation
    default int compareTo(Object obj) {
        return 0;
    }
}

// class implementing the interface
class MyClass implements Stringify, Comparable {
    @Override
    public String toString() {
        return "MyClass";
    }

    @Override
    public int compareTo(Object obj) {
        /* ... */
    }
}

Stringify obj = new MyClass();
System.out.println(obj.toString());

Interfaces may also extend other interfaces.

interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

interface Walkable {
    void walk();
}

interface Amphibious extends Flyable, Swimmable, Walkable {
    void land();
}

Polymorphism

public class Animal {
    public void makeSound() { System.out.println("Animal is making a sound"); }
}
public class Dog extends Animal {
    @Override
    public void makeSound() { System.out.println("Dog is barking"); }
}
public class Cat extends Animal {
    @Override
    public void makeSound() { System.out.println("Cat is meowing"); }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = new Dog();
        Cat cat = new Cat();

        System.out.println(animal instanceof Animal); // true
        System.out.println(dog instanceof Animal); // true
        System.out.println(cat instanceof Animal); // true
        System.out.println(dog instanceof Dog); // true
        System.out.println(cat instanceof Dog); // false


        Animal animal1 = new Animal();
        Animal animal2 = new Dog();
        Animal animal3 = new Cat();

        animal1.makeSound();  // Output: Animal is making a sound
        animal2.makeSound();  // Output: Dog is barking
        animal3.makeSound();  // Output: Cat is meowing
    }
}

Exception

Try, Catch & Finally

BufferedReader reader = null;

try {  // Code that might throw an exception
    reader = new BufferedReader(new FileReader("example.txt"));
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (FileNotFoundException e) {  // Handle the exception
    System.out.println("File not found: " + e.getMessage());
} catch (IOException e) {  // Handle the exception
    System.out.println("Error reading file: " + e.getMessage());
} finally {  // This code will always run
    try {
        if (reader != null) {
            reader.close();
        }
    } catch (IOException e) {
        System.out.println("Error closing file: " + e.getMessage());
    }
}

Throw & Throws Marker

The throw statement is used to explicitly throw an exception. And the throws tells the compiler that a method may throw an exception.

public void checkNumber(int num) throws NegativeNumberException {
    if (num < 0) {
        throw new NegativeNumberException("Number is negative: " + num);
    }
}

Their are two types of exceptions in Java: checked and unchecked. The checked exceptions are checked at compile-time, while the unchecked exceptions are not.

try {
    FileInputStream file = new FileInputStream("myFile.txt");
} catch (FileNotFoundException e) {
    // Handle the checked exception (e.g., provide an alternative file)
}

// ArithmeticException, an unchecked exception
int result = 10 / 0;

It's possible to define a custom exception by extending the Exception class.

class CustomCheckedException extends Exception {
    public CustomCheckedException(String message) {
        super(message);
    }
}

class MyUncheckedException extends RuntimeException {
    // Constructor and additional methods
}

Generic

Type Parameter

To work with generic object in Java, we define classes or interfaces with <T>.

public class Box<T> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

Box<int> intObj = new Box<>(42);
Box<String> strObj = new Box<>("Hello, Generics!");

int intValue = intObj.getData();
String strValue = strObj.getData();

We can also use generic with methods.

public <T> T findMax(T[] arr) {
    if (arr == null || arr.length == 0) {
        return null;
    }

    T max = arr[0];
    for (T item : arr) {
        if (item.compareTo(max) > 0) {
            max = item;
        }
    }
    return max;
}

Python

Python is a high-level, interpreted programming language. It is known for its simplicity and readability. Python is a great language for beginners and is widely used in data science and machine learning.

Features

  • Interpreted
  • Dynamically typed
  • Easy to learn

Sample Program (Hello World)

# hello world in Python
print("Hello world!")

Basic Syntax

Variables

# variable
score = 90
name = "Saplyn"
MAX = 100

# string
name = "Saplyn"
desc = """
he's a
very very
long
cat
"""
fstr = f"Hello, {name}!"

Operators

# arithmetic
+ - * / % // **
# `/` is float division
# `//` is floor division
# `**` is exponentiation

# comparison
== != < > <= >=

# logical
and or not

# assignment
= += -= *= /= %= //= **=

Flow Control

Branching

# if-else
if age < 12:
    print("Kitty!")
elif age < 18:
    print("Cat!")
else:
    print("Adult Cat!")

# simple if-else
print("Kitty!") if age < 12 else print("Cat!")

Looping

# while
i = 0
while i < 5:
    print(i)
    i += 1

# for
for i in range(5):
    print(i)
for i in range(1, 5):
    print(i)
fruits = ['apple', 'banana', 'orange']
for fruit in fruits:
    print(fruit)

# loop-else
# code inside the else block will be executed only if the loop completes all
# iterations without any break statement being encountered.
i = 1
while i <= 5:
    print(f"loop: {i}")
    i += 1
else:
    print("Loop completed without any breaks")

# break
i = 0
while True:
    i += 1
    if i > 5:
        break

# continue
for i in range(5):
    if i % 2 == 0:
        continue
    print(i)

Functions

# definition
def add(a, b=1):
    return a + b
add(3, 4)     # => 7
add(3)        # => 4
add(b=3, a=4) # => 7, keyword arguments

# arbitrary arguments
def add(*args):
    return sum(args)
add(1, 2, 3, 4, 5) # => 15

# arbitrary keyword arguments
def display(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")
display(name="Saplyn", age=18) # => name: Saplyn, age: 18

Lists, Tuples & Dictionaries

# list
numbers = [1, 2, 3, 4, 5]
numbers[0]   # => 1
numbers[-1]  # => 5, backward index
numbers[1:3] # => [2, 3], slicing
numbers[::2] # => [1, 3, 5], step slicing

# tuple (immutable)
normal_tuple = (1, 2, 4, 8, 16)
single_tuple = 1,
empty_tuple  = ()

# dictionary (map)
person = {
    "name": "Saplyn",
    "age": 18,
    "height": 1.75
}
person["name"]  # => "Saplyn"
person["age"]   # => 18
person["height"]# => 1.75

Haskell

Haskell is a purely functional programming language that is declarative and statically typed. It was named after the logician Haskell Curry. Haskell's main implementation is the Glasgow Haskell Compiler (GHC).

Features

  • Statically typed
  • Purely functional
  • Lazy evaluation

Basic Syntax

Values, Functions & Types

Haskell is a purely functional programming language, which means that every variable is immutable (value) and every function is a first-class citizen.

-- values
x :: Int  -- declare x as an Int
x = 1     -- bind 1 to x
y = 2     -- bind 2 to y (type inferred)
z = x + y -- bind (x + y) to z

-- functions
add :: Int -> Int -> Int -- declare `add` as a function from Int to Int to Int
                         -- (can be seen as "take 2 Ints and return an Int")
add x y = x + y          -- declare `add x + y` as a `x + y`
add 1 2                  -- 3

-- types
Int      -- integer, fixed-precision integer
Integer  -- integer, arbitrary-precision integer
Float    -- floating-point number, single-precision
Double   -- floating-point number, double-precision
Bool     -- boolean, either True or False
Char     -- character, single unicode character
[a]      -- list, a sequence of elements of type `a`
(a, b)   -- tuple, a pair of elements of type `a` and `b`

Operators

-- arithmetic 
+ - *  -- (binary, infix)
div    -- (binary, prefix)

-- comparison (binary, infix)
== /= < <= > >=

-- logical
&& ||  -- (binary, infix)
not    -- (unary, prefix)
-- infix marker
4 `add` 2  -- add 4 2
4 `div` 2  -- div 4 2

-- function application
($) :: (a -> b) -> a -> b
($) f x = f x
$ add 1 2  -- 3

-- function composition
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g x = f (g x)
descSort = reverse . sort

List

Unlike imperative programming languages, List in Haskell is considered to be a primary data structure, and one of the most important and widely used types.

-- list of Ints
list = [1, 2, 3]
-- `:` put an element at the beginning of a list
list' = 4 : 5 : 6 : [] 
-- `++` concatenate two lists
list'' = list ++ list' ++ [7, 8, 9]

To work with lists, Haskell provides a set of useful functions:

head [1, 2, 3, 4]   -- 1
tail [1, 2, 3, 4]   -- [2, 3, 4]
init [1, 2, 3, 4]   -- [1, 2, 3]
last [1, 2, 3, 4]   -- 4
length [1, 2, 3, 4] -- 4
null []             -- True

Flow Control

Branching

-- multiple bindings
isZero :: Int -> Bool
isZero 0 = True
isZero _ = False  -- `_` wildcard, matches anything

-- if-then-else
max :: Int -> Int -> Int
max x y =
  if x > y then
    x
  else
    y

Recursion

In a pure functional language like Haskell, there is no way to change the value, making loops impossible. Instead, recursion is used to replace its functionality.

-- factorial
factorial :: Int -> Int
factorial 0 = 1                     -- base case, also termination condition
factorial n = n * factorial (n - 1) -- recursive case

-- fibonacci
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

Custom Types

In Haskell, we can define our own types using type and data keywords.

-- type synonym
type Name = String
type Age = Int

-- data type
data Color = Red | Green | Blue  -- different constructors
data Calculation =
  Add Int Int    -- constructor with parameters
  | Sub Int Int
  | Mul Int Int
  | Div Int Int
calc :: Calculation -> Int
calc (Add x y) = x + y
calc (Sub x y) = x - y
calc (Mul x y) = x * y
calc (Div x y) = x `div` y

-- recursive data type
data PeaNum = Succ PeaNum | Zero
four :: PeaNum
four = Succ $ Succ $ Succ $ Zero

data Tree a = Leaf | Node (Tree a) a (Tree a)
tree :: Tree Int
tree = 
  Node (Node Leaf 1 Leaf) 2 (Node (Node Leaf 3 Leaf) 4 Leaf)

Function

Guards

Guards are a way to define a function by pattern matching on the input arguments. They are defined by a series of boolean expressions.

max' :: Int -> Int -> Int
max' x y
  | x > y     = x
  | otherwise = y

fac n
  | n <= 1    = 1
  | otherwise = n * fac (n-1)

let and where

In functions, let and where can be used to declare local expression aliases in a function.

inRange l r x =
  let in_lower_bound = l <= x
      in_upper_bound = r >= x
  in
    in_lower_bound && in_upper_bound

inRange l r x =
  in_lower_bound && in_upper_bound
  where
    in_lower_bound = l <= x
    in_upper_bound = r >= x

Accumulator

Accumulator is a common pattern in functional programming to avoid stack overflow when using recursion. It's also known as tail recursion.

fac n = aux n 1  -- `aux` is a helper function
  where
    aux n acc    -- `aux` is declared here, and `acc` is the accumulator
      | n <= 1    = acc
      | otherwise = aux (n - 1) (n * acc)

Higher Order Function

A higher order function is a function that takes a function as an argument or returns a function as a result.

app :: (a -> b) -> a -> b -- `app` takes a function `a -> b` and an argument `a`
app f x = f x             -- and returns a result `b`

add1 x = x + 1
app add1 1     -- 2

Lambda Function

Lambda function is an anonymous function that is created using the \ symbol.

(\x -> x + 1) 1      -- 2
(\x y -> x + y) 1 2  -- 3

-- as first-class citizens, lambda functions can be bind to a name
add = \x y -> x + y

Currying

In Haskell, any function can be rewritten to take only one argument:

-- the function that takes 3 args
f :: a -> b -> c -> d
-- can be written as:
f :: a -> (b -> (c -> d))
-- a func that takes 1 arg and returns a func that takes 1 arg
-- which returns a func that takes 1 arg and returns the result

Partial Application

Partial application is the process of supplying a function with fewer arguments than it requires, resulting in a new function that takes the remaining arguments.

add :: Int -> Int -> Int
add = (\x -> (\y -> x + y))

map :: (a -> b) -> [a] -> [b]
doubleList :: [Int] -> [Int]
doubleList = map (\x -> x * 2)

Folding

Folding is a way to reduce a list to a single value. It takes a binary function, a starting value, and a list to fold up. In Haskell, there are two folding functions: foldl and foldr.

foldr :: (a -> b -> b) -> b -> [a] -> b

foldr (+) 0 [1, 2, 3] -- 1 + (2 + (3 + 0)) = 6
sum    = foldr (+) 0
and    = foldr (&&) True
or     = foldr (||) False
length = foldr (\x -> (+) 1) 0
length = foldr (const $ (+) 1) 0
map f  = foldr ((:) . f) []

-- https://wiki.haskell.org/Fold#List_folds_as_structural_transformations
foldr (\x acc -> <term>) <start_acc> <list>
foldl (\acc x -> <term>) <start_acc> <list>

Unsorted Notes

List Comprehension

List comprehension is a concise way to generate lists in Haskell.

[ <gen> | <elem> <- <list>, ..., <guard>, ...]

[ 2*x | x <- [1, 2, 3] ]        => [2, 4, 6]
[ 2*x | x <- [1, 2, 3], x > 1 ] => [4, 6]

[ (x,y) | x <- [1, 2, 3], y <- ['a', 'b'] ]
  => [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'a')]

JavaScript

JavaScript is a programming language used to make websites interactive, allowing web pages to respond to user actions and dynamically update content. It's initially designed to run in web browsers, but with the introduction of Node.js, it can now run on nearly any platform.

Features

  • Interpreted
  • Dynamically-typed

Sample Program (Hello World)

console.log('Hello, world!');

Basic Syntax

Variables & Types

// variable
var score = 90;       // old-fashioned
let name = 'Saplyn';  // preferred in modern JS
// var vs let: https://stackoverflow.com/questions/762011/what-is-the-difference-between-let-and-var

// constant
const PI = 3.14;      // cannot be reassigned

// array (dictionary, associative array)
let numbers = [1, 2, 3, 4, 5];
let names = ['Saplyn', 'Moxie', 'Nyx'];
let mixed = [1, 'Saplyn', true, { name: 'Moxie' }];

// basic types
number, string, boolean
null       // absence of value
undefined  // uninitialized variable
symbol     // guaranteed unique value type
// symbol: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol

Operators

// arithmetic
+ - * / % ++ --
// `/` is floating-point division
// `%` is the remainder of the division

// relational
== !=    // loss equality
=== !==  // strict equality
>= <= > <

// logical
&& || !
// `&&` and `||` are short-circuit operators.

// bitwise
& | ^ ~ << >> >>>

// assignment
= += -= *= /= %=

Control Flow

Branching

// if-else statement
if (score >= 90) {
  console.log('A');
} else if (score >= 80) {
  console.log('B');
} else {
  console.log('C');
}

// switch statement
switch (score) {
  case 90:
    console.log('A');
    break;
  case 80:
    console.log('B');
    break;
  default:
    console.log('C');
}

// ternary operator
let result = (score >= 60) ? 'Pass' : 'Fail';

// nullish coalescing operator
let name = null;
let username = name ?? 'Anonymous';

Looping

// while loop
let i = 0;
while (i < 5) {
  console.log(i);
  i++;
}

// do-while loop
let j = 0;
do {
  console.log(j);
  j++;
} while (j < 5);

// for loop
for (let k = 0; k < 5; k++) {
  console.log(k);
}

// for-in loop (iterate over object properties)
let person = { name: 'Saplyn', age: 25 };
for (let key in person) {
  console.log(key, person[key]);
}

// for-of loop (iterate over iterable objects)
let numbers = [1, 2, 3, 4, 5];
for (let number of numbers) {
  console.log(number);
}

break // exit the loop
continue // skip the rest of the loop

Functions

// function declaration
function add(a, b) {
  return a + b;
}
let sum = add(3, 5);

// function expression (function as value)
let greet = function(name) {
  console.log(`Hello, ${name}!`);
};
greet('Saplyn');

// arrow function (ES6)
let greet = (name) => {
  console.log(`Hello, ${name}!`);
};
greet('Saplyn');

Resources

Runtime

  • Node.js: An open-source, cross-platform JavaScript runtime.
  • Deno: A most productive, secure, and performant JavaScript runtime.
  • Bun: A fast all-in-one JavaScript runtime.

Package Manager & Registry

  • nvm: Node version manager, a bash script to manage multiple active Node.js versions.
  • npm: A package manager and registry for JavaScript.
  • pnpm: A fast, disk space efficient package manager.
  • yarn: A package manager that doubles down as project manager.
  • jsr: A modern package registry for JavaScript and TypeScript.

TypeScript

TypeScript is JavaScript with syntax for types. It is a superset of JavaScript, and was also built on top of JavaScript.

Typed JS

TypeScript is a superset of JavaScript, and it not possible to learn TypeScript without learning JavaScript. For this reason, please refer to the JavaScript section for the basics.

Type Annotations

// typing variables
let n: number = 1;
let s: string = "hello";
let b: boolean = true;
let a: number[] = [1, 2, 3];
let o: {
    name: string,
    age: number
} = { name: "John", age: 30 };

// special types
let n: null = null;
let u: undefined = undefined;

// any type, opt out of type checking
let a: any = 1;

// typing functions
function add(a: number, b: number): number {
  return a + b;
}

Vue

Vue is a JavaScript framework for building user interfaces. It builds on top of standard HTML, CSS, and JavaScript and provides a declarative and component-based programming model that helps us efficiently develop user interfaces.

Basics

Vue Component

In most build-tool-enabled Vue projects, we author Vue components using an HTML-like file format called Single-File Component, .vue. A Vue SFC consists of three parts:

  • <template>: the HTML template
  • <script>: the JavaScript logic
  • <style>: the CSS styles
<!-- https://vuejs.org/guide/introduction.html#single-file-components -->
<template>
  <button @click="count++">Count is: {{ count }}</button>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<style scoped>
button {
  font-weight: bold;
}
</style>

Reactivity

Vue's reactivity is achieved by using the ref and reactive functions. ref is used to create a reactive reference to a value, and reactive is used to create a reactive object.

<!-- https://vuejs.org/tutorial/#step-2 -->
<template>
  <!-- Mustache for templating -->
  <h1>{{ message }}</h1>
  <p>Count is: {{ counter.count }}</p>
</template>

<script setup>
import { reactive, ref } from 'vue'

const counter = reactive({ count: 0 })
const message = ref('Hello World!')
</script>

Reactive properties can be computed using the computed function.

<template>
  <p>Count is: {{ count }}</p>
  <p>Double is: {{ doubleCount }}</p>
</template>

<script setup>
import { computed, ref } from 'vue'

const count = ref(0)
const doubleCount = computed(() => count.value * 2)
</script>

Reactive properties can be watched using the watch function.

<template>
  <p>Count is: {{ count }}</p>
</template>

<script setup>
import { ref, watch } from 'vue'

const count = ref(0)
watch(count, (newValue, oldValue) => {
  console.log(`count changed from ${oldValue} to ${newValue}`)
})
</script>

V-Attributes

  • v-bind:attr = "expr" (:attr = "expr"): binds an attribute to an expression
  • v-on:event = "handler" (@event = "handler"): binds an event listener to a handler
  • v-model: two-way binding for form input elements
<template>
  <p :class="active ? 'text-red-500' : 'text-black'">{{ text }}</p>
  <button @click="handler">click me</button>
  <input v-model="input" />
</template>

<script setup>
import { ref } from 'vue';
const active = ref(false);

function handler() {
  active.value = !active.value;
}

const text = ref('');
</script>
  • v-if="expr", v-else-if="expr", v-else: conditional renders elements
  • v-show="expr": toggles the visibility of an element
<template>
  <div v-if="condA">Hello</div>
  <div v-else-if="condB">World</div>
  <div v-else>!</div>

  <div v-show="condC">Visible</div>
</template>

<script setup>
import { ref } from 'vue';

const condA = ref(false);
const condB = ref(false);
const condC = ref(false);
</script>
  • v-for="item in items", v-for="(item, index) in items: renders a list of items
<template>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.text }}</li>
  </ul>
</template>

<script setup>
import { ref } from 'vue';

const items = ref([
  { id: 1, text: 'Hello' },
  { id: 2, text: 'World' },
]);
</script>

Component

Props

A child component can accept input from the parent via props.

<!-- https://vuejs.org/tutorial/#step-12 -->
<!-- Child.vue -->
<template>
  <h2>{{ msg || 'No props passed yet' }}</h2>
</template>
<script setup>
const props = defineProps({
  msg: String
})
</script>

<!-- Parent.vue -->
<template>
  <Child :msg="greeting" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const greeting = ref('Hello from parent')
</script>

Emits

A child component can emit events to the parent.

<!-- https://vuejs.org/tutorial/#step-13 -->
<!-- Child.vue -->
<template>
  <h2>Child component</h2>
</template>
<script setup>
const emit = defineEmits(['response'])

emit('response', 'hello from child')
</script>

<!-- Parent.vue -->
<template>
  <Child @response="(msg) => childMsg = msg" />
  <p>{{ childMsg }}</p>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const childMsg = ref('No child msg yet')
</script>

Slots

A parent component can pass content to a child component via slots.

<!-- https://vuejs.org/tutorial/#step-14 -->
<!-- Child.vue -->
<template>
  <slot>Fallback content</slot>
</template>

<!-- Parent.vue -->
<template>
  <ChildComp>Message: {{ msg }}</ChildComp>
</template>
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const msg = ref('from parent')
</script>

Resources

Official Tooling

  • Vue.js: Vue official website
  • Vue Router: The official Router for Vue.js
  • Pinia: The official state management for Vue.js
  • Vite: The official build tool for Vue.js
  • IDE Support: Vue.js official IDE support

Community

  • PrimeVue: A rich set of UI components for Vue.js
  • VueUse: A collection of Vue composition utilities
  • Nuxt.js: An intuitive Vue framework

Flutter

Dart

This page is INCOMPLETE

This page is incomplete and is still being written.

Program Structure

// required, `main()` the entry of every dart program.
void main() {
    print('Hello, world!');
}

Variables & Types

// variable
int score = 90;
String name = 'John';

// type inference
var year = 1998;
var height = 1.75;
var map = {
    'name': 'John',
    'age': 23,
};

// null safety
int? age = null;  // nullable
int age = 23;     // non-nullable

// objects
List<int> numbers = [1, 2, 3];
Map<String, int> dict = {
    'one': 1,
    'two': 2,
};

Operators

// TODO

Control Flow

// if statement
if (cond) { /* ... */ }
else if (cond) { /* ... */ }
else { /* ... */ }

// c-style for loop
for (var i = 0; i < 10; i++) { /* ... */ }

// for-in loop
for (var item in items) { /* ... */ }

// while loop
while (cond) { /* ... */ }

Functions

// function
int add(int a, int b) {
    return a + b;
}
add(1, 2); // => 3

// arrow function
int minus(int a, int b) => a - b;
minus(3, 2); // => 1

Dart Classes

This page is INCOMPLETE

This page is incomplete and is still being written.

Classes

class Cat {
    String name;
    int age;

    // constructor
    Cat(this.name, this.age);

    // named constructor
    Cat.fromName(String name) {
        this.name = name;
    }

    // method
    void meow() {
        print('Meow!');
    }
}

Inheritance

class Animal {
    String name;
    int age;

    Animal(this.name, this.age);

    void eat() {
        print('$name is eating.');
    }
}

class Cat extends Animal {
    // propagate constructor
    Cat(String name, int age) : super(name, age);

    void meow() {
        print('Meow!');
    }
}

Mixins

SQL Server

SQL Server is a relational database management system (RDBMS) developed by Microsoft.

Features

  • Relational
  • ACID compliant

Basics

CRUD

Create

CREATE TABLE [person] (
    [person_id] bigint PRIMARY KEY NOT NULL,
    [name] varchar(255) NOT NULL
);

INSERT INTO [person] ([person_id], [name])
VALUES
    (1, 'John'),
    (2, 'Jane');

CREATE INDEX [idx_person_name]
ON person (name);

Read

SELECT * FROM [person];

SELECT [name]
FROM [person]
WHERE [name] = 'Jane';

SELECT count(*) AS [person_count]
FROM [person];

Update

ALTER TABLE [person]
ADD [last_name] varchar(255) NULL;

UPDATE [person]
SET [last_name] = 'Doe'
WHERE [name] = 'Jane';

ALTER TABLE [person]
DROP COLUMN [last_name];

Delete

DELETE FROM [person]
WHERE [name] = 'Jane';

DROP TABLE [person];

Using Database

USE [database_name];

Querying

WHERE Condition

-- Comparison
SELECT * FROM [person] WHERE [name] <> 'John';
SELECT * FROM [person] WHERE [age] > 18;

-- `NULL` Check
SELECT * FROM [person] WHERE [last_name] IS NULL;

-- `OR`, `AND` & `NOT`
SELECT * FROM [person] WHERE [name] = 'John' OR [name] = 'Jane';
SELECT * FROM [person] WHERE [name] = 'John' AND [last_name] = 'Doe';
SELECT * FROM [person] WHERE NOT [name] = 'John';

-- `LIKE` Pattern
SELECT * FROM [person] WHERE [name] LIKE 'J%';     -- %: any string
SELECT * FROM [person] WHERE [name] LIKE 'J_n_';   -- _: any character
SELECT * FROM [person] WHERE [name] LIKE 'J[ae]n'; -- []: any character in the set

-- `IN` & `NOT IN`
SELECT * FROM [person] WHERE [name] IN ('John', 'Jane');

-- `BETWEEN AND`
SELECT * FROM [person] WHERE [age] BETWEEN 18 AND 30;

Refining Results

-- `ORDER BY` `ASC` & `DESC`
SELECT * FROM [person] ORDER BY [name] ASC;
SELECT * FROM [person] ORDER BY [name] DESC;

-- `LIMIT`
SELECT * FROM [person] LIMIT 10;

-- `DISTINCT`
SELECT DISTINCT [name] FROM [person];

-- `TOP` & `WITH TIES`
SELECT TOP 1 * FROM [person];
SELECT TOP 1 WITH TIES * FROM [person] ORDER BY [age] ASC;
SELECT TOP 10 PERCENT * FROM [person] ORDER BY [age] ASC;

-- `GROUP BY`
-- Note: columns not in `GROUP BY` must be in an aggregate function.
SELECT [name], AVG([age]) AS [avg_age]
FROM [person]
GROUP BY [name];

-- `HAVING`
SELECT [name], AVG([age]) AS [avg_age]
FROM [person]
GROUP BY [name]
HAVING COUNT(*) > 1;

Joining Tables

-- `INNER JOIN`
SELECT * FROM [person]
INNER JOIN [address]
ON [person].[person_id] = [address].[person_id];

-- `LEFT JOIN` preserves all left table's rows, leaving `NULL` for unmatched rows.
-- `RIGHT JOIN` is the opposite.
SELECT * FROM [person]
LEFT JOIN [address]
ON [person].[person_id] = [address].[person_id];

-- `FULL JOIN`
-- Preserving all rows from both tables.
SELECT * FROM [person]
FULL JOIN [address]
ON [person].[person_id] = [address].[person_id];

-- `CROSS JOIN`
-- Cartesian product of two tables.
SELECT * FROM [person]
CROSS JOIN [address];

-- Self `JOIN`
-- Joining a table with itself.
SELECT * FROM [person] AS [p1]
INNER JOIN [person] AS [p2]
ON [p1].[person_id] = [p2].[person_id];

-- Chaining `JOIN`
SELECT * FROM [person]
INNER JOIN [address] ON [person].[person_id] = [address].[person_id]
INNER JOIN [phone] ON [person].[person_id] = [phone].[person_id];

Subqueries

-- Basic Subquery (inner query auto captures the outer query's columns)
SELECT
    [stu_id],
    [crs_id],
    [grade]
FROM [grades]
WHERE [grade] > (
    SELECT AVG([grades_sub].[grade]) AS [grade_avg]
    FROM [Grades] AS [grades_sub]
    WHERE [grades_sub].[stu_id] = [stu_id]
);

-- `ANY`/`SOME`: `true` if any subquery values meet the condition.
SELECT [product_name]
FROM [products]
WHERE [product_id] = ANY (
    SELECT [product_id]
    FROM [order_details]
    WHERE [quantity] = 10
);

-- `ALL`: `true` if every subquery values meet the condition.
SELECT [product_name]
FROM [products]
WHERE [product_id] = ALL (
    SELECT [product_id]
    FROM [order_details]
    WHERE [quantity] = 10
);

-- `IN` & `NOT IN`
SELECT [supplier_name]
FROM [suppliers]
WHERE [supplier_id] IN (
    SELECT [supplier_id]
    FROM [products]
    WHERE [price] > 50
);

-- `EXISTS` & `NOT EXISTS`
SELECT [supplier_name]
FROM [suppliers]
WHERE EXISTS (
    SELECT [product_name]
    FROM [products]
    WHERE [products].[supplier_id] = [suppliers].[supplier_id] AND [price] < 20
);

-- Relational Division
-- select students who selected all the courses
SELECT -- projection
    [students].[name],
    [students].[id]
FROM [students]
WHERE NOT EXISTS( -- divisor
    SELECT *
    FROM [courses]
    WHERE NOT EXISTS( -- dividend
        SELECT *
        FROM [enrollments]
        WHERE [enrollments].[crs_id] = [courses].[id]
            AND [enrollments].[stu_id] = [students].[id] 
    )
);

INSERT With SELECT

-- `INSERT INTO SELECT FROM`
INSERT INTO [customers] ([customer_name], [city], [country])
SELECT [supplier_name], [city], [country]
FROM [suppliers];

-- `SELECT INTO`
SELECT [id], [name], [email]
INTO [employees_new]
FROM [users]
WHERE [id] > 200;

Integrity

Constraints

-- `PRIMARY KEY`
ALTER TABLE [users]
ADD CONSTRAINT [pk_users_id]
PRIMARY KEY ([id]);

-- `FOREIGN KEY`
ALTER TABLE [orders]
ADD CONSTRAINT [fk_orders_customer_id]
FOREIGN KEY ([customer_id]) REFERENCES [customers]([id])
-- When referenced row is deleted, delete the row with the foreign key.
ON DELETE CASCADE
-- When referenced row is updated, do nothing.
ON UPDATE NO ACTION;

-- `UNIQUE`
ALTER TABLE [users]
ADD CONSTRAINT [uq_users_email]
UNIQUE ([email]);

-- `CHECK`
ALTER TABLE [orders]
ADD CONSTRAINT [ck_orders_quantity]
CHECK ([quantity] > 0 AND [quantity] < 100);

-- `DEFAULT`
ALTER TABLE [users]
ADD CONSTRAINT [df_users_created_at]
DEFAULT GETDATE() FOR [created_at];

-- `IDENTITY`: an auto-incr column, typically for generating primary keys.
ALTER TABLE [users]
ADD CONSTRAINT [df_users_id]
IDENTITY(1, 1);

Transactions

Transactions are used to ensure that a series of SQL statements are executed as a single unit. If any of the statements fail, the entire transaction is rolled back.

-- `BEGIN TRANSACTION` and `COMMIT TRANSACTION`
BEGIN TRANSACTION;
PRINT 'SQL statements...';
PRINT 'SQL statements...';
COMMIT TRANSACTION;

-- `ROLLBACK` to undo the transaction
BEGIN TRANSACTION;
PRINT 'SQL statements...';
PRINT 'Oops, something went wrong...';
ROLLBACK;

SQL Programming

Variables

-- Declare a variable
DECLARE @max_id int;

-- Set a variable's value
SET @max_id = 100;
SELECT @max_id = 100, @min_id = 1;

-- SQL variables can only hold one value.
-- If multiple is assigned at a time, the value of the last row is used.
SELECT @employee_name = [employee_name]
FROM [employees]
WHERE [employee_id] = 123;

Control Flow

Grouping

-- `BEGIN` & `END`
BEGIN
    PRINT 'statement 1';
    PRINT 'statement 2';
    PRINT 'statement 3';
END

Branching

-- `IF ELSE` statement
IF EXISTS (
    SELECT *
    FROM [courses]
    WHERE [name] = 'relational database'
)
    BEGIN
        DECLARE @avg_grade FLOAT;
        SET @avg_grade = (
            SELECT AVG([grade])
            FROM [grades]
            INNER JOIN [courses]
            ON [grades].[crs_id] = [courses].[id]
            WHERE [courses].[name] = 'relational database'
        );
    END
ELSE
    PRINT "there is no course named relational database";

-- `CASE WHEN` expression
SELECT
    [order_id],
    [quantity],
    CASE
        WHEN [quantity] > 30 THEN 'The quantity is greater than 30'
        WHEN [quantity] = 30 THEN 'The quantity is 30'
        ELSE 'The quantity is under 30'
    END AS [quantity_text]
FROM [order_details];

Looping

-- `WHILE` loop
DECLARE @counter INT;
SET @counter = 0;
WHILE @counter < 10
BEGIN
    PRINT 'The counter is ' + CAST(@counter AS VARCHAR);
    SET @counter = @counter + 1;
END

-- `BREAK` & `CONTINUE`
DECLARE @counter INT;
SET @counter = 0;
WHILE (1 = 1)
BEGIN
    SET @counter = @counter + 1;
    IF @counter > 10
        BREAK;
    IF @counter = 5
        CONTINUE;
    PRINT 'The counter is ' + CAST(@counter AS VARCHAR);
END

Stored Procedures

A procedure in SQL Server is a named set of SQL statements, stored in the database, that can be executed as a single unit. It can alter database tables, execute queries, and perform other operations.

-- Declaring a stored procedure
CREATE PROC [proc_customer_info]
    -- `@customer_id` is the input parameter
    @customer_id INT
AS  -- start the procedure's code block
BEGIN
    SELECT * FROM [customers]
    WHERE customer_id = @customer_id
END

-- Executing a stored procedure
EXEC [proc_customer_info] 123;

-- Output parameters
CREATE PROC [proc_customer_info]
    @customer_id INT,
    -- `@customer_name` is the output parameter
    @customer_name NVARCHAR(50) OUTPUT
AS
BEGIN
    SELECT @customer_name = [customer_name]
    FROM [customers]
    WHERE [customer_id] = @customer_id
END

Functions

A function in SQL Server is a reusable piece of code that performs a specific task and returns a value. It must have at lease one input parameter and returns a value. It may not alter database objects.

-- Declaring a function
CREATE FUNCTION [fn_get_customer_name]
    -- `@customer_id` is the input parameter
    (@customer_id INT)
RETURNS NVARCHAR(50)  -- the return type
AS
BEGIN
    DECLARE @customer_name NVARCHAR(50);
    SELECT @customer_name = [customer_name]
    FROM [customers]
    WHERE [customer_id] = @customer_id;
    RETURN @customer_name;
END

-- Calling a function
SELECT [fn_get_customer_name](123) AS [customer_name];

-- Inline `RETURNS TABLE` function
CREATE FUNCTION [fn_get_orders]
    (@customer_id INT)
RETURNS TABLE
AS
RETURN (
    SELECT *
    FROM [orders]
    WHERE [customer_id] = @customer_id
);

-- Calling an inline `RETURNS TABLE` function
SELECT * FROM [fn_get_orders](123);

-- Multi-statement `RETURNS TABLE` function
CREATE FUNCTION [fn_get_orders]
    (@customer_id INT)
RETURNS @orders TABLE (
    [order_id] INT,
    [order_date] DATETIME,
    [total_amount] DECIMAL(10, 2)
)
AS
BEGIN
    INSERT INTO @orders
    SELECT [order_id], [order_date], [total_amount]
    FROM [orders]
    WHERE [customer_id] = @customer_id;
    RETURN;
END

Triggers

Triggers are a special type of stored procedure that are automatically executed when certain events occur in the database.

-- `CREATE TRIGGER`
CREATE TRIGGER [trg_orders_insert]
ON [orders]
AFTER INSERT
AS
BEGIN
    PRINT 'A new order has been inserted.';
END;

-- `INSTEAD OF`
CREATE TRIGGER [trg_orders_update]
ON [orders]
INSTEAD OF UPDATE
AS
BEGIN
    PRINT 'An order has been updated.';
    PRINT 'The update has been ignored.';
END;

-- `inserted` and `deleted` tables
CREATE TRIGGER [trg_orders_update]
ON [orders]
AFTER UPDATE
AS
BEGIN
    DECLARE @order_id INT;
    SELECT @order_id = [order_id] FROM [inserted];
    PRINT 'An order has been updated.';
    PRINT 'The order ID is ' + CAST(@order_id AS NVARCHAR(10));
END;

Cursors

A cursor in SQL Server is a database object used to retrieve and manipulate data row by row, allowing for sequential access to query results.

-- Declaring a cursor
DECLARE [customer_cursor] CURSOR FOR
SELECT [customer_name], [email]
FROM [customers]
WHERE [last_purchase_date] >= DATEADD(MONTH, -1, GETDATE());

-- Opening a cursor: before fetching rows, the cursor must be opened
OPEN [customer_cursor];

-- Fetching rows
DECLARE @customer_name NVARCHAR(50);
DECLARE @email NVARCHAR(50);
FETCH NEXT FROM [customer_cursor] INTO @customer_name, @email;

-- Closing a cursor & deallocating resources
CLOSE [customer_cursor];
DEALLOCATE [customer_cursor];

Security

Views

Views are virtual tables that are based on the result of a SELECT statement. They are used to simplify complex queries and to hide the complexity of the underlying tables.

-- `CREATE VIEW`
CREATE VIEW [v_users] AS
SELECT [id], [email], [created_at]
FROM [users]
WHERE [is_active] = 1;

-- `SELECT` from a view
SELECT * FROM [v_users];

-- Pass-through `UPDATE` & `DELETE`
-- (The view must be "simple" and directly "computed" from the underlying tables)
UPDATE [v_users]
SET [email] = 'sample@example.com'
WHERE [id] = 123;

-- `WITH CHECK OPTION`
CREATE VIEW [v_active_users] AS
SELECT [id], [email], [created_at]
FROM [users]
WHERE [is_active] = 1
-- this will only allow pass-though modifications that is visible in view
WITH CHECK OPTION;

Schema

A scheme in SQL Server refers is a way to organize database objects, such as tables, views, procedures, and functions, into logical groups.

-- Creating a schema
CREATE SCHEMA [security];

-- Creating a table in a schema
CREATE TABLE [security].[users] (
    [id] INT,
    [email] NVARCHAR(50),
    [created_at] DATETIME
);

Logins, Users & Roles

-- `CREATE LOGIN`: allowing to connect to SQL Server.
CREATE LOGIN [sample_user] WITH PASSWORD = 'password';

-- `CREATE USER`:
-- an identity that can be associated with a login, and can be granted permissions.
CREATE USER [sample_user] FOR LOGIN [sample_user];  -- associate with a login

-- `CREATE ROLE`:
-- a group of users, and can be granted permissions.
CREATE ROLE [sample_role];
CREATE ROLE [sample_role] AUTHORIZATION [sample_user];  -- owned by a user

Permissions

-- `GRANT`: gives a permission to a user or role.
GRANT SELECT ON [users] TO [sample_role];

-- `REVOKE`: removes a previously granted permission.
REVOKE UPDATE ON [users] FROM [sample_role];

-- `DENY`: explicitly denies a permission to a user or role.
DENY INSERT ON [users] TO [sample_user];

-- `WITH GRANT OPTION`: allows the user to grant the permission to others.
GRANT SELECT ON [users] TO [sample_role] WITH GRANT OPTION;

SurrealDB

SurrealDB is document database that supports multiple mental models, including document, graph, and table. It has a SQL-like query language named SurrealQL that enables advanced querying and data manipulation.

Features

  • Multi-model database
  • ACID compliant

Basics

CRUD

Create

-- record id field (`id`) is defined by default
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name ON TABLE person TYPE string;

INSERT INTO person (name) VALUES ("John"), ("Jane");
INSERT INTO person { name: "John" };
INSERT INTO person [{ name: "John" }, { name: "Jane" }];
CREATE person SET name = "John";

DEFINE INDEX idx_name ON TABLE person COLUMNS name;

Read

SELECT * FROM person;

SELECT name
FROM person
WHERE name = "Jane";

SELECT count() AS person_count
FROM person
GROUP ALL;

Update

DEFINE FIELD last_name ON TABLE person TYPE string;

UPDATE person
SET last_name = "Doe"
WHERE name = "Jane";

REMOVE FIELD last_name ON TABLE person;

Delete

DELETE person WHERE name = "Jane";

REMOVE TABLE person;

Using Namespaces/Databases

USE NS test; -- Switch to the 'test' Namespace
USE DB test; -- Switch to the 'test' Database

-- Switch to the 'test' Namespace and 'test' Database
USE NS test DB test;

Data Relationships

This page is INCOMPLETE

This page is incomplete and is still being written.

In SurrealDB, data relationships can be model in three ways:

  • embedded record: storing a document within another record field, sacrificing type safety and referential integrity.
  • record link: storing a reference to another record, sacrificing referential integrity.
  • graph relation:
-- embedded record
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name ON TABLE person TYPE string;
DEFINE FIELD address ON TABLE person FLEXIBLE TYPE object; -- flexible typed document
INSERT INTO person:john {
    name: "John",
    address: { street: "123 Main St", city: "City" }
};

-- record link
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name ON TABLE person TYPE string;
DEFINE FIELD address ON TABLE person TYPE record<address>; -- record link to 'address' table
DEFINE TABLE address SCHEMALESS;
INSERT INTO address:john_addr { street: "123 Main St", city: "City" };
INSERT INTO person:john {
    name: "John",
    address: address:john_addr
};

-- graph relation

Querying

This page is INCOMPLETE

This page is incomplete and is still being written.

WHERE Condition


Refining Results

-- `ONLY`

Traversing Relations

-- doc notation: embedded record & link
SELECT
    name,
    address.city,
    address.street
FROM person;

-- array notation: graph relation

Tmux

Tmux is a terminal multiplexer, enabling a number of terminals to be created, accessed, and controlled from a single screen.

Command Cheatsheet

  • tmux: Creates a unnamed session
    • tmux new -s <id>: Create a named session
  • tmux a: Attach to most recent session
    • tmux a -t <id>: Attach to specified session
  • tmux ls: List all sessions
  • tmux kill-session <id>: Kill specified session

Keybind Cheatsheet

Ctrl + b: Prefix key.

  • Session Management
    • d: Detach
  • Pane
    • %: Split horizontal
    • ": Split vertical
    • direction arrow: Switch pane
    • q: Quick jump to pane
    • Ctrl + direction arrow: Resize
    • Alt + direction arrow: Aggressive Resize
    • Alt + number: Apply layout
    • x: Kill pane
    • ,: Rename pane
  • Window
    • c: Create new window
    • w: Window List
    • n: Next window
    • &: Kill window
    • $: Rename window
  • Copy mode
    • [: Enter copy mode
    • (copy mode) space: starts copy
    • (copy mode) enter: ends copy

Resources

NeoVim

NeoVim is a modernized version of Vim. It's a fork of Vim, with the goal of improving the codebase and adding new features.

Cheatsheet

  • substitute:
    • :s/old/new/: substitute the first occurrence of old with new in the current line.
    • :s/old/new/g: substitute all occurrences of old with new in the current line.
    • :#,#s/old/new/g: substitute all occurrences of old with new in the range of lines.
    • :%s/old/new/g: substitute all occurrences of old with new in the entire file.

Plugins

  • lazy.nvim: A modern plugin manager for Neovim.
  • telescope.nvim: A highly extendable fuzzy finder.
  • neo-tree.nvim: Easily manage the file system and other tree like structures.
  • trouble.nvim: A pretty list for showing diagnostics, references, telescope results, quickfix and location lists.
  • undotree.vim: The undo history visualizer for Vim.
  • todo-comments.nvim: Highlight, list and search todo comments in your projects.

Resources

  • dotfyle: Discover and share Neovim plugins.
  • kickstart.nvim: A launch point for your personal nvim configuration
  • LazyVim: A flexible, battery included Neovim distribution.

Git

Git is a distributed version control system. It is a tool to manage source code history. It is designed to handle small to very large projects with speed and efficiency.

Basics

Git Repository

Git repository is a collection of files and folders that are tracked by Git. It is a directory that contains the .git directory. The .git directory is where Git stores the metadata and object database for the project.

# Create a new repository
git init

A newly created repository looks like this:

.
└── .git
    ├── branches
    ├── config
    ├── description
    ├── HEAD
    ├── hooks
    │   ├── applypatch-msg.sample
    │   ├── commit-msg.sample
    │   ├── fsmonitor-watchman.sample
    │   ├── post-update.sample
    │   ├── pre-applypatch.sample
    │   ├── pre-commit.sample
    │   ├── pre-merge-commit.sample
    │   ├── prepare-commit-msg.sample
    │   ├── pre-push.sample
    │   ├── pre-rebase.sample
    │   ├── pre-receive.sample
    │   ├── push-to-checkout.sample
    │   └── update.sample
    ├── info
    │   └── exclude
    ├── objects
    │   ├── info
    │   └── pack
    └── refs
        ├── heads
        └── tags

Recording Changes

Each file in the working directory can be tracked or untracked, and tracked files can be unmodified, modified, or staged. The staged files can be committed to the repository.

# Check the status of the working directory
git status

# Add a file to the staging area
git add <file>

# Commit the staged changes
git commit -m "commit message"

# Show the changes between the working directory and the staging area
git diff

# View the commit history
git log

It is possible to tell git to ignore files by creating a .gitignore file. Its syntax is simple: each line contains a pattern for files to ignore.

# https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository
# ignore a file named `ignore-me.txt`
ignore-me.txt

# ignore all `.a` files
*.a

# but do track `lib.a`, even though you're ignoring `.a` files above
!lib.a

# only ignore the `TODO` file in the current directory, not `sub-dir/TODO`
/TODO

# ignore all files in any directory named `build/`
build/

# ignore `doc/notes.txt`, but not `doc/server/arch.txt`
doc/*.txt

# ignore all `.pdf` files in the `doc/` directory and any of its subdirectories
doc/**/*.pdf

We can also explicitly remove a file from the staging area or tracked files, or discard changes in the working directory.

# Restore a file from the staged area
# https://stackoverflow.com/a/65434709
git restore --staged <file>
git rm --cached

# Remove files from the working tree (tracked files)
git rm <file>

# Discard changes in the working directory
git restore <file>

# Move or rename a file
git mv <old-file> <new-file>

Tagging

Git can tag a specific point in a repo' history as being important.

# List existing tags
git tag

# List tags that matches "v1.8.5*"
git tag -l "v1.8.5*"

# Create a annotated tag v1.0 with message "First stable release"
git tag -a v1.0 -m "First stable release"

# Create a lightweight tag v1.4
git tag v1.4

# Tagging previous commit
git tag -a v1.2 9fceb02

# Display tag information
git show v1.4

# Deleting tags
git tag -d v1.2

# Pushing all tags to remote
git push origin --tags

# Delete a remote tag
git push origin --delete v1.2

CMake

CMake is a cross-platform free and open-source software tool for managing the build process of software using a compiler-independent method.

Basics

Directory Structure

CMake is a build system generator. It generates build files in the specified directory, usually build/, from the CMakeLists.txt file in the project directory (top-level directory).

project_dir/
├── build/
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   └── ...
│   ├── cmake_install.cmake
│   └── Makefile
├── ...
└── CMakeLists.txt

Build & Install Binary

cmake_minimum_required(VERSION 3.22.1)
project(actual_project_name)

# Build executable
add_executable(executable_name main.cpp)

# Install binary to PATH
install(TARGETS executable_name DESTINATION bin)

Build & Install Library

cmake_minimum_required(VERSION 3.22.1)
project(actual_project_name)

# Build library
add_library(library_name lib.cpp)

# Install library to INCLUDE
install(TARGETS library_name DESTINATION lib)

Linking Library

# Linking system installed library
target_link_libraries(target_name library_name)

# Linking library by path
target_link_directories(target_name PRIVATE path/to/the/library/)
target_link_libraries(target_name library_name)

Resources

.vimrc

syntax on

set number
set ruler
set cursorline
set mouse=a

set hlsearch
set incsearch
set showcmd

set clipboard=unnamedplus

set smartindent
set autoindent
set tabstop=4
set softtabstop=4
set shiftwidth=4
set expandtab

inoremap { {}<ESC>i

.bashrc

# Shell prompt customize
PROMPT_COMMAND=__prompt_command

__prompt_command() {
    local last_cmd_code="$?" # Need to be the first

    # Definition of colors
    local no_color='\[\e[0m\]'
    
    local st_bold='\[\e[1m\]'
    local st_underln='\[\e[4m\]'
    local st_reverse='\[\e[7m\]'
    
    local fg_black='\[\e[30m\]'
    local fg_red='\[\e[31m\]'
    local fg_green='\[\e[32m\]'
    local fg_yellow='\[\e[33m\]'
    local fg_blue='\[\e[34m\]'
    local fg_purple='\[\e[35m\]'
    local fg_cyan='\[\e[36m\]'
    local fg_white='\[\e[37m\]'
    
    local bg_black='\[\e[40m\]'
    local bg_red='\[\e[41m\]'
    local bg_green='\[\e[42m\]'
    local bg_yellow='\[\e[43m\]'
    local bg_blue='\[\e[44m\]'
    local bg_purple='\[\e[45m\]'
    local bg_cyan='\[\e[46m\]'
    local bg_white='\[\e[47m\]'
    
    # Initialize prompt
    PS1="\n${fg_blue}█${no_color}${bg_blue}${st_bold}host${no_color}${fg_blue}█▓▒░${no_color}\n"
    PS2="${fg_blue}${st_bold}∙${no_color} "

    # Current working directory
    PS1+="${fg_cyan}${st_bold}\w${no_color}"
    PS1+="\n"

    # Last command status & prompt arrow
    if [ $last_cmd_code != 0 ]; then
        PS1+="${fg_red}${st_bold}❯${no_color}"
    else
        PS1+="${fg_green}${st_bold}❯${no_color}"
    fi
    PS1+=" "
}

# Quality of life aliases
alias l="ls -alF"
alias ll="ls -lF"

.tmux.conf

# Quality of life
set -g mouse on
setw -g mode-keys vi

# Enable color modes
set -g default-terminal "screen-256color"
set-option -ga terminal-overrides ",xterm-256color:Tc"

# Disable key escape
set -sg escape-time 0

# Quality of life
set -g mouse on
setw -g mode-keys vi
setw -g history-limit 10000000

# Easy reload config file
bind r source-file ~/.tmux.conf

# Move around panes with hjkl
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

########## Styling ##########

# Pane border color
set-option -g pane-active-border-style fg=colour213
set-option -g pane-border-style fg=colour239

# Pane number display
set-option -g display-panes-active-colour colour201
set-option -g display-panes-colour colour189

# Clock
set-window-option -g clock-mode-colour colour207

# Enable status bar
set-option -g status "on"

# Status bar basic setting
set -g status-interval 1
set -g status-left-length 20
set -g status-right-length 50

# [win/cur{/}tab/----------/time]
set-window-option -g window-status-separator ""

# [win/cur/tab/{----------}/time]
set-option -g status-style bg=colour0,fg=colour255

# [{win}/cur/tab/----------/time]
set-option -g status-left "\
#[bg=\#5e5faf, fg=colour189]#{?client_prefix,#[bg=colour167],}  #{?window_zoomed_flag,󰻿 ,}#S\
#[bg=colour0, fg=\#5e5faf]#{?client_prefix,#[fg=colour167],}◤"

# [win/{cur}/tab/----------/time]
set-window-option -g window-status-current-format "\
#[bg=colour0, fg=\#5e5faf]◢\
#[bg=\#5e5faf, fg=colour189]#I\
#[bg=colour189, fg=\#5e5faf]◤\
#[bg=colour189, fg=\#5e5faf, bold]#W\
#[bg=colour0, fg=colour189]◤"

# [win/cur/{tab}/----------/time]
set-window-option -g window-status-format "\
#[bg=colour0, fg=#5e5faf]◢\
#[bg=#5e5faf, fg=colour189]#I\
#[bg=\#4e4e4e, fg=#5e5faf]◤\
#[bg=\#4e4e4e, fg=colour189]#W\
#[bg=colour0, fg=\#4e4e4e]◤"

# [win/cur/tab/----------/{time}]
set-option -g status-right "\
#[bg=colour0, fg=\#5e5faf]◢\
#[bg=\#5e5faf, fg=colour189]%d %b %Y %H:%M "

# Window list color
set-window-option -g mode-style bg=#5e5faf,fg=colour189