Header logo.
small hallucinations
homeyearstagsaboutrss

A lightweight Swift workflow with Zed

I played with Swift over the weekend, and Xcode felt sluggish from time to time on my machine. I also had to ask Codex for help quite often just to navigate Xcode's UI.

When I tried coding with Zed, it failed to recognize structs, variables, and the like defined in other files in the project.

Quick googling led me to a very helpful blog post, “How I Got Zed Editor Working with Swift Projects,” and this GitHub repository: xcode-build-server.

Following the instructions in the blog post, I installed xcode-build-server, generated a buildServer.json file, and configured Zed accordingly.

Zed could now recognize the project's symbols.

I wanted to use the simulator without opening Xcode. After some back and forth, I got hot reload working too. This is what I did:

  • Navigate to the project directory and run open -a Simulator.
  • Run xcodebuild to build the codebase as an .app bundle.
  • Run xcrun to install and launch the built bundle in the simulator.

I've shared the Bash script I'm using in a GitHub Gist. Update values such as the project name, scheme, and directories, then install fswatch to watch for file changes. Now you can run this command and have fun:

open -a Simulator && dev.sh

Your ideas are wrong (and your instincts aren't)

I've been reading Life at the Speed of Play by Mark Pincus, founder of Zynga, the social gaming company behind Farmville.

Here are a few takeaways I found fascinating:

  1. Instincts and ideas are two different things. Your instincts are almost always right. Your ideas are often wrong. Recognizing this distinction makes it much easier to abandon failing ideas without giving up the core insight. Your instinct tells you where the market is going or which problems are worth solving. Your ideas are a specific way to build a product or find a solution. Ideas tend to be wrong because they involve countless permutations of details. Which brings us to:
  2. Proven, better, new. When you follow an instinct, find which features are proven to work in competing products and copy them. Identify which details in existing products you can objectively make better. Introducing new features or ways of interaction is risky. That's why:
  3. A “minimum idea state” is much more useful than a “minimum viable product”. MVPs are too heavy. Instead, build out just enough of your idea to rapidly gather data and test the waters.

Alpaca WebSocket authentication

I was trying to stream market data from Alpaca's WebSocket endpoint.

Alpaca's documentation lists the following three steps to receive market data from the stream.

  1. Connect to the endpoint.
  2. Send an authentication frame.
  3. Send a subscription frame.

Step 2 proved to be tricky.

The library I'm using is WebSockex. Per best practice, you should either send the auth frame in the handle_connect function or reply with an auth frame inside handle_frame when the “connected” message is received. In this particular case, both would fail or time out.

The handle_connect function is called immediately after the WebSocket connection is established. Inside handle_connect, you cannot return {:reply, auth_frame, state}. You can only return {:ok, state} or {:close, state}.

If you want to send the auth frame to the server, you'll have to use WebSockex.send_frame(pid, frame). ChatGPT keeps telling me to do the following, although it invariably causes a CallingSelfError. Apparently, you cannot just ask yourself (the same process) to send a frame.

1def handle_connect(_conn, state) do
2  # This will cause an error.
3  WebSockex.send_frame(self(), {:text, auth_frame})
4  {:ok, state}
5end

But in handle_frame function, you can return a tuple like this: {:reply, auth_frame, state}. The only thing you need to do is pattern match incoming frames. When you see "connected", return that reply tuple. This should have worked if it didn't time out. (Curiously, replying with a subscription frame worked.)

In the end, I did the following: I sent the auth frame inside the start_link function.

 1  def start_link(state) do
 2    connected = WebSockex.start_link(@uri, __MODULE__, state)
 3
 4    case connected do
 5      {:ok, pid} ->
 6        WebSockex.send_frame(pid, {:text, auth_json})
 7        {:ok, pid}
 8
 9      {:error, reason} ->
10        Logger.info(reason)
11    end
12  end

Reading “Elixir in Action”

I finished reading  "Elixir in  Action." It's a great book that explains complex and novel (at least to me) ideas in simple and clear language.

In each  chapter, the author illustrates a set of new concepts with simple code examples and occasional exercises. As you go deeper  into the book, you'll clearly see how new concepts are built on top of the simpler concepts introduced earlier. It feels like recursion.

At its core, Elixir is very, very simple. You define small functions that perform specific tasks. You can specify the conditions under which these functions run using the when clause and pattern matching of parameters. You then build larger functions by iteratively composing smaller functions.

The notions of pure functions and immutability go hand in hand. They  may  seem  like  constraints on the  surface, until you  realize they have liberated you from asking  yourself,  "Am I manipulating this list in place or  not?" or  "Is this pointer mutable or  not?" No, you didn't change the list in place. No, it's not  mutable, and you don't need to worry about pointers.

The most basic way to coordinate processes is by sending messages.  A process quits when it has received and processed a message. To persist  a process, you recursively call the function that receives messages. There are complex features, but at the core are these simple notions. This makes it much easier to think about than  goroutines and  channels.

Go and read it.

Two more learning tricks

1: Ask your coding agent to generate a minimal, runnable code snippet to demonstrate a concept.

2: After vibe-coding, ask your coding agent to summarize the changes it has made and create Anki flashcards.

Using Elixir Observer on a Mac

tl;dr

On a Mac, if you want to use the Observer GUI for Elixir/Erlang, just use the Homebrew distribution of Erlang. It comes with wxWidgets correctly pre-configured.

Erlang distributions installed via asdf do not support Observer out of the box. When you install wxWidgets via homebrew separately, Erlang will complain that it was not compiled with the --enable-compat30 flag, making it incompatible. (Do not even think about compiling from the source code on your own.)

context

Elixir/Erlang ships with a GUI observer that shows how processes interact. This seems very helpful when you have a complex mix of Supervisors, GenServers, and processes.

For some reason, I couldn't use it on my Mac. The error message mentioned something about wx. I decided to look into it and came across this blog post. Following the instructions, I tried installing wxwidgets using Homebrew. The installation failed because a few dependencies were the x86_64 version, but they should have been arm64.

It took me some time to realize my Homebrew was, for some reason, for x86_64. I don't recall how that happened, but that explained why I remember occasionally seeing architecture mismatches when adding dependencies. I also had a version of Elixir installed from MacPorts. I removed MacPorts, removed Homebrew. Reinstalled Homebrew for the correct architecture.

Now with the right version of Homebrew in place, I installed wxWidgets from Homebrew again, carefully set environment variables including KERL_CONFIGURE_OPTIONS="--with-wx", and tried installing Erlang and Elixir using the ASDF version manager. Erlang failed to install with an error message that said something like: “wxWidgets was not compiled with --enable-compat30, wx will NOT be useable”.

So, I tried building wxWdigets from source code. It was ChatGPT who suggested all the steps: clone the repo and configure it with a few flags, notably --enable-compat30, which is exactly what the last error message indicated.

The configuration script then noticed missing submodules, which I downloaded following the instructions given by the script. I repeated this step a few times for a handful of missing submodules.

When I finally got the compilation working, I was quickly faced with an error message: fp.h was missing, which was required by the built-in libpng. ChatGPT told me to install libpng via Homebrew and pass a parameter to the configuration script. I did so and reran the configuration script. This then happened again with libtiff.

I said, “Argh. I just wanted to use :observe,” as I was about to give up.

At that very moment, ChatGPT finally brought this up, as if it were an afterthought.

Yeah, this rabbit hole is brutal. If your real goal is just :observer, you don’t actually need to hand-build wxWidgets at all. ... Use Homebrew’s Erlang (it comes with the GUI bits working on macOS).

Bruh...

Why didn't you tell me this before I attempted to compile C++ on my own?

Run migrations while deploying a Phoenix app on Railway

I built a small app using Phoenix LiveView and deployed it on Railway. I  encountered some  minor roadblocks. Here is what I did.  Hopefully, this  will be useful.

Deployment

  1. Create a project from my GitHub repository. At this stage, Railway would decide it's an Elixir project and automatically configured deployment workflow.

  2. Right-click on the Railway project canvas, then select “Database” and choose “Add PostgreSQL.”

  3. Set up environment variables as instructed in this section of the documentation. This section lists SECRET_KEY_BASE, LANG, LC_CTYPE, DATABASE_URL, and ECTO_IPV6.

  • Interestingly, I've set LANG and LC_CTYPE to en_US.UTF-8. But I'm still seeing this error: LC_ALL: cannot change locale (en_US.UTF-8). It seems harmless for now.
  • This list also seems incomplete. To make a Phoenix LiveView app work, you need to add the following variables: PHX_SERVER and PHX_HOST. (You can also check runtime.exs for these settings.)
    • Set PHX_SERVER to true.
    • Set PHX_HOST to my-app.up.railway.app. (I'll use my-app as a placeholder name.)
    • If you don't set PHX_SERVER, you'll see this error message in the logs: Configuration :server was not enabled for HaveYourBackWeb.Endpoint, http/https services won't start.
    • If you don't set PHX_HOST correctly, incoming WebSocket requests will be rejected.

Migration

After completing the steps above, the Phoenix app is running and successfully connects to the hosted Postgres database. However, the database remained empty. It turned out that no part of the building and deploying process explicitly ran the migrations.

After some trial and error, I got the migrations working by doing the following:

  1. In your codebase, create a file at lib/my_app/release.ex with a MyApp.Release module, and define a function to run migrations:
 1defmodule MyApp.Release do
 2  @app :my_app
 3
 4  def migrate do
 5    Application.load(@app)
 6    for repo <- Application.fetch_env!(@app, :ecto_repos) do
 7      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
 8    end
 9  end
10end
  1. In lib/my_app/application.ex, add a conditional to run migrations in production.
 1defmodule MyApp.Application do
 2  use Application
 3
 4  @impl true
 5  def start(_type, _args) do
 6
 7    # BEGIN ADDED
 8    if Application.get_env(:my_app, :sql_sandbox) == false do
 9      MyApp.Release.migrate()
10    end
11    # END ADDED
12
13    # Existing code.
14  end
  1. Then, in mix.exs, add the following configuration:
 1defmodule HaveYourBack.MixProject do
 2  use Mix.Project
 3
 4  def project do
 5    [
 6      app: :my_app,
 7      # Existing code.
 8      deps: deps(),
 9
10      # BEGIN ADDED
11      releases: [
12        my_app: [
13          include_executables_for: [:unix],
14          applications: [runtime_tools: :permanent]
15        ]
16      ]
17      # END ADDED
18    ]
19  end
20
21  # Existing code.
  1. Finally, add this command to the Custom Start Command field under Settings -> Deploy.
/app/_build/prod/rel/my-app/bin/my-app eval "MyApp.Release.migrate" && \
/app/_build/prod/rel/my-app/bin/my-app start