Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

State machines are an underrated approached.

Just remember, if you're ever debugging something and some values in a Class/Object/Group of them are set and others are unset the state machine avoids that issue. When you go to transition between states check that the future state will be valid and if-not go to an error state with that information. The amount of time I've seen spent debugging where spurious `null`s came from completely dwarfs the time it would've taken just to write that class/algorithm as a state machine.

Kinda surprised WC wasn't a state machine to beginning with. Isn't it effectively a special characters counter where if you see a charactered followed by a space bump up the word count? I'm judging by the repos comment of "The real programs spend most of their time in functions like mbrtowc() to parse multi-byte characters and iswspace() to test if they are spaces -- which re-implementations of wc skip." that the big improvement is removing unnecessary work of those methods. mbrtowc [1] appears to re-create the provided substring which isn't necessary to count.

[1]: https://en.cppreference.com/w/cpp/string/multibyte/mbrtowc



> Just remember, if you're ever debugging something and some values in a Class/Object/Group of them are set and others are unset the state machine avoids that issue.

Better yet, switch to a language that supports discriminated unions to avoid invalid state without having to write a low-level state machine. Each case in the union represents one of the valid states of the type. This is one of the many benefits of functional programming.


This comes down to "Make Invalid States Unrepresentable"

Notably Go's choice here is instead "Make Representable States Valid". So for example in Go it's not possible for a type not to have a default value - every type must have a value you get by default, you could name this unwanted default value FUCK_OFF or DO_NOT_USE if you like, but in a larger system (where Go is supposed to thrive) you can be certain you'll find FUCK_OFF and DO_NOT_USE values in the wild.

This is one of the few things the C++ type system almost gets right. You really can say for your C++ type no, it doesn't have a default. Unfortunately exception safety means we can easily end up in a place where too bad it's in some state anyway, but if we turn off exceptions or discount them we can live without nonsensical defaults.


> This is one of the few things the C++ type system almost gets right. You really can say for your C++ type no, it doesn't have a default.

Rust does even better here, I think, for fairly subtle reasons:

- Exceptions are replaced by Result, with explicitly-marked return points. They're far less magic. ("panic!" is still magic, but it's allowed to be a call to "abort", so you only use it when the world is burning.)

- "Move semantics" by default makes a lot of edge cases cleaner. If you move out of an object by default, it gets consumed, not set to a default value. Similarly, moving values into a newly-constructed object can't fail, because it's just memmove.

- Object construction is atomic from the programmer's perspective.

- Default initialization only works if you implement "Default". Which is actually just a normal trait with no magic.

- If you discover that you have a state machine, you can switch to discriminated unions ("enum") to make your states mutually exclusive and clearly represented.

This whole area is one of the better parts of Rust's design.


> Default initialization only works if you implement "Default".

And -- and I think this is important -- even then, you have to explicitly assign the default value[0]. You won't just somehow magically get a default value.

[0] E.g.:

    let foo: SomeType = Default::default();


Maybe something like CppFront can allow metaprogramming such that the default constructor is deleted by default on all types and becomes opt-in.


> Just remember, if you're ever debugging something and some values in a Class/Object/Group of them are set and others are unset the state machine avoids that issue.

You don't want a state machine for that, you want sum types. Using state machines give you the worse problem of not being able to tell how you got into to a given state, not being able to take meaningful stack traces, which is generally an even worse problem.




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

Search: