Saturday, August 19, 2023

Calling write-only atoms in Jotai

Every atom in Jotai, when called through its useAtom primitive, returns a tuple of value + setter, just like React’s own useState. In a Jotai write-only atom, however, there is no value, so the canonical call is:

const [_, setVal] = useAtom(myValAtom);

In order to get rid of the verbose underscore, and thus the whole useless tuple, I wrote the following function:

import {WritableAtom} from 'jotai';

/**
 * Syntactic sugar to useAtom() for write-only atoms.
 */
export function useWriteAtom<V, A extends unknown[], R>(a: WritableAtom<V, A, R>) {
	const [_, setFunc] = useAtom(a);
	return setFunc;
}

The atom type was copied straight from Jotai’s TypeScript definitions, so the type inference works seamlessly. The example now can be written as:

const setVal = useWriteAtom(myValAtom);

Saturday, July 29, 2023

Delegating Display/Debug trait implementations

Suppose you want implement Display and Debug traits for your struct, but the output is the same. Instead of copy & paste the implementation, you can delegate the implementation by casting the Self type to the trait type:

impl std::fmt::Debug for MyStruct {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		write!(f, "MyStruct");
	}
}

impl std::fmt::Display for HRESULT {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		<Self as std::fmt::Debug>::fmt(self, f) // delegate
	}
}

As a reminder, the Self casting can be useful in other situations.

Monday, June 26, 2023

A generic hook for useState and Immer

While prospecting the use of useState with the great Immer, I ended up finding the small use-immer package, which unifies both things. However, it doesn’t have proper TypeScript typings.

So I wrote my own hook to perform this task: just like useState, it returns a value and a setter, but the setter’s argument is a mutable state, automatically wired to produce, and all that using a generic argument for proper typing:

import {useCallback, useState} from 'react';
import {Draft, produce} from 'immer';

export function useStateImmer<T>(initialState: T | (() => T)): [
	T,
	(updater: (draft: Draft<T>) => void) => void,
] {
	const [obj, setObj] = useState(initialState);
	const setObjProduce = useCallback((updater: (draft: Draft<T>) => void): void => {
		setObj(curState => {
			return produce(curState, draft => {
				updater(draft);
			});
		});
	}, [setObj]);
	return [obj, setObjProduce];
}

Note that the returned setter function is identity, since it’s guarded by an useCallback call.

Thursday, June 22, 2023

Java Either class

Today, at work, I had the dire need of discriminated unions in Java, which of course doesn’t support it. Then I found the idea of the Either type, which I implemented myself using the Optional class from Java 8:

import java.util.Optional;
import java.util.function.Consumer;
	
/**
 * Guarda 3 elementos mutuamente exclusivos, isto é, apenas 1 dos 3 pode existir
 * dentro do objeto.
 */
public class Either3<T1, T2, T3> {
	
	private Optional<T1> v1 = Optional.empty();
		
	private Optional<T2> v2 = Optional.empty();
		
	private Optional<T3> v3 = Optional.empty();
	
	/** Cria um objeto com o valor do tipo 1. */
	public static <<1, T2, T3> Either3<T1, T2, T3> of1(T1 v1) {
		Either3<T1, T2, T3> obj = new Either3<>();
		obj.v1 = Optional.of(v1);
		return obj;
	}
	
	/** Cria um objeto com o valor do tipo 1. */
	public static <T1, T2, T3> Either3<T1, T2, T3> of2(T2 v2) {
		Either3<T1, T2, T3> obj = new Either3<>();
		obj.v2 = Optional.of(v2);
		return obj;
	}
	
	/** Cria um objeto com o valor do tipo 1. */
	public static <T1, T2, T3> Either3<T1, T2, T3> of3(T3 v3) {
		Either3<T1, T2, T3> obj = new Either3<
		obj.v3 = Optional.of(v3);
		return obj;
	}
	
	/** Retorna o valor do tipo 1; caso não exista, lança {@code NoSuchElementException}. */
	public T1 get1() {
		return v1.get();
	}
	
	/** Retorna o valor do tipo 2; caso não exista, lança {@code NoSuchElementException}. */
	public T2 get2() {
		return v2.get();
	}
	
	/** Retorna o valor do tipo 3; caso não exista, lança {@code NoSuchElementException}. */
	public T3 get3() {
		return v3.get();
	}
	
	/** Diz se o objeto possui um valor do tipo 1. */
	public boolean has1() {
		return v1.isPresent();
	}
	
	/** Diz se o objeto possui um valor do tipo 2. */
	public boolean has2() {
		return v2.isPresent();
	}
	
	/** Diz se o objeto possui um valor do tipo 3. */
	public boolean has3() {
		return v3.isPresent();
	}
	
	/** Executa a função caso haja um valor do tipo 1. */
	public void if1(Consumer<? super T1> consumer) {
		v1.ifPresent(consumer);
	}
	
	/** Executa a função caso haja um valor do tipo 2. */
	public void if2(Consumer<? super T2> consumer) {
		v2.ifPresent(consumer);
	}
	
	/** Executa a função caso haja um valor do tipo 3. */
	public void if3(Consumer<? super T3> consumer) {
		v3.ifPresent(consumer);
	}
}

Since Java doesn’t have variadic generic parameters, having one class to each amount is needed. Nonetheless, it’s doable and remarkably ergonomic.

Thursday, June 8, 2023

Using Zustand, TypeScript and Immer together

In React land, Redux nightmare finally came to an end with the inception of Zustand, which throws away all the chores we had to previously do, leaving us with a simple workflow of minimal code. On top of that, TypeScript allows us to relax our brains with some degree of strong typing.

When dealing with our Zustand store, just like Redux, we have to manipulate a chunk of immutable data, which becomes more and more annoying as we deepen the object structure. And, when it comes to dealing immutable data, Immer is everyone’s best friend – just slap a produce call and you can go home earlier.

Now here comes the question: how to use Immer inside our Zustand store?

Turns out Zustand provides a few middlewares – functions which lie between the data and your manipulation functions –, and among them an Immer middleware. Using the middleware has two advantages:

  • you don’t need to explicitly call produce, because the state argument already comes to you as a WritableDraft;
  • you don’t need to explicitly type the state argument, because it already comes to you properly typed.

To put everything together, with automatic type inference, we also need the combine middleware, so we don’t need to write the store type by hand. Here is the full template that can be used as the starting point of your store:

import {create} from 'zustand';
import {combine} from 'zustand/middleware';
import {immer} from 'zustand/middleware/immer';

interface Person {
	name: string;
	age: number;
}

const useStore = create(immer(
	combine({
		people: [] as Person[],
	},
	(set, get) => ({
		add(name: string, age: number) {
			set(state => {
				state.people.push({name, age});
			});
		},
		remove(name: string) {
			set(state => {
				state.people = state.people.filter(p => p.name === name);
			});
		},
	})),
));

export default useStore;

Notice how you can simply mutate the state inside the actions.

Saturday, June 3, 2023

TypeScript map for objects

In another round of React experiments, after another Vue frustration with Volar not typing event arguments, I was again into the state normalization land. When dealing with objects with IDs as keys, every operation requires a tedious reduce, in contrast to the intuitive map we use with arrays.

So I wrote a map for objects:

function mapObj<T>(obj: Record<string, T>, callback: (elem: T, key: string) => T): Record<string, T> {
	return Object.keys(obj).reduce((accum, key) => {
		accum[key] = callback(obj[key], key);
		return accum;
	}, {} as Record<string, T>);
}

It’s fully typed, and it was surprisingly easy to write. TypeScript is a great language.

Saturday, April 15, 2023

Testing GitHub pull requests before merging

My personal projects on GitHub oftentimes. It’s very flattering when other people get interested in your personal work, and actively use it. GitHub pull request is how other people suggest changes to our code.

Pull requests can be merged in GitHub with just one click, but it’s also possible – and recommended – trying the changes locally first. The usual way to do this is creating a local branch, then pulling the modified code onto it.

As an example, let’s take pull request #75 of WinSafe. We create another branch called foo and check the pull request onto it by using:

git fetch origin pull/75/head:foo
git checkout foo

Simple enough, and for some reason it took me years to try this for the first time today.

Thursday, February 9, 2023

Fetch calls with global Jotai object

Zustand is a great React state management library, but it has the drawback of not having a standard way to define getters, which must be defined as ordinary hooks.

Written by the same author, Jotai seems to be the answer to this situation. However, while Zustand provides a way to change the state outside React components, Jotai does not. Today I was able to devise a way to work around this, by simply applying the hooks concept, answering my own question:

const errorAtom = atom(''); // storage

const errorReadAtom = atom(get => { // read-only (computed)
	return 'Error: ' + get(errorAtom);
});

const writeErrorAtom = atom(null, (get, set, arg: string): void => { // setter
	set(errorAtom, arg);
});

function useGet(): (url: string) => Promise {
	const [, setError] = useAtom(errorAtom);
	return async (url: string): Promise => {
		const resp = await fetch(url);
		const json = await resp.json();
		setError('Hello');
		return json as T;
	};
}

Usage:

function App() {
	const doGet = useGet();

	async function click(): void {
		const data = await doGet('/foo');
	}

	return <button onClick={click}>Click</button>;
}

So, with Jotai I finally have the complete solution to state management in React.

Tuesday, December 6, 2022

The three rules of lifetime elision in Rust

While reviewing some Rust fundamentals, I stumbled across this excellent video about lifetimes. What caught my attention the most was the “three rules” of lifetime elision – a topic I had some idea about, but I’ve never seen clearly explained.

For reference, they are:

  • Each parameter that is a reference gets its own lifetime parameter;
  • If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters;
  • If there are multiple input lifetime parameters, but one of them is &self or &mut self, the lifetime of self is assigned to all output lifetime parameters.

Wednesday, October 5, 2022

Zustand computed values

This week I’ve faced some situations with Vue’s reactive that alarmed me. I’m finding the hard way what I’ve read a couple times: reactive proxies can behave unpredictably in some situations. Too bad I’m in the middle of a project which began in Vue 3. I’m strongly considering rewriting it in React now – yes, it will be an insane amount of work, including back-end changes to return normalized objects.

But React brings its own problems. The biggest of all is certainly the raw state management.

Among all state management tools I’m evaluating, Zustand is showing to be the most promising. It’s ticking all the boxes, and the only open question so far is computed state. The best I could do was to use custom hooks, but they look rather ugly and verbose:

import create from 'zustand';
import {combine} from 'zustand/middleware';

/**
 * The store, which holds the state and the actions.
 */
const useBearStore = create(
	combine({
		bears: 0,
	},
	(set, get) => ({
		increasePopulation(): void {
			set(state => ({ bears: state.bears + 1 }));
		},
		removeAllBears(): void {
			set({ bears: 0 });
		},
	})),
);

export default useBearStore;

/**
 * Custom hook that returns a computed value.
 */
export function useBearCountPlusOne(): number {
	const bears = useBearStore(s => s.bears);
	return bears + 1;
}

Usage example in a component:

import useBearStore, {useBearCountPlusOne} from './useBearStore';

function App() {
	const increasePopulation = useBearStore(s => s.increasePopulation);
	const populationPlusOne = useBearCountPlusOne();

	return <>
		<div>Population + 1: {populationPlusOne}</div>
		<button onClick={() => increasePopulation()}>
			Increase population
		</button>
	</>;
}

If I find a way to rework these loose hooks, I think I found my way.

Wednesday, September 28, 2022

Multiple className values in React

React offers basically zero support for CSS. Personally I’m fond of CSS Modules along with SCSS instead of the slow CSS-in-JS solutions out there. Still, we need a way to manage multiple classes in className property.

In order to mitigate this problem, I wrote a TypeScript function to deal with sequential or conditional class names situations:

/**
 * Generates the className attribute with the given class names.
 */
export function cls(...names: (string | undefined)[]): string;
export function cls(names: (string | undefined)[]): string;
export function cls(names: Record<string, boolean>): string;
export function cls(names: any): string {
	if (typeof names === 'string' && arguments.length === 1) { // just 1 string
		return names === undefined ? '' : names;
	} else if (typeof names === 'string' && arguments.length > 1) { // multiple strings
		let s = '';
		for (const name of arguments) {
			if (name !== undefined) s += name + ' ';
		}
		return s;
	} else if (Array.isArray(names)) { // string array
		let s = '';
		for (const name of names) {
			if (name !== undefined) s += name + ' ';
		}
		return s;
	} else if (typeof names === 'object') {
		let s = '';
		for (const name in names) {
			if (names[name]) s += name + ' ';
		}
		return s
	} else {
		throw 'Invalid class name input!';
	}
}

Example using the 4 argument possibilities:

import {cls} from '@/funcs';
import c from './App.module.scss';
	
function App() {
	return <>
		<div className={cls( c.first )} />
		<div className={cls( c.first, c.second )} />
		<div className={cls( [c.first, c.second] )} />
		<div className={cls({
			[c.first]: true,
			[c.second]: false,
		})} />
	</>;
}

This covers all situations I ever faced.

The spread operator version was added in February 9, 2023.

Thursday, July 21, 2022

Sizes of Windows integral types

While developing WinSafe, it’s very common to convert the Windows integral data types to their Rust equivalent. Care must be taken, however, when it comes to pointer size, which varies according to the architecture. Since WinSafe is aimed to both 32 and 64-bit Windows, I must pay attention.

For reference, below is the table I’m using to figure out the sizes:

Signed C Signed Rust Unsigned C Unsigned Rust 32-bit 64-bit
CHAR
 
 
i8 UCHAR
BYTE
BOOLEAN
u8 8 bit (1 byte)
SHORT
 
 
i16 USHORT
WCHAR
WORD
u16 16 bit (2 byte)
BOOL
INT
LONG
 
i32  
UINT
ULONG
DWORD
u32 32 bit (4 byte)
INT_PTR
LONG_PTR
LPARAM
 
isize UINT_PTR
ULONG_PTR
WPARAM
SIZE_T
usize 32 bit (4 byte) 64 bit (8 byte)
LARGE_INTEGER
LONG64
LONGLONG
 
 
i64 ULARGE_INTEGER
ULONG64
ULONGLONG
DWORD64
DWORDLONG
QWORD
u64 64 bit (8 byte)

The table above is an extension of this one.