eniagreen / botml

Powerful markup language for modern chatbots.
https://codename.co/botml
MIT License
101 stars 20 forks source link

conditional logic problems #42

Open dcsan opened 5 years ago

dcsan commented 5 years ago

I made a simple "survey" type bot, but the conditionals logic doesn't seem to work like expected. .bot script is here: https://github.com/dcsan/botml/blob/master/examples/survey.bot

I run it with nodemon --ext js,bot lib/cli.js examples/survey.bot

~ survey
< OK lets go
~ [color]
~ [age]
# ~ [name]
# ~ [birthplace]
< OK thanks!
~ [status]

~ color
< What's your favorite color?
> *{color}
---
  > purple
  < Do you like Prince?
---
  > blue
  < Don't be sad!
---
< OK you like ${color}

but I get a result like this on running it:

< OK lets go
< What's your favorite color?
> red
< Do you like Prince?
>
dcsan commented 5 years ago

I think its down to trying to capture the user's input chosen color here:

~ color
< What's your favorite color?
> *{color}
dcsan commented 5 years ago

but when i remove that there's no way to capture the color in a variable for later, eg:


~ color
< What's your favorite color?
---
  > purple
  < Do you like Prince?
---
  > blue
  < Don't be sad!
---
> *{color}
< OK you like ${color}

if they say blue or purple I don't know of a way to capture that variable.

dcsan commented 5 years ago

also there are some problems 'returning' from a conditional block

loaded in: 8.798ms
< OK lets go
< What's your favorite color?
> blue
< Don't be sad!

(now we're stuck, flow doesn't return from that block, and no other options get picked up either)

> cyan
WARN:  [botml] No matching dialogue found.
> blue
WARN:  [botml] No matching dialogue found.

however if I used an unknown color that fell through to the end it would work

> survey
< OK lets go
< What's your favorite color?
> asdfdsf
< OK you like asdfdsf
< How old are you?
>

so a bit puzzled on a few things here.

arnaud commented 5 years ago

Hey DC

We're currently working on bubl, a codepen-like solution dedicated at designing botml-enabled bots.

It's not polished just yet, but could improve your workflow by letting you test it in a REPL way, validate your scripts and observe all the conversational paths dynamically.

Could you test your script there?

dcsan commented 5 years ago

Got it, I saw that on your notion page.

The whole script is in the URL then? Hope that works :) I can see the same problems

https://dev.bubl.es/#%3E%20start%0A%3C%20Lets%20go%0A~%20[colors]%0A~%20[ending]%0A%0A~%20colors%0A%3C%20Whats%20your%20favorite%20color?%0A%3E%20*{color}%0A---%0A%20%20%3E%20red%0A%20%20%3C%20roses%20are%20red%0A---%0A%20%20%3E%20blue%0A%20%20%3C%20sky%20is%20blue%0A---%0A%3E%20*{color}%0A%3C%20you%20like%20$color%0A%0A~%20ending%0A%3C%20Thanks%20and%20goodnight.%0A

image

so when the extra capture is there

~ colors
< Whats your favorite color?
> *{color}
---
  > red
  < roses are red
---

everything will get captured there. So actually it looks like I misunderstood that usage, I got it from your email example

https://github.com/codename-co/botml#conditional-branches

~ ask_for_email
> start email workflow
~ listen_email
< Your email please?
> *{email}
---
  ` !/^.+@.+\..+/.test('$email')
  < This email $email seems not legit!
  ~ [listen_email]
---
< Cool. We'll reach you over at $email

but I guess in that case you are then capturing and doing tests later against the variable itself.

So this method works, it's just quite ugly to write:


~ colors
< Whats your favorite color?
> *{color}
---
  ` 'red'.test($color) `
  < roses are red
---
  ` 'blue'.test($color) `
  < sky is blue
---
> *{color}
< you like $color

So then the question is how to capture variables if just using normal expression matching without regexes

arnaud commented 5 years ago

It seems clear that the conditional branches should be more documented.

You are right: there are two ways of using them, and a few things to know beforehand.

The first thing to know is that in botml, any block is linear, meaning there is a single start and a single end. Except for conditional branches, where we can conditionally fork into external paths.

To me the best example that describes it is the following authentication flow:

~ authenticate
< What is the password?
~ password-ask
--
  > secret
  ~ [password-right]
--
< No, that is not the password. Try again.
~ [password-ask]
~ password-right
< Access granted.

Here, we see that very linear flow that relies on a breakpoint ~ password-ask to loop (via ~ [password-ask]).

But there is an edge case: which is actually knowing the passcode. And here we fork to an "external" branch (here it's at the end of the block, but it could be the reference of another workflow.

This is the primary way of using conditional branches, where the capture/test is done at each step and enables the more flexibility.

The second thing to know is that, because blocks are linear, they will "return" into their parent flow (if any) only when their main flow is reached; not the edge cases.


Another way of using conditional branches is to capture first, like in the docs:

~ ask_for_email
> start email workflow
~ listen_email
< Your email please?
> *{email}
---
  ` !/^.+@.+\..+/.test('$email')
  < This email $email seems not legit!
  ~ [listen_email]
---
< Cool. We'll reach you over at $email

The best use case for this type of writing are validations. In such cases, we want to capture first, and fork to a branch only after a certain type of test.

We found this way of handling tests and validations way clearer. And very handy in production for handling real complex use cases.

But this can be subject to discussion, and I can understand that it may not the most clearer choice.

dcsan commented 5 years ago

The second thing to know is that, because blocks are linear, they will "return" into their parent flow (if any) only when their main flow is reached; not the edge cases.

when you say "edge case" you mean when any condition is met other than the default/fall-through? So I guess I understand, but that means there is no way to return if you match anything. So I'm not sure what the use case would be beyond simple Q=>A scripts. Managing state / conditionals / control flow with botML seems very difficult?


The other method you've shown is basically writing javascript inside ``` blocks. So switching back and forth between the two, where all logic is actually JS. That seems a bit messy... Also that doesn't actually allow blocks to be treated as return from subroutines.

Did you think of other ways to handle conditionals?

I'm sure there's a bot ruby-like DSL that has the basic flow-control of a script language, but without all the extra {( )} noise of a script. maybe it's just a coffeescript like transpile to JS...

For example if a line did NOT have one of the special symbols could it just be treated as JS?

if $input === 'password'
< good job!
return

or

:askpw
< whats the password
> *input

switch $input
  when 'password'
    < you got it!
    return
  when *
    < not quite
    go :askpw

for one of our YAML interpreters we ended up using a mongo like matching syntax, which was easy to parse but really ugly to hand-write.

- if { input: { $eq: "password" } } 
dcsan commented 5 years ago

for reference: https://www.rivescript.com/docs/tutorial#conditionals

+ what am i old enough to do
* <get age> == undefined => I don't know how old you are.
* <get age> >  25 => You can do anything you want.
* <get age> == 25 => You're old enough to rent a car with no extra fees.

conditionals aren't too bad and do allow you to also redirect

* <get age> >  25 => @tooYoung

if they just got rid of that XMLy stuff and had $age > 25 => @sub