Hacker Newsnew | past | comments | ask | show | jobs | submit | davidkpiano's favoriteslogin

This is a huge part of it, for sure.

I think this is why people often dislike state machines too, for example. They force you to really think through what you're trying to do, and there are no shortcuts.

The irony is that the state machine pattern itself kind of is a shortcut to stable, reliable, safe code. People find them overly complex and full of ceremony, but it's like... Do you really want to try getting the same assurances from your code without this kind of abstraction? I'd rather know my I can't shoot myself in the foot, because I guarantee you that I will.

Elixir also has the added layer of unfamiliarity for a lot of people (myself included) where you might intuitively know how to do something imperatively, but you need to do it within the FP paradigm. I like FP and have adopted a lot of its patterns all over the place, but I still find I'm not a great FP thinker in general. After something like 10 years. My FP-fu is limited to relatively basic operations, and when I go into advanced territory I'm constantly leaning on reference material. That's more so a skill issue than an Elixir issue.


"Asynchronous" isn't part of the name of some really cool state machine :-) Its just an adjective and means the same as when you put it in front of any other noun.

A synchronous state machine is one where the incoming stream of events is always "in sync" with the state transitions, in the following sense:

1. When an event happens, the state machine can transition to the next state and perform any necessary actions before the next event happens

2. After the the state machine has transitioned and performed any necessary actions, the program must wait for the next event to happen. It can't do anything else until it does.

An asynchronous state machine doesn't make the main program wait until the next event happens. It can go on and do other things in the meantime. It doesn't have to wait for then next event to arrive.

When the next event does arrive, the program pauses whatever else it is doing, and hands control back to the state machine, which process the event, and then hands control back over to the main program.


Ok, I'll bite. I've always thought that agent-oriented / reactor / proactor type patterns are elegant and yet under-utilized and under-appreciated. If I had to guess about why.. maybe these systems scale well in terms of traffic but do not tend to scale well in terms of implementation at most orgs?

When the average dev team finally gets the simple queue they asked ops for, the thing is probably late and only present in 2/3 of dev/qa/prod (or some similar SNAFU). Flink works with AWS EMR, so we can't explain everything by just considering whether hosted services are available, but admin/setup/general conceptual overhead is on a different level. Partly because they've been burned in the past with stuff like this, and partly because they are lazy.. devs mostly want simple infrastructure mirroring simple data-structures where they can test/develop discrete code locally without thinking about system-level stuff.

Consider the problem of a) developing a new agent in an actor system vs b) developing a new "step" in a simpler batch-oriented pipeline. For (a), to actually run my code experimentally locally I need to simulate the appropriate message(s) locally and maybe other aspects of system-runtime. Whereas for (b) one digs up appropriate input and starts hacking on a docker-container that one trusts can be jammed into an airflow DAG later. Both approaches need a bit of dev/test harness kinda setup, but (a) is potentially more involved and more importantly it usually crosses team-boundaries (requiring both devops and devs). This will probably create pain unless the org has a talented "platform team" already in place.

Another aspect involving team boundaries is that besides dev teams disliking ops/infra, they also don't trust each other! So after effort is sunk into things like the "smarter-queue" that might support messaging and actors making actors, it turns out every team wants their own queues, routing, codebases, underlying storage, etc. For better or worse, attempting to provide features along the lines of flexibility/interoperability are thus undermined. Out of necessity, teams often want to provide some limited access to data which other teams can consume. But they resist providing any kind of access to code APIs / runtime.

Typical scenario: Ever work at an org that's supposed to have a data-lake, but every single new app or new feature inside an existing app generates requests for new buckets that literally nothing in the existing system can access? Some manager or "senior" dev is having a knee-jerk reaction that they want to build a kingdom. In the end they won't actually enjoy answering access-requests to their walled-garden, but they think they can push those over to support requests. With only data as a deliverable, they don't need to think about any interop and, bonus, no one will even know if their messy scripts are in version control.


Whoa, the top comment is really +5 insightful.

> Map<CustomerId, Set<PageId>> will do

You can do a little better than that. Each item in your map has exactly 3 states:

- We’ve seen this customer visit one unique page with (xx) url on the first day

- We’ve seen this customer visit two unique pages - but only on the first day.

- We’ve seen the customer visit one unique page (xx) and they’ve visited on both days.

In the second state you don’t actually care what the URLs are. And I think the logic for the 3rd state is identical to the logic for the 1st state - since you only add them as a “loyal customer” by visiting them again on the second day. So I think you can get away with using an Option<Url> to store the state instead of a Set or a list. (Though I’d probably use a custom parametric enum for clarity).

It’s a great problem to model using a state machine.


> LLMs can memorize a brute force explosion in finite state machines (interconnected with Word2Vec-like associations) and then traverse those machines and associations as some kind of mashup, akin to a coherent abstract concept.

That's actually the closest to a working definition of what a concept is. The discussion about language representation has little bearing on humans or intelligence, because it's not how we learn and use language. Similarly, the more people - be it armchair or diploma-carrying philosophers - try to find the essence of a meaning of some word, the more they fail, because it seems that meaning of any concept is defined entirely through associations with other concepts and some remembered experiences. Which again seems pretty similar to how LLMs encode information through associations in high-dimensional spaces.


It's similar to Shape Up's Breadboarding[1] in its simplicity and UI/UX orientation, but unlike the former, it's based on a formal Harel Statecharts[2].

Added bonus, it can be formally verified using Alloy [8].

Another similar FSM-based UI/UX tools are XState[3], XState Visualizer[4], and Stately.ai[5] powered by XState.

But Sketch.systems seems to be easier for fast prototyping using plain text format (what they mistakenly calling "markdown"). While XState generates a JS code, which can be used in React apps.

I guess it's possible to convert Sketch.systems format into XState, or other similar ones, after finishing prototyping and moving to implementation/debugging.

Sketch.systems seems to be much more lightweight, which is important for fast prototyping, and lowering barrier to entry for non-tech people.

Another relevant method is Event Modeling[6], which is somewhat in the middle between Breadboading and EventStorming[7]. Its main advantage is that checks the Information Completeness of the entire flow (both frontend and backend, including external services/systems, not just UI/UX), and that it can map 1:1 to CQRS/ES software design.

--

[1] https://basecamp.com/shapeup/1.3-chapter-04#breadboarding

[2] https://www.wisdom.weizmann.ac.il/~harel/papers/Statecharts....

[3] https://xstate.js.org/

[4] https://stately.ai/viz

[5] https://stately.ai/

[6] https://eventmodeling.org/posts/what-is-event-modeling/

[7] https://www.eventstorming.com/

[8] Formally specifying UI https://www.hillelwayne.com/formally-specifying-uis/


And yet many the web or iPhone dev will wildly smear ad hoc state machines within modules and across modules in their codebase via if/case, maybe dressing it up a bit with redux, et al.

As the codebase grows, teams of those devs will labor under the constant threat of suffocation from the thrashing-swirling spaghetti of events and side-effects they themselves authored and don't know how to tame.


> any implementable function has to be one whose first k outputs are determined by at most the first k inputs -- i.e., you can't look into the future. That is, these functions have to be causal.

    def f(input_stream):
        i = next(input_stream)
        j = next(input_stream)
        yield i + j
        f(input_stream)
This function produces k outputs when given 2*k inputs, so it's either acausal or impossible to execute. Right?

The state machine example is definitely a very fitting use of goto, but it reminds me of another thing that seems to have become a rare skill but is very useful: flowcharting. Besides making people comfortable with goto in general, it also helps visualise control flow in ways that a lot of programmers these days don't realise, and it's unfortunate that a lot of courses seem to have omitted its teaching.

Also worth reading is "GOTO Considered Harmful Considered Harmful": https://news.ycombinator.com/item?id=11056434

And here Microsoft provides us with lovely example of such ridiculous nesting.

That's a very memorable example, but ultimately the true cause of that monstrosity is a clearly stupid API design; this is the API for a file picker, the recommended replacement for an existing one that they wanted to deprecate. In the existing one, you fill in a structure and call a single function with a pointer to it. In its replacement, you need to call a dozen methods on an object, and check for "possible" errors on each call, even if probably 99% of them only do things like assign to a field in a now-opaque structure and can never produce an error. Then the example code must've been edited by someone with severe gotophobia. (Not all MS code is like that --- they have plenty of other example code that uses goto, e.g.: https://github.com/microsoft/Windows-driver-samples/blob/mai... ) The existing API was even extensible, since it used a structure with a size field that could differentiate between different versions and extensions, but they didn't.


Decision Tables[1] is probably the simplest formal method, i.e. instead of writing imperative nested if and switch/case statements, your provide a declarative solution.

But IMO the most useful formal method in Software Engineering is modeling your code as an FSMs, for example using StateCharts UML notation [2,3].

That way even junior developers can reason about complex realtime systems.

Next close come Algebraic Type Systems, but they're usually not available in mainstream PLs.

Unfortunately Formal Specification/Verification tools like TLA+, Alloy, etc. are less practical for most real life projects.

--

[1] https://en.wikipedia.org/wiki/Decision_table

[2] https://en.wikipedia.org/wiki/UML_state_machine

[3] https://en.wikipedia.org/wiki/State_diagram#Harel_statechart


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: