HomeAboutPostsTagsProjectsRSS

Updated
Words179
TagsRead1 minute

after reading When An Alias Should Actually Be An Abbr | sean henderson I completely abandon using alias in fish shell, use abbr ever since.

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

Updated
Words785
TagsRead2 minutes

Records in Gleam: Comparison and Uniqueness

Record Comparison

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:

  1. Uniqueness: Each reference is guaranteed to be unique within an Erlang node (and even across connected nodes).
  2. Lightweight: References are very memory-efficient.
  3. Unguessable: They can’t be forged or guessed, which can be useful for security in some contexts.
  4. 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.

Example

import gleam/erlang

pub type TensorId =
  erlang.Reference

pub type Tensor {
  ScalarTensor(value: Float, id: TensorId)
  ListTensor(List(Tensor))
}

pub fn create_scalar_tensor(value: Float) -> Tensor {
  ScalarTensor(value, erlang.make_reference())
}

pub fn create_list_tensor(tensors: List(Tensor)) -> Tensor {
  ListTensor(tensors)
}

pub fn tensor_id(tensor: Tensor) -> TensorId {
  case tensor {
    ScalarTensor(_, id) -> id
    ListTensor(_) -> erlang.make_reference()
  }
}

pub fn tensor_equal(a: Tensor, b: Tensor) -> Bool {
  tensor_id(a) == tensor_id(b)
}
import gleam/dict

pub type GradientMap =
  dict.Dict(TensorId, Float)

pub fn create_gradient_map() -> GradientMap {
  dict.new()
}

pub fn set_gradient(map: GradientMap, tensor: Tensor, gradient: Float) -> GradientMap {
  dict.insert(map, tensor_id(tensor), gradient)
}

pub fn get_gradient(map: GradientMap, tensor: Tensor) -> Result(Float, Nil) {
  dict.get(map, tensor_id(tensor))
}

Updated
Words325
TagsRead2 minutes

This is my thought about the layers of computation in Machine Learning while porting The Little Learner to Gleam .

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Updated
Words220
TagsRead1 minute

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.

I just discovered [icloud-nosync]( GitHub - nicolodiamante/icloud-nosync: Take control of your iCloud Drive files with iCloud NoSync—the ultimate utility for effortless sync and management! Get the edge you need to stay organised and up–to–date. ), which can prevent the .git folder from syncing. The way it works is by replacing the actual folder with a symbolic link and renaming the folder with a .tmp extension, which prevents the folder from syncing to iCloud.

Updated
Words908
TagsRead3 minutes

typical usage of result.unwrap and result.or

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.

Typical usage:

import gleam/result

pub fn get_user_name(user_id: Int) -> Result(String, Nil) {
  // Simulated user lookup
  case user_id {
    1 -> Ok("Alice")
    2 -> Ok("Bob")
    _ -> Error(Nil)
  }
}

pub fn greet_user(user_id: Int) -> String {
  let name = get_user_name(user_id)
    |> result.unwrap("Guest")

  "Hello, " <> name
}

// Usage:
pub fn main() {
  io.println(greet_user(1))  // Prints: "Hello, Alice"
  io.println(greet_user(3))  // Prints: "Hello, Guest"
}

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:

import gleam/result

pub fn get_config_from_file() -> Result(Config, String) {
  // Simulated file read
  Error("File not found")
}

pub fn get_default_config() -> Result(Config, String) {
  // Return a default configuration
  Ok(Config(..))
}

pub fn get_config() -> Result(Config, String) {
  get_config_from_file()
  |> result.or(get_default_config())
}

// Usage:
pub fn main() {
  case get_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.

Updated
Words204
TagsRead1 minute

Rust compilation failed on Apple Silicon Macbook

ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Fix: [ld: symbol(s) not found for architecture x86_64 · Issue #15 · rochacbruno/rust-python-example · GitHub]( ld: symbol(s) not found for architecture x86_64 · Issue #15 · rochacbruno/rust-python-example · GitHub )

switch to rust nightly

rustup toolchain install nightly
cd <path/to/project>
rustup override set nightly

cargo rustc --release -- -C link-arg=-undefined -C link-arg=dynamic_lookup

Updated
Words1216
TagsRead2 minutes

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-libvterm
function vterm_printf
    if begin
            [ -n "$TMUX" ]; and string match -q -r "screen|tmux" "$TERM"
        end
        # tell tmux to pass the escape sequences through
        printf "\ePtmux;\e\e]%s\007\e\\" "$argv"
    else if string match -q -- "screen*" "$TERM"
        # GNU screen (screen, screen-256color, screen-256color-bce)
        printf "\eP\e]%s\007\e\\" "$argv"
    else
        printf "\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 $argv
        set -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 name
    function 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
	(defun vterm-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-p win)
	                                               (eq 'vterm-mode (buffer-local-value 'major-mode (window-buffer win))))
	                                      (throw 'found win)))))
	          new-file-window)
	      (when current-vterm-window
	        ;; Open file in a new window from current VTerm window
	        (select-window current-vterm-window)
	        (setq new-file-window (split-window-below)) ; Adjust split direction to preference
	        (set-window-buffer new-file-window (find-file-noselect file))
	        ;; Delete all other windows except for VTerm and the new file window
	        (mapc (lambda (win)
	                (unless (or (eq win current-vterm-window)
	                            (eq win new-file-window))
	                  (delete-window win)))
	              (window-list))
	        (select-window new-file-window))))
	
	  (add-to-list 'vterm-eval-cmds '("vterm-edit-file" vterm-edit-file)))

Updated
Words595
TagsRead1 minute

Recently discovered Gleam language, and totally fell in love with it shortly!

Here is how you can setup Gleam in doom-emacs

packages.el

We just need the gleam-ts-mode.el, do not download gleam-mode.el as when it gets compiled it requires tree-sitter which causes problem.

(package! gleam-ts-mode
  :recipe (:host github
           :repo "gleam-lang/gleam-mode"
           :branch "main"

           :files ("gleam-ts-*.el")))

config.el

(use-package! gleam-ts-mode
  :config
  ;; setup formatter to be used by `SPC c f`
  (after! apheleia
    (setf (alist-get 'gleam-ts-mode apheleia-mode-alist) 'gleam)
    (setf (alist-get 'gleam apheleia-formatters) '("gleam" "format" "--stdin"))))

(after! treesit
  (add-to-list 'auto-mode-alist '("\\.gleam$" . gleam-ts-mode)))

(after! gleam-ts-mode
  (unless (treesit-language-available-p 'gleam)
    ;; compile the treesit grammar file the first time
    (gleam-ts-install-grammar)))

hack

If you, like me, use Treesitter grammar files from Nix, the tree-sitter subdirectory within the directory specified by user-emacs-directory is linked to Nix’s read-only filesystem, meaning gleam-ts-install-grammar is unable to install grammar files there.

Here’s how you can adjust treesit-extra-load-path and install the grammar file.

(after! gleam-ts-mode
  (setq treesit-extra-load-path (list (expand-file-name "~/.local/tree-sitter/")))
  (unless (treesit-language-available-p 'gleam)
    ;; hack: change `out-dir' when install language-grammar'
    (let ((orig-treesit--install-language-grammar-1 (symbol-function 'treesit--install-language-grammar-1)))
      (cl-letf (((symbol-function 'treesit--install-language-grammar-1)
                 (lambda (out-dir lang url)
                   (funcall orig-treesit--install-language-grammar-1
                            "~/.local/tree-sitter/" lang url))))
        (gleam-ts-install-grammar)))))

Updated
Words205
TagsRead1 minute

Just learned that using [[Emacs]], press C-\ to set input method to TeX, then input Greek symbols will be very easy, for example type \theta will display θ

Here are some frequently used symbols when writing math equations.

symbol
\alphaα
\thetaθ
\delta
Δ
\nabla

Updated
Words590
TagsRead2 minutes

In macOS, I’ve configured a variety of shortcuts to automate tasks, for example:

  • Appending selected text to a specified Obsidian note.
  • Opening a prompt menu and sharing the selected text with ChatGPT to obtain results.
  • Extracting text from an image to read in an editor.
  • And many others.

I frequently use these actions on both my iPhone and Mac. However, on macOS, I find myself having to manually navigate through the Services menu to activate these actions, which tends to slow down my workflow.

To streamline this process, I’ve developed a script that automatically triggers a Services menu item whenever I select text and press Command + Right Click. This enhancement significantly speeds up my interaction with macOS, making my workflow more efficient.

[[Hammerspoon]] script

-- Ensure the listener is global to avoid garbage collection issues
MouseListener = hs.eventtap.new({ hs.eventtap.event.types.rightMouseDown }, function(e)
  local buttonPressed = e:getProperty(hs.eventtap.event.properties.mouseEventButtonNumber)
  local cmdPressed = e:getFlags().cmd

  if cmdPressed == true then
    -- AppleScript to open the Services menu
    -- https://stackoverflow.com/a/59330902/22903883
    hs.osascript.applescript([[
tell application "System Events"
    set appName to item 1 of (get name of processes whose frontmost is true)
    tell (process 1 where frontmost is true)
        tell menu bar 1
            tell menu bar item appName
                tell menu appName
                     tell menu item "Services"
                          tell menu "Services"
                            click menu item "GPT: Share"
                          end tell
                     end tell
                end tell
            end tell
        end tell
    end tell
end tell]])
    return true -- Consume the right-right-click
  end

  return false
end)

-- Start the event listener
MouseListener:start()