Against Object Pools
Do not use object pools in your library
Instead expose an interface for applications to implement their own object pools, such as:
interface Lender<T>
{
T borrow();
return(T obj);
}
Why? By definition, an object pool is a leaky abstraction. Quite literally, an object pool holds onto memory once it has allocated it (usually). This means users of your library will need to know about your library's object pools when chasing down a memory leak. However, that is not to say that object pools are bad, they can play a crucial role in reducing allocation times, and help with garbage collection overhead. But whether this optimization is worth the trade-off can only be known at the application level.
Note posted on Sunday, June 22, 2025 8:39 AM CDT - link
Open Source 2025 Notes
Throwback Thursday: Old School Optimization Using Newfangled Machine Learning
Linear Programming
- "Programming" in the sense of scheduling.
- Inequalities carve out regions of space. These are called "constraints".
- Objective function: goal we are trying to maximize.
Linear Optimization Facts
- Solvable when bounded.
- Min-Max duality.
- As an example - RPGs: Max strength == min weakness
- Deterministic - guarantees best answer given the input. Is susceptible to garbage input.
When to use
- Making the best choice out of many available while satisfying constraints.
- Resource allocation.
- Matching.
- Routing problems.
- Inventory decisions.
- Best fit for batch decisions under constraints.
Expected Outcomes
- Better use of resources.
- Stable operations planning.
- Better inventory management
Pros of Linear Programming
- Measured risk, explainable, and respects your contraints. Can be built quickly with a small team.
- Plays nice with forecasted values: making best prediction with the data available.
- Any vertex of the feasible region has a cone of normal vectors where it is the optimal solution.
- Strength of forecast affects the feasible region.
How to Make One?
- Pyomo functions like an ORM for interfacing with solvers.
- CBC (COIN-OR Branch Cut) to solve.
React at Work Post-Create-React-App
- Provided testing, bundling, and a dev server built-in.
- "Kill it with Fire" book looks interesting.
- Create React App officially deprecated in February 2025.
- React 19 doesn't work well with it.
- React is slowly becoming its own framework instead of just acting as a component in a bring-your-own-framework model.
Alternatives
Next.js
Cons:
- Tight coupling of backend and frontend.
- Built-in server runtime.
Remix
- Also tightly coupled.
- Gnarly nested routing structure.
Bun
- Really fast bundler.
- Too much magic.
- Incomplete ecosystem compatibility.
Vite
- Fast, flexible, and friendly.
- Pretty easy to migrate from CRA.
- Static by default.
- Unbundled dev server, optimized builds.
- Can use existing rollup plugings.
Open Source Tooling and Best Practices to Improve Vulnerability Management
- VM: Vulnerability Management
--Identification -> Reporting -> Evaluation -> Prioritization -> Remediation -- ∧ | | ∨
- Competency trap: people don't use new tools because building competency in a new tool is challenging.
- Mend Renovate: formerly Renovate.
- Renovate bot can work off of dependencies defined in comments in a dockerfile.
Beyond the Chatbot: Delivering Business Value with LLMs
- According to IBM "Institute of Business Value", only 25% of AI initiatives have delivered expected ROI.
- 16% have scaled enterprise wide.
- IBM's Advice: ignore FOMO, lean into ROI.
- When to chatbot:
- Onboarding users (employees/customer) to complex systems.
- When users are lost, confused, or not even sure what they need.
- Need metrics
- conversion rate
- % requests routed to a human
- manual time saved
- response/execution times.
- When not to chatbot:
- Simple forms
- When users are experts
- LLM Assisted Automation (agents) can provide value by performing tasks on triggers.
- Using AI to capture business that you don't have the number of people to take in the business?!?!
- Solving problems with GenAI:
- Classifying unstructured data.
- What kind of document is it? What needs to be done with it?
- Convert unstructured so structured data?
- Parsing quote requests, emails, etc.
- Translate similar data between systems:
- Referencing information from external vendor systems.
- Classifying unstructured data.
- Why was something not already automated?
- Complex SOPs (Standard Operating Procedures).
- Unstructured data
- Parsing information from multiple internal/external services
Measuring GenAI Solutions
LLM Evals
"Answer true or false" is the key.
- Example:
SYSTEM: Act as a metallurgy expert. Do not explain your answer. Answer true or false.
USER: Steel is an alloy of iron and carbon?
Easy to apply to any business domain. Ask experts: what are the toughest questions you're asked? What are the perfect 20 year veteran answers? Then let the expert decide when the AI is trustworthy enough.
Metrics Driven Development
- Answer correctness
- True/false, multiple choice questions
- More complex questions:
- Determine ground truth
- Take intersection of model answers and ground truth and divide by intersection + model + ground truth.
- AnswerIntersections / (AnswerIntersections + Ground Truth + Model Answers)
- Faithfulness - degree to which outputs align with facts
- Relevancy
- Context recall
- Arize Phoenix: LLM evaluation tool
- guardrailsai.com
These tools are generally used to test existing LLM's, not to fine tune or create a new model. Most things that are changed by the LLM implementer are the context that is fed in, or the prompt that is given.
Note posted on Wednesday, May 28, 2025 7:17 PM CDT - link
Minnesota Code Freeze Conference Notes
My notes from the 2025 Minnesota Code Freeze conference that took place at the University of Minnesota.
Working Effectively with Legacy Code
Michael Feathers (r7k)
- Test-Driven Architecture (like TDD but for architecture).
- Hyrum's Law: Any behavior you expose on an API will become depended upon even if it's not intended.
- Knowledge tends to dissipate over time (think music popularity reduction over time, for example, the ragtime of the 1920's).
- Galileo's scaling law (the square/cube law) - structure must change as things grow:
- Initially named after the concept that as an object grows its volume grows faster than the surface (n^3 > n^2).
- However, applies to other scaling concerns, for example, you can't build a skyscraper with wood.
- With graphs, as they grow, you need to break them into smaller graphs to manage them effectively.
- Understanding this tension can improve design.
- Miller's law - humans can track 7 +/- 2 items
- This relates to class design, a class with 20 methods is much harder to track than a class with around 7.
- "The Principle of Deliberate Context" (coined by Michael Feathers):
- Systems should be designed with intentionally bounded contexts that are optimally sized for human cognition.
- Robustness principle: be liberal in what you accept and conservative in what you produce.
- A potential issue with AI is when it generates things that we don't understand.
Infrastructure as Code Anti-Patterns
Jason Baker (Director of Cloud Operations at Civix)
- Thought on anti-patterns: every anti-pattern started as a pattern with good intentions.
- Why do infrastructure as code anti-patterns exist?
- Senior tech leadership usually is promoted through software not infrastructure (citation needed).
- Defining infrastructure as code is immature compared to software development (isn't infrastructure as code software development?).
- Cloud Cowboys
- Creating cloud infrastructure manually (using a web console) instead of using code.
- Muti-Tool Madness
- Using too many tools to define the infrastructure.
- Increased complexity, risk, and toil.
- Picking a consistent tool is more important than pick the best tool.
- Partitioning infrastructure projects by infrastructure type or environment instead of by context.
- Building all the environments
- Using a single infrastructure template to build out all environments.
- Templates should be environmentally agnostic.
- Infrastructure changes should be promoted using the same process as normal software.
- One repo to rule them all
- Defining all resources using infrastructure code in one monolithic infrastructure code repository.
- Can introduce undesirable coupling.
- Partition infrastructure code by services.
- Using DRY With Infrastructure
- All software problems can be solved with another layer abstraction, except for the problem of too many layers of abstraction.
- Taking an imperative approach to building infrastructure instead of a declarative approach.
- Coupling vs. cohesion
- Modularize all the things
- Same as above.
- Enforce the updating of module versions across projects, allowing no more than for example 2 versions in use.
- The infra team
- All infrastructure code has to be defined by an infrastructure team.
- Reinforcese the traditional division between developer and infrastructure operations.
- A single team becomes a major bottleneck for infrastructure work.
- In high-performing organizations most of the infrastructure code is defined by software developers and co-located in application repositories.
- All security related infrastructure code changes must be implemented and approved by the secuirty team.
Finite State Machines and AI
Adam Terlson (https://github.com/adamterlson/AgenticStateMachines)
- Using state machines to control AI agents.
- X State: state management and orchestration library
- Model-based testing of state machines
- His AI Agent definition:
- Autonomous - operates independently, making decisions without constant human input
- Goal-oriented - achieves specific objectives
- Context-aware
- Collaborates
- Interactive
- Persistence - retains memory of past interactions and data.
- Adaptive
- Agentic Systems:
- Actors provide autonomy and communication.
- State machines provide structure, enforce predictable behavior.
- AI provides adaptability.
- Patterns:
- Tool use
- Available tools given to LLM.
- LLM returns JSON object describing a method (tool) call.
- Expected next message is tool response.
- Human in the Loop (tool approval, if tool is expensive).
- Feedback: one oagent receives the output of another agent and gives feedback on how to improve.
- Collaboration: multiple specialist agents working on a broader goal where each agent owns a slice of the broader task.
- Orchestration: Planning agent dynamically routes to the next agent based on current context.
- "Chartering": state machine defined by LLM.
- Tool use
- Advantages over industry tools?
- (LangGraph and competitors)
Evolveable Architectures
Rebecca Parsons
- Conway's law - the systems you build will reflect the dysfunction of your organization.
- Last responsible moment - wait to make decisions until the last possible moment.
- How do we build code that is auditable?
- How do we build code that increases code quality?
- Contract testing allows developers to ignore each other.
- Use AI for contract testing?
- LLM hallucinations are a feature, not a bug. How are you supposed to generate something new if you never make something up? The question is whether there are ways to bound the hallucinations (yes, with temperature).
- How do you test non-deterministic systems?
Monolith vs. Platform vs. Serverless
Corwin Diamond
- This talk was generally biased against monoliths (seemed to imply a definition of a monolith as a giant legacy application, in something like an older ecommerce server application).
- CAP Theorem:
- Consistency - works correctly.
- Availability - always works.
- Partition Tolerance - works across different partitions.
- You can pick 2.
- Distributed CAP: different services/teams within your ecosystem will have different CAP requirements.
- Multi-cluster platforms...
- Need to solve data replication.
- Sounds like classic SOA/microservice problems.
- Cellular architecture...
- Platform monoliths.
- Modularize abstract abstractions.
- Minimize different types of deployment processes.
- Remove requireemnts that aren't required.
Panel
David Laribee, Rebecca Parsons, Michael Feathers
- Remote work:
- How can we maximize the effectiveness of in-person time?
- Shoud do hybrid meetings remote first.
- Important to work on things together with remote work.
- Create venues where smaller collaborations can occur.
- Times when we get together should be treated as precious and valuable.
- Is the problem encouraging people to value social connections?
- Modular Monoliths:
- Return to simplicity.
- Look into testcontainers.
- It is important at the early stages of a product development to develop a well-structured monolith.
- Chris Richardson has good ideas on microservices.
- Event-driven applications can be done in process (and I do).
- Functional programming:
- "The little lisper", "the little schemer" books.
- If one spends too much time in the OOP world, it can become difficult to understand functional programming.
- More people are recognizing the benefits of functional programming.
- How do we discern when we should adhere to new programming principles vs traditional principles?
- With low-code/no-code, you can get started very easily, but it's hard to scale to complex problems.
- It's easy to solve 80% of the problem, hard to solve the remaining 20%.
- Be very cautious about de-scaling your development.
- Where have all the mid-level engineers gone :D.
- With low-code/no-code, you can get started very easily, but it's hard to scale to complex problems.
- Is agile dead?
- Agility was kind of jettisoned, and "agile" became a management practice.
- Good technology (and good process?) disappear, they just become standard operating procedure, like small commits, TDD, etc.
- There are different product domains: Simple, Complex, and Chaotic. Which domain your product fits in determines how "agile" you need to be.
- 3 things developers can do to stay current:
- Need to start using AI.
- How are they going to make money off of it?
- Everyone is using AI today which differs from the prior AI hype cycles of the 60's and 80's.
- And problematically, most people do not have a strong mental computational model that matches how it really works.
- AI is good at ideation. It can also help increase your skill at asking questions.
- Need to start using AI.
Note posted on Friday, January 17, 2025 1:28 PM CST - link
Distributed Large Language Models
LLM's have proven their utility for generating small scripts. You'll often read comments along these lines on programming forums these days:
Sure LLM's can generate little scripts but they can't create real programs...
I think this is largely true; however, this is actually useful in "real" programs — the "scripting" ability combined with auto-complete can definitely contribute to developing a full-sized program. Already, tools like Copilot are taking advantage of this ability. This is also generally useful (although it comes with some major caveats in my opinion).
Since LLM's are so great at condensing a large amount of information into a relatively small model that can be queried with natural language, like many others, I worry about the loss of great sources of information like Stack Overflow. Stack Overflow's advantage over an LLM is that it's constantly gaining new information, and it's generally fed by great feedback loops that encourage contributing new information. Of course, this has its own issues, but in general, I think that holds.
However, part of me feels like the philosophers of old, concerned that this new-fangled writing technology was going to reduce human intelligence since we no longer would need to learn through discussion. So, I think it's important to think of what will come next, now that we can in some ways have a miniature static Stack Overflow on our PC's: will we have shared models that can send information back to a host, feeding in new tips, fixes, etc.? I fear this being centralized by commercial entities, and I think even Stack Overflow is trying to build something like this. OpenAI and Microsoft are already using feedback mechanisms.
Would it be possible for the programming community to do P2P model training and inference? This sounds great, but how do we develop and use shared models that have minimal or no quality control? Does or can a community form around a P2P model that corrects its training? How does a community pay for the training? Is it distributed, like folding@home but for programming help? Building a DNN model isn't just about dropping in a bunch of data and hitting the train button, it also involves a lot of decisions on the trainer: choosing appropriate number of layers, how many iterations to train, and more. This might need to be static to some degree with a shared community model.
Note posted on Saturday, January 4, 2025 6:59 PM CST - link
June 17th, 2024
Targeting Multiple Form Factors on Android
If you'd want to target multiple form factors on Android for a single application, you suffer from a case of too many options:
- Provide the same app:
- Views that adapt to different view dimensions.
- Views that render differently depending on flags given during the launch of the application (typically via intent).
- Provide different apps:
- Different product flavors or different modules:
- Different application IDs.
- Different version codes.
- Different product flavors or different modules:
This means there are 6 (2 + 2*2) possible ways of providing a different application based on the deployed form factor. So how to choose the best?
First off, the "different apps design" seems mostly intended for completely different applications that share the same codebase; the fact that you can use it for different form factors seems to just be a nice side effect. This means that this approach really works best with different applications - with different application IDs, you don't need to worry about collisions when providing application artifacts on Google Play. The downside is that this leaves you with two separate apps as far as Google Play is concerned, or two different version code tracks. If you take the "different version code" and later consolidate your handheld and TV apps, with the TV app being on the "higher" version code track, users will have to for example uninstall the existing TV application. One thing to be aware of with this approach is that once set-up in the Google Play store, it is difficult to reverse.
The other approach is to modify your views given different view dimensions or different Intent
information. For TV, for example, this can be discerned by launching a special activity that is filtered on the android.intent.category.LEANBACK_LAUNCHER
intent category. While having a single app makes publishing to the app store easier, it does leave both your TV users and your handheld users with the whole app, even though they may not use large swaths of it. Sharing views between TV and handheld is harder than it seems, at least with Jetpack compose, as the API's between touch and D-Pad navigation are largely separate.
Regardless of approach, it feels like swimming upstream trying to have the same app in different form factors. The ideal would to be able to produce different applications per form-factor that can share application ID and version code. Absent that, I think the best approach is unfortunately to maintain a single application.
Note posted on Monday, June 17, 2024 10:54 PM CDT - link
April 9th, 2024
Rust seems like the perfect language for an LLM-driven programming agent, because the compiler has so many safeguards, that often the best way to build the best program is often to just iterate until your program passes the compilers tests anyways.
Note posted on Monday, June 17, 2024 10:53 PM CDT - link
March 23rd, 2024
Bittorrent is Dying, NFT's were dumb, but together they could be great?
This is probably an already explored idea, but I quite like the idea for digital art of some sort of proof-of-sale/proof-of-ownership system that uses a git-like (or blockchain-like) structure, which contains magnet links for that digital art, with the digital art itself remaining unencrypted. I think something like this would give society a neat way to appreciate artists for their work without having to lock down said work. Maybe this is what NFT's already are, I don't know!
March 18th, 2024
A defining feature of an asynchronous operation is that it is cancellable.
March 5th, 2024
Good programing is more about good judgment than anything else.
Note posted on Tuesday, March 5, 2024 2:29 PM CST - link
The Interaction State Primitive
I've been developing UI's in some form or another for about 25 years. It's taken nearly that long to realize the basic primitive of dynamic UI's: what I call the "Interaction State" primitive. The Interaction State primitive has these rules:
- The current state of the property can be accessed via a normal get accessor. In Java, this might be a method with a signature like
T getValue()
. Sometimes this is called a snapshot, but I prefer the more direct terminology of state. In a language with more syntactical magic available, it can be done via something like a get accessor. - Its value can be updated via a normal set accessor. In Java, this might look like
setValue(T value)
. Again, in other languages, syntax magic can make the write access more terse. - Updates to the property's state are observable. Below, I will cheat and use the ReactiveX flavors to achieve this.
setValue(T value)
only notifies observers the value is updated when the new value is not equal to the existing value. This should follow normal equality rules of the language.
I am not the first to make this observation; KnockoutJS, which I used c.a. 2014, had observables in Javascript, and Rx.Net and other ReactiveX flavors have been around for quite some time, with the BehaviorSubject<T>
, which has the above semantics, but with slightly contorted syntax. However, in my opinion, all of these languages and libraries don't make it plain that these are the rules they're following. Perhaps the authors of these API's think these rules are obvious and don't need to be broadcast. However, these rules weren't made plain to me until I came across the StateFlow type in Kotlin: upon observing it's simplicity, the lack of too much magic or layers of obfuscation, this raw form of the Interaction State primitive made it all obvious to me.
What benefits does this Interaction State primitive give?
- When used as the main representation of the data state in a View Model, they're easily testable; the current state of the property can be taken just by calling
getValue()
, and if you want to validate how the property changes over time, you can easily do that too. - When the observable portion is implemented using something like the Reactive extensions, you can observe its value over time with endless numbers of operators. Using something very generic like Reactive extensions instead of something like
StateFlow
leaves you very free of any assumptions made about the runtime or environment; for example, you don't have to mess with coroutine contexts or anything like that. - Integrates very nicely with dynamic UI frameworks; React obviously has its state variables, Jetpack Compose has the magical
stateOf
andmutableStateOf
, and C# has theINotifyPropertyChanged
magical interface, and the Interaction State primitive can integrate very nicely with all of them! It also integrates well with something like WinForms.
I want to show some examples of how this primitive can be used in different languages, with a fairly simple use case: given expenses and incomes, display the networth.
Kotlin and Jetpack Compose
Here's an example in Kotlin, integrating with Jetpack Compose:
// The read-only interface for the Interaction State Primitive
abstract class InteractionState<T> : Observable<NullBox<T>>() {
abstract val value: T
}
// A mutable implementation of the read-only interface for the Interaction State Primitive.
class MutableInteractionState<T>(private val initialValue: T) : InteractionState<T>() {
// RxJava has the unfortunate design decision of nulls not being allowed as values, so we
// box the value to allow nulls.
private val behaviorSubject = BehaviorSubject.createDefault(NullBox(initialValue))
override var value: T
get() = behaviorSubject.value?.value ?: initialValue
set(value) {
if (behaviorSubject.value != value)
behaviorSubject.onNext(NullBox(value))
}
override fun subscribeActual(observer: Observer<in NullBox<T>>?) {
behaviorSubject.safeSubscribe(observer)
}
fun asReadOnly(): InteractionState<T> = this
}
class LiftedInteractionState<T: Any>(source: Observable<T>, private val initialValue: T) : InteractionState<T>(), AutoCloseable {
private val behaviorSubject = BehaviorSubject.createDefault(NullBox(initialValue))
private val subscription = source.distinctUntilChanged().subscribe { behaviorSubject.onNext(NullBox(it)) }
override val value: T
get() = behaviorSubject.value?.value ?: initialValue
override fun subscribeActual(observer: Observer<in NullBox<T>>?) {
behaviorSubject.safeSubscribe(observer)
}
override fun close() {
subscription.dispose()
}
}
class MoneyViewModel(accountManager: ManageAccounts) : ViewModel() {
val expenses = MutableInteractionState(0.0);
val income = MutableInteractionState(0.0);
val networth = LiftedInteractionState(
Observable.combineLatest(expenses, income).map { expenses, income -> income - expenses },
0.0);
suspend fun loadAccount(int accountId) {
var account = accountManager.loadAccount(accountId);
expenses.Value = account.Expenses;
income.Value = account.Income;
}
}
// easily map MutableInteractionState to mutableStateOf(...)
@Composable
fun <T, S : MutableInteractionState<T>> S.subscribeAsMutableState(
context: CoroutineContext = EmptyCoroutineContext
): MutableState<T> {
val state = remember { mutableStateOf(value) }
DisposableEffect(key1 = this) {
val disposable = subscribe { state.value = it.value }
onDispose {
disposable.dispose()
}
}
LaunchedEffect(key1 = this) {
value = state.value
if (context == EmptyCoroutineContext) {
snapshotFlow { state.value }.collect {
value = it
}
} else withContext(context) {
snapshotFlow { state.value }.collect {
value = it
}
}
}
return state
}
// The Compose view
fun MoneyView(viewModel: MoneyViewModel) {
with (viewModel){
var expensesState by expenses.subscribeAsMutableState()
StandardTextField(
placeholder = stringResource("Debit"),
value = expensesState,
onValueChange = { expensesState = it }
)
var incomeState by income.subscribeAsMutableState()
StandardTextField(
placeholder = stringResource("Credit"),
value = incomeState,
onValueChange = { incomeState = it }
)
val networth by networth.subscribeAsState()
Text(networth);
}
}
Typescript and React
Here's my usage in Typescript/React:
// Build up the type definitions
export interface InteractionState<State> extends Subscribable<State> {
get value(): State;
}
export interface UpdatableInteractionState<State> extends InteractionState<State> {
set value(state: State);
}
// With Typescript, we have the advantage that we can just inherit from the existing `BehaviorSubject` and here
// make `value()` read-only via interface. Of course it's not enforced in the runtime, but it's good enough for us.
class MutableInteractionState<T> extends BehaviorSubject<T> implements UpdatableInteractionState<T> {
constructor(initialValue: T) {
super(initialValue);
}
set value(value: T) {
this.next(value);
}
}
class LiftedInteractionState<T> extends BehaviorSubject<T> implements InteractionState<T> {
constructor(observable: Observable<T>, initialValue: T) {
super(initialValue);
observable.subscribe(this);
}
}
export function liftInteractionState<T>(observable: Observable<T>, initialValue: T): Observable<T> & InteractionState<T> & SubscriptionLike {
return new LiftedInteractionState(observable, initialValue);
}
export function mutableInteractionState<T>(initialValue: T): Observable<T> & UpdatableInteractionState<T> & SubscriptionLike {
return new MutableInteractionState(initialValue);
}
export function useInteractionState<T>(interactionState: InteractionState<T>): T {
const [state, setState] = React.useState(interactionState.value);
React.useEffect(() => {
const sub = interactionState.subscribe({ next: setState });
return () => sub.unsubscribe();
}, [interactionState]);
return state;
}
export function useMutableInteractionState<T>(interactionState: UpdatableInteractionState<T>): [T, Dispatch<SetStateAction<T>>] {
const state = useInteractionState(interactionState);
return [
state,
(stateOrAction: SetStateAction<T>) => {
if (stateOrAction instanceof Function) {
interactionState.value = stateOrAction(interactionState.value);
return;
}
interactionState.value = stateOrAction;
}];
}
export class MoneyViewModel {
readonly income = mutableInteractionState(0);
readonly expenses = mutableInteractionState(0);
readonly networth = liftInteractionState(
combineLatest([this.income, this.expenses]).pipe(map(([i, e]) => i - e)),
0);
}
const vm = new MoneyViewModel();
export function MoneyView() {
const income = useInteractionState(vm.income);
const expenses = useInteractionState(vm.expenses);
const networth = useInteractionState(vm.networth);
return <div>
<input type="text" value={income} onChange={e => vm.income.value = Number.parseFloat(e.target.value)}/>
<input type="text" value={expenses} onChange={e => vm.expenses.value = Number.parseFloat(e.target.value)}/>
<div>
<h3>Networth:</h3>
<p>{networth}</p>
</div>
</div>;
}
C# and WPF
interface IInteractionState<T> : IObservable<T>
{
public T Value { get; }
}
// Implementing INotifyPropertyChanged as well allows it to be used with WPF/XAML bindings
class MutableInteractionState<T> : IReadOnlyObservableProperty<T>, INotifyPropertyChanged, IDisposable
{
private readonly BehaviorSubject<T> subject;
public event PropertyChangedEventHandler PropertyChanged;
public MutableInteractionState(T initialValue)
{
subject = new BehaviorSubject<T>(initialValue);
}
public T Value
{
get => subject.Value;
set
{
if (!EqualityComparer<T>.Default.Equals(value, subject.Value))
{
subject.OnNext(value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
}
public IDisposable Subscribe(IObserver<T> observer) => subject.Subscribe(observer);
public override void Dispose() => subject.Dispose();
}
// A read-only observable
class LiftedInteractionState<T> : IReadOnlyObservableProperty<T>, INotifyPropertyChanged
{
private readonly BehaviorSubject<T> subject;
private readonly IDisposable sourceSub;
public event PropertyChangedEventHandler PropertyChanged;
public LiftedInteractionState(IObservable<T> source, T initialValue)
{
subject = new BehaviorSubject<T>(initialValue);
sourceSub = source.Subscribe(v => Value = v);
}
public T Value
{
get => subject.Value;
private set
{
if (!EqualityComparer<T>.Default.Equals(value, subject.Value))
{
subject.OnNext(value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
}
public IDisposable Subscribe(IObserver<T> observer) => subject.Subscribe(observer);
public override void Dispose()
{
sourceSub.Dispose();
subject.Dispose();
}
}
class MoneyViewModel
{
private readonly IAccountManager accountManager;
public MutableInteractionState<decimal> Debit { get; } = new(0);
public MutableInteractionState<decimal> Credit { get; } = new(0);
public IInteractionState<decimal> Networth;
public MoneyViewModel(IAccountManager accountManager)
{
this.accountManager = accountManager;
this.Total = new LiftedInteractionState<decimal>(
this.Credit.CombineLatest(this.Debit).Select(tuple =>
{
var (credit, debit) = tuple;
return credit - debit;
}),
0);
}
public async Task LoadAccount(int accountId) {
var account = await accountManager.LoadAccount(accountId);
Debit.Value = account.Debit;
Credit.Value = account.Credit;
}
}
<Window>
<Grid>
<TextField Value={Binding Debit.Value} />
<TextField Value={Binding Credit.Value} />
<TextLabel Value={Binding Networth.Value} />
</Grid>
</Window>
In my opinion, removing the obscurity around this primitive has a similar effect to understanding the Promise; it opens up and simplifies a whole class of programming problems: with the Promise
, a single definition is wrapped around building asynchronous functions and receiving their results, likewise, for the "Interaction State" primitive, a single definition is given to making a UI that reacts to inputs agnostic of the source; in other words, regardless of whether the UI needs to respond to user input or the UI needs to respond to asynchronous calls, the situation will be handled in the same way by the "Interaction State" primitive. It also enables a programmer to use it for purposes beyond what the implementations above restrict the user to; for example, I've used the Kotlin implementation in both Android XML views and Jetpack Compose, likewise, I've used the C# implementation for common code between WinForms views and WPF views. This allows my view models to remain stable, while only the decoration is updated with new binding syntax.
Note posted on Saturday, January 27, 2024 11:47 AM CST - link
November 29th, 2023
A core component of learning is separating signal from noise on a given subject. The corollary is that a core component of teaching a subject is informing the student which parts are noise; the signal falls out from this.
November 1st, 2023
Having dealt with C# DLL hell for years, and then going through the equally hellish dotnet HttpClient
fiascos, and now struggling with adopting ES modules in a Typescript library, I am beginning to believe that Microsoft has a department focused on making module resolution a living hell for anyone who uses their tools.
Note posted on Wednesday, November 8, 2023 1:17 PM CST - link
Automated Project Publishing
The idea: a small script that periodically runs, checking for changes to project readme's, and publishes updates to davidvedvick.info.
This script would need to reside in a-personal-site. Would need to be configurable so that it could accurately be run on a local machine but also on a build server.
Update 11/28/2023: I instead implemented this as GoCD pipeline that I run on-demand, and it is glorious.
Note posted on Friday, September 22, 2023 7:17 PM CDT - link