sasa1977 / fsm

Finite State Machine data structure
MIT License
357 stars 21 forks source link

on_enter, on_exit hooks #3

Closed Mazyod closed 8 years ago

Mazyod commented 8 years ago

Hello!

I really love this library, as it helped me build a game bot within a single day! I am having a small issue, though. I have multiple states _(matchmaking, gameroom) transitioning to the same state (playing). Now, the initialization code for the common state is the same (create a new game state).

I was wondering if it makes sense to have on_enter and on_exit hooks to add such required code?

Thanks! Maz

sasa1977 commented 8 years ago

I'm kind of sceptical about such handlers which echo the event style of programming. This seems implicit, and it becomes harder to reason about the code. Could you simply extract the common code into a function and call it from all the places? Would that work?

Also, I feel I should note that I don't suggest using this library. I've explained some reasoning in this mailing list thread. The thread is quite long, so TL;DR of it is that I feel we can get the same functionality with plain pattern matching and maps/structs. The code will perhaps have a bit more boilerplate, but it will also not be burdened with custom non-standard macros.

This library was built before we had maps/structs, so then it perhaps made a bit more sense. My feeling today is that plain idiomatic Elixir should usually work as well (or nearly as well). That being said, the library is still maintained. I'll keep it in sync with future Elixir versions, and accept PRs when I think they make sense.

Mazyod commented 8 years ago

Whoa, if anything this library provides the necessary structure for writing FSMs for a totally clueless developer like myself. I don't think I can let go of the library so easily at this point, but I also appreciate the reasoning behind the proposed functionality.

Honestly, I am using this FSM in a testing part of my product, so my top priority is DRY and small footprint code. I was willing to sacrifice readability, since again, I'm just trying to get these tests running for now.

Either way, your library is greatly appreciated, and I think it still serves a good purpose. I will have to consider your alternatives for FSMs in the server code itself.

sasa1977 commented 8 years ago

Hey,

I'm glad you like FSM, and feel somewhat strange that I'm arguing against my own library. Don't get me wrong, feel free to use it - that's why it's here, and I'll still maintain it. Just wanted to recommend that it's good to grow accustomed to plain Elixir idioms before considering moving to this DSL. Essentially, the same advice I gave in the ExActor library, where I suggested people first learn GenServer before going to custom macros.

To make the idea more concrete, this snippet could be rewritten with plain functions and maps as:

defmodule BasicFsm do
  def new, do: %{state: :stopped, speed: 0}

  def run(%{state: :stopped}, speed), do: %{state: :running, speed: speed}

  def stop(%{state: :running}), do: %{state: :stopped, speed: 0}

  def slowdown(%{state: :running, speed: speed} = fsm, by),
    do: %{fsm | speed: speed - by}
end

Arguably it is somewhat noisy (and it can get even noisier if you use structs), but it is also more "down to earth", i.e. not clouded by a macro-powered indirection. This gives you some nice benefits.

  1. You can be confident about what the code does. There are no layers - it's just plain vanilla Elixir :-)
  2. You get the full power of the Elixir language - something which is probably not possible with FSM macros. Of course, I tried as much as possible to support all the features I could think of (e.g. pattern matching), but there's always a chance I missed something. Plain Elixir will by definition support all the features of plain Elixir :-)
  3. Finally, relying on plain Elixir is a good way to learn and practice the language :-) Macros in this library implement a simple DSL, and are not useful beyond the scope of FSM. Plain Elixir constructs are useful in all sorts of situations. Even if you decide to use FSM, it makes sense to try with plain Elixir first, so you can get a feeling of how would it look.

Hope that makes some sense. Regardless of the approach you choose, I wish you best of luck and a lot of fun :-)

Mazyod commented 8 years ago

You're almost breaking my heart now, since I've actually used ExActor in the production app, haha.

As I'm sure you're well aware, the whole dependencies argument has been going on for decades, and people tirelessly push back and forth two concepts:

"don't reinvent the wheel" "don't bloat your app with unpredictable dependencies".

Indeed, this argument was raised in the very informative discussion you shared earlier regarding some developers demanding for a maintained FSM implementation vs the argument against that.

My stand point is, I can take as many self-contained dependencies, as long as they obviously provide value, and are simple enough to read through and understand. ExActor and FSM are great examples. I noticed a boost in productivity not having to handle GenServer.cast, then trying to remember what parameters handle_cast takes as opposed to handle_call. The library itself is self-contained, and as far as I can tell, hardly anything could go wrong. After all, I am unit testing it as well.

I would like to present why I believe in this so much. I'm an iOS developer, and countless times I found myself repeating the same mistakes: rewriting tedious code, and missing deadlines because of monolithic dependencies. My solution was Kitz. Small collection of self-contained libraries you can easily drop in, or drop out, that solve a very small problem to help you be a little bit more productive.

Finally, I have to admit, the bigger the team grows, and the longer the life-span of the project, the less dependencies you should have. In my case, I am a single developer working on 2-3 projects per year! I feel I can never go through all these projects without the undying spirit of the open source community :D

I really don't want to take more of your time, but I'm learning a lot here, so thanks!

sasa1977 commented 8 years ago

Hey,

Just to be clear, I'm also a fan of smaller self-contained deps. Somehow such deps feel less magical and I feel I have a better grasp on what they're doing, so I can still reason about the code.

My point about FSM and ExActor were about something else:

  1. I suggest first getting accustomed to working with plain Elixir, simply to learn the proper foundations. Then if you decide there's too much boilerplate, move to ExActor/FSM. I don't know what's your Elixir/OTP level, but if you know how to work with plain GenServer, then using ExActor is IMO quite fine. I'm just cautioning against using ExActor without learning GenServer first.
  2. In the case of FSM, I don't think that macros add enough value to be justified. That's just my opinion though. If you feel FSM makes your code more readable, then feel free to use it :-)

Thanks for the discussion, it also helps me to consolidate my thoughts on the subject.

Happy coding!