fizruk / telegram-bot-simple

Easy to use library for building Telegram bots in Haskell.
BSD 3-Clause "New" or "Revised" License
108 stars 54 forks source link

Why BotM has to end with an action? #41

Closed cblp closed 2 years ago

cblp commented 3 years ago

It looks like ending BotM with pure NoAction is boilerplate. As well as handleAction NoAction = pure model.

So, an extra action is always issued.

And user Action is always ~ Maybe Action'.

At the same time, botAction allows for the absence of action with Maybe in its signature.

Is Eff really needed? How to send a message without an extra action?

cblp commented 3 years ago

I wrote some helpers for myself

    botAction :: Update -> Model -> Maybe (Maybe Action)
    botAction update model = Just <$> onUpdate update model

    botHandler :: Maybe Action -> Model -> Eff (Maybe Action) Model
    botHandler maction model =
      case maction of
        Nothing     -> pure model
        Just action -> onAction action model

(<#) :: model -> BotM a -> Eff (Maybe action) model
m <# b = withEffect (b $> Nothing) m

They don't help with extra iteration, but remove non-sense code.

fizruk commented 3 years ago

It looks like ending BotM with pure NoAction is boilerplate. As well as handleAction NoAction = pure model.

I agree, ergonomics might be slightly better, however I don't think using Nothing instead of NoAction is better.

At the same time, botAction allows for the absence of action with Maybe in its signature.

Ideally that Maybe should actually be something more sophisticated, e.g. Either UpdateParseError. So Nothing there does not have the same meaning as NoAction.

Is Eff really needed?

Eff ensures separation of "impure" BotM actions from pure model updates.

How to send a message without an extra action?

You can use the helper (<#) that you wrote, with Nothing or NoAction depending on your preference :)

cblp commented 3 years ago

Ideally that Maybe should actually be something more sophisticated, e.g. Either UpdateParseError. So Nothing there does not have the same meaning as NoAction.

Why? How can the framework handle this instead of user code? I can imagine type Action = Either UpdateParseError GoodData, but no -> Either UpdateParseError Action

Eff ensures separation of "impure" BotM actions from pure model updates.

But why [BotM action] instead of BotM ()?

Separation can be achieved through handleAction :: action -> model -> (BotM (), model)

How to send a message without an extra action?

You can use the helper (<#) that you wrote, with Nothing or NoAction depending on your preference :)

Sorry, but no, that Nothing is inevitable re-issued and triggers handleAction an extra time.

fizruk commented 3 years ago

Why? How can the framework handle this instead of user code?

Even if the user handles this, I think there should be a separate place for it.

But why [BotM action] instead of BotM ()?

The idea was that each BotM action in that list is sort of independent and "atomic" in some sense. A single action might trigger several other things to be done (asynchronously). For example, if the user asks bot something, it can reply instantly, while processing the request in the background and maybe also updating status once in a while. That's an action triggering 3 other things (also as actions), one of which is repeated until the job is done. Current implementation runs BotM actions sequentially and puts second generation actions in a queue which is then processed in the background, but that's an implementation detail.

Sorry, but no, that Nothing is inevitable re-issued and triggers handleAction an extra time.

I do not understand what you mean by "Nothing is inevitable", sorry. Regarding handleAction being triggered one extra time: first of all, this is not directly a problem (you have a check for Nothing/NoAction somewhere anyway), but I can see how putting lots of NoActions into a queue might not be ideal; secondly, this is a performance issue, which was never really considered in this library (the goal was to only present a TEA-like architecture for Telegram bots with some interesting higher-order features like bot transformations, see conversationBot).

On the other hand note that NoOp message/action used in many apps written in Elm (and miso in Haskell) is very similar to NoAction (of course NoAction was inspired by NoOp).

cblp commented 3 years ago

A single action might trigger several other things to be done (asynchronously).

Well, I didn't expect an asynchronous job framework in a simple bot library.

And it seems to miss exceptions.

For example, if the user asks bot something, it can reply instantly, while processing the request in the background and maybe also updating status once in a while.

As for me, forkIO and async suit better.

cblp commented 3 years ago

I do not understand what you mean by "Nothing is inevitable", sorry.

When I want to handle just one message and reply to it, I have to emit NoAction after reply, and then I have to handle that NoAction.

One can see in the debug log:

Incoming: SomeAction...
Incoming: NoAction
swamp-agr commented 2 years ago

Should be fixed in https://hackage.haskell.org/package/telegram-bot-simple-0.4.