Skip to main content

Michael Uloth

TIL - Today I Learned

  1. SELECT DISTINCT outputs a column's unique values

    Here’s how to see all the different values in a column:

    unique-column-values.sql
    SELECT DISTINCT column1, column2, ...columnN 
    FROM table_name;

    I found that helpful today when I was trying to understand the significance of a value by comparing it to the other options.

    Source: Distinct in SQL: A Comprehensive Guide

  2. It's tricky to statically type a "pipe" function in Python

    I wanted a type-safe pipe utility to help me write Python in a more functional style, but unfortunately toolz.pipe and returns.pipelines.flow both output Any.

    Happily, it turns out creating your own pipe mechanism is a one-liner:

    script.py
    from functools import reduce
     
    result = reduce(lambda acc, f: f(acc), (fn1, fn2, fn3), value)

    Which you can reuse by wrapping it in a function:

    fp.py
    from functools import reduce
    from typing import Callable, TypeVar
     
    _A = TypeVar("A")
    _B = TypeVar("B")
     
    def pipe(value: _A, *functions: Callable[[_A], _A]) -> _A:
        """Pass a value through a series of functions that expect one argument of the same type."""
        return reduce(lambda acc, f: f(acc), functions, value)

    And calling it with any number of functions:

    assert pipe("1", int, float, str) == "1.0"
    # => i.e. str(float(int('1')))
    # => i.e. int("1") -> float(1) -> str(1.0) -> "1.0"

    So you can stop thinking up names for throwaway variables like these:

    def str_to_float_str(value: string):
        as_integer = int(value)
        as_float = float(as_integer)
        as_string = str(as_float)
        return as_string
     
    assert str_to_float_str("1") == "1.0"

    Credit to Statically-Typed Functional Lists in Python with Mypy by James Earl Douglas and the returns.pipeline.flow source code for the inspiration.

    Update on Nov 29, 2024

    While the pipe function above with the Callable[[A], A] type hint works fine if every function in the pipeline outputs the same type (A), the example I showed above doesn’t actually work out very well! Mypy notices that some of the functions (int and float) output a different type than we started with (str), so we aren’t actually passing A all the way through.

    After trying a number of workarounds (and getting some good advice on Reddit), I learned that you can either tell Mypy what’s going on by painstakingly articulating every possible overload:

    _A = TypeVar("A")
    _B = TypeVar("B")
    _C = TypeVar("C")
    _D = TypeVar("D")
    _E = TypeVar("E")
     
    @overload
    def pipe(value: _A) -> _A: ...
     
    @overload
    def pipe(value: _A, f1: Callable[[_A], _B]) -> _B: ...
     
    @overload
    def pipe(
        value: _A, 
        f1: Callable[[_A], _B], 
        f2: Callable[[_B], _C]
    ) -> _C: ...
     
    @overload
    def pipe(
        value: _A, 
        f1: Callable[[_A], _B], 
        f2: Callable[[_B], _C], 
        f3: Callable[[_C], _D]
    ) -> _D: ...
     
    @overload
    def pipe(
        value: _A,
        f1: Callable[[_A], _B],
        f2: Callable[[_B], _C],
        f3: Callable[[_C], _D],
        f4: Callable[[_D], _E],
    ) -> _E: ...
     
     
    def pipe(value: Any, *functions: Callable[[Any], Any]) -> Any:
        return reduce(lambda acc, f: f(acc), functions, value)

    Or, you can just use expression, which already does this for you.

    I’m going to do the latter, but this was a fun exercise in the meantime. 😎

  3. An iOS Shortcut can add data to a Google Sheet

    I followed this short guide by Thiago Alves for sending arbitrary data to Google Sheets from iOS via an iOS Shortcut and a Google Form and it worked like a charm.

    In brief, here are the steps:

    1. Create a Google Form
    2. Link it to a Google Sheet
    3. Copy the pre-filled link to your form and edit it to end with &submit=Submit and say formResponse instead of viewform 
    4. Use a “Text” shortcut action to insert your data into that link
    5. Use a “Get contents of URL” action to send your data to your form results sheet

    A response link for a form with one question will look something like this:

    https://docs.google.com/forms/d/e/<form-id>/formResponse?usp=pp_url&entry.<question-id>=<answer>&submit=Submit

    Here’s what my shortcut to save the current URL looks like:

  4. How to query a BigQuery table from Python
    1. Install the SDK:
    pyproject.toml
    dependencies = [
    	"google-cloud-bigquery[bqstorage, pandas]",
    ]
    1. Create a BigQuery client:
    client.py
    from google.cloud import bigquery
     
    bq_client = bigquery.Client(project="project")
    1. Execute your query and store the result:
    query.py
    query = """
    SELECT column_one, column_two
    FROM bq_table
    WHERE column_three = true
    """
     
    df = bq_client.query(query).to_dataframe()
  5. CloudFlare sells domain names at cost

    I thought of a domain I wanted to grab.

    I looked it up on my usual domain registrar (Hover): $20.99

    I looked it up on CloudFlare: $12.99

    Sold.

    (The free security features don’t hurt either.)

  6. Homebrew packages might install dependencies

    I wondered why brew list showed so many packages I didn’t install myself.

    Then, I discovered those are the dependencies of the packages I installed. And the dependencies of those dependencies. And so on.

    Seems kinda obvious now.

    # Which packages and casks are installed?
    brew list
    # Why is <package-name> installed? Which packages are using it?
    brew uses <package-name> --installed
    # Which dependencies came with <package-name>?
    brew deps <package-name> --tree
  7. Shell functions don't need parentheses

    Copilot surprised me by generating a zsh function that looked like this:

    function act {
      # do stuff
    }

    Instead of like this:

    act() {
      # do stuff
    }

    It turns out function parentheses are optional in bash and zsh. Though, using parentheses is more POSIX compliant if that’s relevant for your use case.

    In fact, all of these variations are equivalent:

    function act () { 
      command
    }
     
    function act() { 
      command 
    }
     
    function act { 
      command 
    }
     
    act () { 
      command 
    }
     
    act() { 
      command 
    }
     
    act () command
     
    act() command
  8. You can run shell scripts in tmux.conf
    1. Create a shell script:
    battery.sh
    #!/usr/bin/env bash
     
    echo "" $(pmset -g batt | grep -Eo '[0-9]+%')
    1. Make the file executable:
    chmod +x battery.sh
    1. Call it from within tmux.conf:
    tmux.conf
    set -g status-right "#($HOME/.config/tmux/battery.sh)"