Live data from Hacker News

Gem adds support for creating state machines for attributes on any Ruby class

github.com

31–40 of 50 posts

Re: Gem adds support for creating state machines for attributes on any Ruby class

#31
post #2

I think Ruby is OK enough, but what is this? state_machine :state, initial: :parked do I think in their zeal to "golf" the language, they forgot to actually keep it readable

I've been writing Ruby for about 16 years, and I find that eminently readable. You're apply state_machine to the attribute state , and giving it an initial value of :parked . Then there's a block that ostensibly encapsulates state transitions following (hence the do ). Ruby can be written extremely tersely, and that can be difficult for non-Rubyists to read. This might help: state_machine(:state, {:initial => :parked…

Both of these gems just feel unreasonable verbose to me. E.g. why "transitions from: :somestate, to: :otherstate" vs just "transition :somestate => : otherstate"?

A lot of the syntax of these feels over-engineered, or like someone has just read a Smalltalk introduction and is overdoing keyword arguments to emulate it.

If I'm going to use a DSL to define a state machine, it better be a lot terser and/or clearer than a method with a case block.

E.g.

    det state = @state || :initial_statr

    def state=(event)
      case [state, event]
      in [:somestate, :someevent]
        someaction
        @state = :newstate
      in ...
      ...
      end
    end
or case event ..., or case state ..., depending on the "shape" of your state machine.

("in" vs "when" here largely depends on whether you want to use any of the newer, fancier pattern matching allowed with "in"; I'm not sure it matters here)

I could see wrapping up those two methods in a single small helper, and maybe a little bit extra sugar to make it easier to decompose large state machines, but it better be a complex set if transitions before I'd not throw a fit over the use of these gems in a code review.

(Yes, I can see they offer a lot more information about the states and transitions, but I'd argue they offer far too broad APIs for it by default, and you'll end up going hunting for method definitions that don't exist all over the place because of all the methods it injects)

In particular the whole before/around/after transition screams at me to apply caution to prevent people having to hunt around to figure out the total set of possible effects of a transition.

Re: Gem adds support for creating state machines for attributes on any Ruby class

#32

Earlier quoted context omitted.

I've been writing Ruby for about 16 years, and I find that eminently readable. You're apply state_machine to the attribute state , and giving it an initial value of :parked . Then there's a block that ostensibly encapsulates state transitions following (hence the do ). Ruby can be written extremely tersely, and that can be difficult for non-Rubyists to read. This might help: state_machine(:state, {:initial => :parked…

Positionally-significant colons is more nightmarish than significant whitespace. Speaking as a critic of significant whitespace. Put them at the front and make them mean something, or put them at the back and make them mean something, Making both places mean something entirely different, or even allowing both as an allowable syntax, is horrible for readability.

It seems like you have the concept down pat. I don't see what's wrong with it?

Like lots of languages use positionally-dependent symbols. In both Perl and VB various characters in the variable name implied things about its type. With C/C++ the position of * tells you lots of important things. How is this anything less than yet another random language quirk?

Re: Gem adds support for creating state machines for attributes on any Ruby class

#33
post #31

Earlier quoted context omitted.

I've been writing Ruby for about 16 years, and I find that eminently readable. You're apply state_machine to the attribute state , and giving it an initial value of :parked . Then there's a block that ostensibly encapsulates state transitions following (hence the do ). Ruby can be written extremely tersely, and that can be difficult for non-Rubyists to read. This might help: state_machine(:state, {:initial => :parked…

Both of these gems just feel unreasonable verbose to me. E.g. why "transitions from: :somestate, to: :otherstate" vs just "transition :somestate => : otherstate"? A lot of the syntax of these feels over-engineered, or like someone has just read a Smalltalk introduction and is overdoing keyword arguments to emulate it. If I'm going to use a DSL to define a state machine, it better be a lot terser and/or clearer than a…

On why not to use "transition :somestate => :otherstate", it would be because then other optional arguments might conflict with the state names. For example, if the `transition` method takes an optional argument of `given:` then a state machine could not have state called `given`. In fact, no optional arguments could be added in a backwards compatible way.

The states could be passed positionally but, imo, the increased verbosity helps readability rather than hinders it.

Your example code seems not really thought through. If I were to implement that, then I would write code that looked like this: `my_thing.state = :some_action; assert my_thing.state == :some_new_state`. Now we're spending company money discussing whether the state machine code you decided to write by yourself has a reasonable interface.

I've used aasm for years and it's just fine. If someone suggested we just implement a non-trivial state machine with our own homegrown solution, I would push back. Trust the community's experience and just pull in the well-established library. There seems to me a certain hubris in deciding that the rest of the world has settled on an overly complex solution when, if they had just thought for a moment, it should just be a 'switch` statement.

Re: Gem adds support for creating state machines for attributes on any Ruby class

#34
post #31

Earlier quoted context omitted.

Both of these gems just feel unreasonable verbose to me. E.g. why "transitions from: :somestate, to: :otherstate" vs just "transition :somestate => : otherstate"? A lot of the syntax of these feels over-engineered, or like someone has just read a Smalltalk introduction and is overdoing keyword arguments to emulate it. If I'm going to use a DSL to define a state machine, it better be a lot terser and/or clearer than a…

On why not to use "transition :somestate => :otherstate", it would be because then other optional arguments might conflict with the state names. For example, if the `transition` method takes an optional argument of `given:` then a state machine could not have state called `given`. In fact, no optional arguments could be added in a backwards compatible way. The states could be passed positionally but, imo, the increas…

Then you explicitly pass the transitions as a hash, or provide other methods for those specific options, but part of my point was that adding so many options in the first place is part of the problem of that API. It's trying to do way too much at the same time, and the result is you're ending up with really excessive code.

My example code was a quick and dirty example; instead of state= you could use "event" or whatever name you prefer. You're having the discussion about API whether you have it explicitly or have it by having the discussion about pulling in those gems.

And the point was the verbosity of it, not the details. If you need "a solution" for a state machine, odds are your state machine is too big and convoluted and ought to be decomposed, because to me at least their examples obfuscates the state machine they're defining in a way that is a huge red flag.

I definitely would not trust "the community's" experience on this - it's by no means "the rest of the world" that has settled on this, but a tiny subset. Thankfully I've never had to deal with code using either of these gems "in the wild" in 19 years of Ruby development.

Having looked at the source of the state_machine gem, I'm now even less inclined to want it anywhere near my code. It's grossly over-engineered. A quick look at aasm makes it look slightly saner, but it's still grossly invasive and huge amounts of code.

Re: Gem adds support for creating state machines for attributes on any Ruby class

#36
post #29

I'm not sure why this is interesting enough to post. It's an old gem, and the DSL seems awfully verbose to me for how little it does. I love Ruby, but I wouldn't want to write Ruby like this...

Too late to edit this, but here's a toy example of what I'd be more inclined to like for cases complex enough that a purely case-based state machine feels too cumbersome:

https://gist.github.com/vidarh/04b7d54c94f1f03b4cf084081166d...

It's very limited compared to this gem, some on purpose, some because it's a toy.

On purpose: Only a single state machine per class; I might be inclined to offer a tiny helper to let you delegate to a (sub-)state machine, but there's little to be gained by not using extra classes as extra namespaces to contain this.

The biggest gap is the lack of a lot of little helpers that'd be easy to add if you want (e.g. "state_name?") but that I feel creates little benefit over "state == :state_name", though trivially added if you eg. define an "event(state, &block)" class method that does "define_method(state, block)" and then add whatever extra "define_method"'s you want for introspection.

The other big gap is introspection. I think if you need to be able to introspect the state machine, then chances are it's too big to start with and you might want to decompose it.

But if you absolutely need that, you can still maintain most of the simplicity of the example I gave. E.g. you can either define a helper to define events (so you'd do e.g. event(:event_name) do ... end instead of def event_name = ...), but you can also use "method_added" and a flag to e.g. wrape the event methods in an "events" block so you can do "events do ... def some_event" and still obtain a list of events.

If you do the former, you can return or instance_eval a builder object (personally, and I've done this myself, I think Ruby devs are way too quick to resort to builder/instance_eval DSLs in cases where regular methods work just fine; builders/instance_eval risks creating all kinds of unintended issues).

If you do the latter, you can have the helpers honor a "dry run" flag that records the transitions instead.

The caveat in both cases is that unlike the example in the gist you can't use the regular Ruby flow control or some transitions and conditions might be "invisible", so you might end up with something like this:

   events do
     def repair  = if_state(:stalled) do
        unless_action(:auto_shop_busy) { transition_to(:parked) }
     end
     # other events
   end
The change to the helpers to support building a state graph from this is trivial, or if you want you can even conditionally include the "dryrun" version of the helpers as needed.

Re: Gem adds support for creating state machines for attributes on any Ruby class

#37
post #34

Earlier quoted context omitted.

On why not to use "transition :somestate => :otherstate", it would be because then other optional arguments might conflict with the state names. For example, if the `transition` method takes an optional argument of `given:` then a state machine could not have state called `given`. In fact, no optional arguments could be added in a backwards compatible way. The states could be passed positionally but, imo, the increas…

Then you explicitly pass the transitions as a hash, or provide other methods for those specific options, but part of my point was that adding so many options in the first place is part of the problem of that API. It's trying to do way too much at the same time, and the result is you're ending up with really excessive code. My example code was a quick and dirty example; instead of state= you could use "event" or whate…

Too late to edit, but see also: https://news.ycombinator.com/item?id=39087098 where I give a more fleshed example of something more similar that I'd be happier with, and which you'll notice is a lot closer to aasm than the linked gem. Compared to the state_machines gem, aasm I have mostly nitpicks over the syntax of and most of my issue is that the implementation is way more complex than it needs to be.

By all means, use what you're familiar with - aasm is by far the better choice if you've first made the investment.

Re: Gem adds support for creating state machines for attributes on any Ruby class

#38
post #8
post #2

I think Ruby is OK enough, but what is this? state_machine :state, initial: :parked do I think in their zeal to "golf" the language, they forgot to actually keep it readable

This is idiomatic Ruby syntax, and anyone familiar with the language knows what that code does

This is kind of an absurd statement, given how prevalent custom DSLs are in Ruby (and in this example). No amount of familiarity with the Ruby language can tell me what this means:

       event :ignite do
         transition stalled: same, parked: :idling
       end
You have to be familiar with the DSL, not Ruby.

Re: Gem adds support for creating state machines for attributes on any Ruby class

#39
post #31

Earlier quoted context omitted.

I've been writing Ruby for about 16 years, and I find that eminently readable. You're apply state_machine to the attribute state , and giving it an initial value of :parked . Then there's a block that ostensibly encapsulates state transitions following (hence the do ). Ruby can be written extremely tersely, and that can be difficult for non-Rubyists to read. This might help: state_machine(:state, {:initial => :parked…

Both of these gems just feel unreasonable verbose to me. E.g. why "transitions from: :somestate, to: :otherstate" vs just "transition :somestate => : otherstate"? A lot of the syntax of these feels over-engineered, or like someone has just read a Smalltalk introduction and is overdoing keyword arguments to emulate it. If I'm going to use a DSL to define a state machine, it better be a lot terser and/or clearer than a…

I worked on a state machine framework in another language, and have definitely have found less terse to be pretty good. Typing a few extra characters isn't that bad, especially if it makes some awful bit of evented code easier for someone to understand.

Of the things available open source, I think P-lang is pretty cool: https://github.com/p-org/P/blob/master/Tutorial/1_ClientServ...

Re: Gem adds support for creating state machines for attributes on any Ruby class

#40
post #6

Earlier quoted context omitted.

Wait, is :state a keyword or an instance attribute? Using the same syntax for both seems really confusing.

It's a symbol. Basically a glorified string literal.

It's more like an integer with a textual name.
Post reply on HN