Nowadays I start a research of topic by writing a note on [[Obsidian]]. I use this template for
Introduction - Templater
to insert a link to start the search in Google app.
The language is so straightforward that an experienced programmer can learn it in just a day or two. However, to truly appreciate its simplicity and constraints, one must have prior experience with complex programming languages and substantial coding practice.
focus on concrete problem
It is hard for less experienced developers to appreciate how rarely architecting for future requirements / applications turns out net-positive.
Racket provides layers of abstraction like rename-out to override operators like + - * / > < = (literally can be anything) when exporting module. This is great for building abstraction to teach concepts or to build another new language/DSL, but such flexibility often comes with maintainability costs and cognitive burden.
The Little Learner provides different implementations for tensors. Functions, and operators that appear identical have different meanings across different tensor implementations. Also operators are overridden, when reading the codebase, I often get confused by operators like <, sometimes it compare numbers, other times it compare scalars or tensors, Racket is a dynamic language, without type annotation, checking those functions and operations can be really frustrating and confusing.
While this uniform abstraction layer is beneficial for teaching machine learning concepts, it can be challenging when examining the actual code.
In contrast, Gleam shines with its simplicity and lack of hidden complexities. Everything must be explicitly stated, making the code clean and readable. Additionally, the compiler is smart enough to perform type inference, so you usually don’t need to add type notations for everything.
abbr will expand the command into what it shorts for, before pressing enter to execute it.
For me the biggest advantage is that when searching the shell history there is no hidden complexity, you don’t have to member all the black magic that a short command name stands for.
abbr is the new alias, function for everything else
In [[Gleam]], records are compared by value (deep nested comparison), which can present challenges when using them as dictionary keys, unlike in some other functional languages.
Records are Compared by Value
It’s important to note that Gleam doesn’t have objects in the traditional sense. All data structures, including records, are compared by value. This means that two records with identical field values will be considered equal.
To make a record unique, an additional identifier field is necessary. This approach allows for distinguishing between records that might otherwise have the same content but represent different entities.
Ensuring Uniqueness
Simple Approach: UUID Field
One straightforward method to ensure record uniqueness is to add a UUID field. However, UUID strings can be memory-intensive and cpu-costly.
Improved Approach: Erlang Reference
A more efficient alternative is to use an [[erlang reference]] as a unique identifier for records.
Erlang references are unique identifiers created by the Erlang runtime system. They have several important properties:
Uniqueness: Each reference is guaranteed to be unique within an Erlang node (and even across connected nodes).
Lightweight: References are very memory-efficient.
Unguessable: They can’t be forged or guessed, which can be useful for security in some contexts.
Erlang-specific: They are native to the BEAM VM, so they work well with Gleam, which runs on this VM.
It’s important to note that:
Erlang references are not persistent across program runs. If you need to save and reload your records, you’ll need to implement a serialization strategy.
References are not garbage collected until the object they’re associated with is no longer referenced.
Bit-Level Representation: At the lowest level, data is stored as bits in memory. However, direct manipulation at this level is rarely necessary in modern ML. I learned how flat binary tensors are represented in memory and how to manipulate bit arrays for performing arithmetic operations on tensors.
Arithmetic Operations: Higher-level abstractions allow for operations on tensor objects without manual bit manipulation. These tensors represent multi-dimensional arrays of numbers, which can be integers or floating-point values. A important concept is that how the operations are extended to apply to tensors of certain shapes, rather than just apply to numbers.
Automatic Differentiation: This layer use a dual data structure #(tensor, link) to connect a tensor with its generating function. This structure is crucial for backpropagation, where gradients are computed via the chain rule. Nodes in the graph represent operations, while edges represent tensors flowing between operations.
Differentiable Data and Model Parameters: A tensor, by nature of its operations and structure, is differentiable. Any list or sequence of tensors adhering to the operations defined is also differentiable. At the top of the computation tier, differentiable data (which generally represent model parameters) are passed to gradient descent or other optimization functions. These are updated during training using optimization algorithms based on gradients calculated through the lower layers.
I use a cron job to sync my [[Obsidian]] notes to a private GitHub repository every day. It’s just a simple script to do a git commit && git push, and it works mostly fine. However, the .github directory synced to iCloud significantly slows down the Obsidian startup speed on mobile.
result.unwrap and result.or are both useful functions in Gleam for working with Result types, but they serve different purposes.
result.unwrap
result.unwrap is used to extract the value from a Result, providing a default value if the Result is an Error. It’s typically used when you want to proceed with a default value rather than propagating an error.
In this example, result.unwrap allows us to use a default value (“Guest”) when the user lookup fails, ensuring that we always have a name to greet.
result.or
result.or is used to provide an alternative Result when the first Result is an Error. It’s typically used when you have a fallback operation or value that you want to try if the primary operation fails.
Typical usage:
importgleam/resultpubfnget_config_from_file()->Result(Config,String){// Simulated file read
Error("File not found")}pubfnget_default_config()->Result(Config,String){// Return a default configuration
Ok(Config(..))}pubfnget_config()->Result(Config,String){get_config_from_file()|>result.or(get_default_config())}// Usage:
pubfnmain(){caseget_config(){Ok(config)->io.println("Config loaded")Error(err)->io.println("Failed to load config: "<>err)}}
In this example, result.or allows us to try loading the configuration from a file first, and if that fails, fall back to using a default configuration. The get_config function will only return an Error if both operations fail.
Key differences and when to use each:
Use result.unwrap when you want to extract a value from a Result and have a sensible default to use if it’s an Error. This effectively “throws away” the error information.
Use result.or when you want to try an alternative operation if the first one fails, while still preserving the Result type. This allows you to chain multiple fallback options.
result.unwrap returns the unwrapped value directly, while result.or returns another Result.
result.unwrap is often used at the “edges” of your program where you need to interface with code that doesn’t use Results, while result.or is more commonly used within the “core” logic where you’re still working with Results.
Both functions are valuable tools for error handling in Gleam, and understanding when to use each can lead to more robust and expressive code.
Rust
Same principle can be applied to Rust as the design is very alike.
Here is the most valuable part of my emacs
vTerm
config with fish shell.
add source vterm.fish to your fish shell configuration file
use edit <file> in vTerm buffer will open file at the new window above vTerm buffer, similar to using code <file> in VSCode embedded terminal.
vterm.fish
# https://github.com/akermu/emacs-libvtermfunction vterm_printf
if begin
[ -n "$TMUX"]; and string match -q -r "screen|tmux""$TERM" end
# tell tmux to pass the escape sequences throughprintf"\ePtmux;\e\e]%s\007\e\\""$argv"elseif string match -q -- "screen*""$TERM"# GNU screen (screen, screen-256color, screen-256color-bce)printf"\eP\e]%s\007\e\\""$argv"elseprintf"\e]%s\e\\""$argv" end
end
function vterm_cmd --description 'Run an Emacs command among the ones been defined in vterm-eval-cmds.'set -l vterm_elisp ()for arg in $argvset -a vterm_elisp (printf'"%s" '(string replace -a -r '([\\\\"])''\\\\\\\\$1'$arg)) end
vterm_printf '51;E'(string join ''$vterm_elisp)end
if["$INSIDE_EMACS"= vterm ]set -gx EDITOR code
function clear
vterm_printf "51;Evterm-clear-scrollback" tput clear
end
# used by vterm buffer namefunction fish_title
hostname
echo":" prompt_pwd
end
function vterm_prompt_end --on-variable PWD
vterm_printf '51;A'(whoami)'@'(hostname)':'(pwd) end
function edit
set -q argv[1]; or set argv[1]"." vterm_cmd vterm-edit-file (realpath "$argv") end
end
doom emacs config
config.el
(after!vterm(defunvterm-edit-file(file)"Open a file from vterm in another window, keeping only the vterm window and the new file window."(interactive)(let((current-vterm-window(catch'found(dolist(win(window-list))(when(and(window-live-pwin)(eq'vterm-mode(buffer-local-value'major-mode(window-bufferwin))))(throw'foundwin)))))new-file-window)(whencurrent-vterm-window;; Open file in a new window from current VTerm window(select-windowcurrent-vterm-window)(setqnew-file-window(split-window-below)); Adjust split direction to preference(set-window-buffernew-file-window(find-file-noselectfile));; Delete all other windows except for VTerm and the new file window(mapc(lambda(win)(unless(or(eqwincurrent-vterm-window)(eqwinnew-file-window))(delete-windowwin)))(window-list))(select-windownew-file-window))))(add-to-list'vterm-eval-cmds'("vterm-edit-file"vterm-edit-file)))