Improving the ASK Dialect (and replacing INPUT)

#1

I have been wanting ASK to become more clever than it is today, e.g. to take a dialect:

>> ask ["Operation failed." <(A)bort> <(R)etry> <(I)gnore>]
Operation failed. (A)bort, (R)etry, (I)gnore? A
== #Abort

That's an imaginative idea which would let you use TAG! to signal things to prompt for, and give you back an ISSUE! synthesized from it.

But whatever it is, I'd been thinking that there should be some way of replacing the 0-parameter INPUT with it. Because I hate INPUT being taken for this, it's better for a variable name (we wouldn't want OUTPUT to be a function by default would we?)

But when you don't want any prompt, I wasn't particularly happy with any of:

  • ask _
  • ask []
  • ask blank

Then @gchiu brought up the idea of type filtering, which leads to a pretty good answer for unfiltered text input with no prompt. ask text!. That sounds like a winner to me, and I say regardless of the rest of the design let's go ahead and get rid of INPUT and use ASK TEXT!.

But then...what about the rest of the design?

Can the user cancel the input without canceling the script? e.g. hitting "Escape" returning NULL in some interface.

 ask text! else [print "You indicated you didn't want to give any input"]

If NULL signals that, is there a different signal for errors? Or does the ASK implicitly keep harassing the user until they pass the validation, or hit escape to give NULL?

>> ask ["Enter a number:" integer!]
Enter a number: Q
** Invalid Input
Enter a number: 10
== 10

This seems like a fun dialect to design, so someone should get on it! In the meantime, INPUT is dying, use ASK TEXT!...

1 Like
#2

A further thought or question on ASK's behavior is if it might be tricky and look to its left, and if its left is a SET-WORD! then use that to cue what it's asking you for:

>> number-of-variants: ask integer!
Enter number-of-variants: 

Using <skip>-able parameters, it could still have a fall back if it can't find a SET-WORD! to its left:

>> do compose [x: (ask integer!)]   ; now ASK sees nothing to left
Enter integer!: 

And clearly: if you provide a custom prompt in the ASK, it should override the guess for lazy people that would be based on the variable name.

#3

I like ask and would rather use it instead of input

Maybe this is a bit too much, but what about something that could support these variations ...

ask text!
; no prompt, validates input is the correct data type

ask [text! integer!]
; no prompt, validates input is one of the correct data types

ask "Enter a response: "
; has prompt, no validation

ask ["Enter a number: " integer!]
; has prompt, validates input is the correct data type

ask ["Enter a response: " [text! integer!]]
; has prompt, validates input is one of the correct data types

ask ["Operation Failed. (A)bort, (R)etry, (I)gnore: " ["A" "R" "I"]]
; has prompt, validates input is one of the correct choices

ask ["Enter a number from 1 - 10 or (R)andom: " [integer! "R"]]
; has prompt, validates input is one of the correct data types or choices

ask ["Enter a number: " integer! "You did not enter a number."]
; has prompt, validates input is the correct data type, and displays a custom message when it doesn't

ask ["What do you want to do next? " parse-response]
; has prompt, validates input with a custom function you provide
; returning false keeps the prompt looping, true exits it

So ask would accept one parameter that is a datatype!, text! or block!. If the parameter is a block! then different rules would apply depending on the data type in each position.

1 Like
#4

Pretty much the kind of thing I had in mind!

I think it's cleaner to have the space be implicit. If people are sure what they're dealing with is a console and truly need more control they can still write-stdout (to be write stdout a.k.a. write system/ports/output), and then ask text!.

By a similar token, I wonder if the colon should be implicit?

One thought I had was that if "choices" of this nature were abstracted out more as an "intent" in the dialect, that they might use alternate input methods like showing buttons. Reverse-engineering that intent from a very console-specific UI like this would be harder.

On the other hand, the fact that I hadn't figured out a notation for that has stopped a simpler version of this from being implemented. So...

In terms of keeping things down to a good composable box of parts, I'm not sure how much we should stuff into the dialect vs. making this clean to do in the language itself.

until [parse try ask "What do you want to do next?" [
    some | rules | here
] else [
    print "That wasn't a good answer, try again!"
]]

But seems to me the critical question here is about canceling the ASK without canceling the script. HALT or Ctrl-C are beyond the realm of CATCH.

Should the only way out be if you put cancellation in as one of the in-band responses? Or is the standard of NULL back from ASK a better way...and then have the escape key or some other UI mechanism able to "soft cancel"?

Given everyone seems to like it and no one has spoken up to defend INPUT...

I'll go ahead and switch over to support for ask text! and ask "Prompt" soon. But this brings up that we need to get some way of introducing deprecation warnings, so things like INPUT can vanish over a period of time, giving people at least an opportunity in their scripts to say input: does [ask text!] even if they don't feel like updating all of their code (or prefer INPUT for some reason).

At minimum that means...write-stderr, where you can start getting some JS console log warnings and stderr output in a terminal.

2 Likes
#5

I agree.

No, I think "?" would be another common one to use, and I'm sure others will come up too.

I like where you're going with this, but it would need some further thought/discussion on a proper syntax. I think at least getting this simpler version working is a good first step.

Yea, true. This was sort of a last minute thought I had. It might be nice to have, but could be handled by the language too, so maybe better to keep things cleaner.

Maybe there is a use case I haven't run into before... but personally, I don't see the need to allow the user to cancel out of a prompt. It is there for a reason and providing no input could break the logic that follows, and that is an unexpected outcome that you'd always have to handle.

1 Like
#6

It might be that a cancellable ASK is a separate intent, whereas keeping you locked in a loop or cancelling out of the entire script is the default.

But I think it's a common enough intent to formalize and speak the "soft failure" protocol of returning a NULL. People have certain UI behavior they expect out of cancellation...even if there's a cancel button, they tend to want escape to do what that button does.

The reason I feel like NULL on canceling ASK could be good as a default is precisely because it would trigger a failure down the road on an unset variable if you didn't handle it. The user can opportunistically say "Hey, I don't want in on this particular option, get me out of here" and if the script can handle it, it will. Otherwise a failure will get them no worse off than if they had Ctrl-C'd out of the entire script.

And being able to write:

until [x: ask ...]

Seems kind of like a reasonable way of saying "This isn't cancelable, I'm going to keep asking you until you halt the entire script or give me a value."

1 Like
#7

I've taken the first step. INPUT is left for the time being as a synonym for ASK TEXT!:

For the moment it preserves the cancellation code that was in INPUT as-is. We can discuss the ramifications of that, and if perhaps ask* should be some lower-level cancellable ASK, or similar.

This has uncovered a latent problem, that apparently before, INPUT with no text already on a line to act as a prompt did not work. Another thing for the REPL-TEST suite to test...!

While ASK is a small thing, I think that it really does touch upon some of where this methodology aims to go... there's something kind of profound about it, sort of.

2 Likes
#8
Ask [char!] 

For hit any key ...

#9

Seems a nice idea, but...

...standard I/O (e.g. in Linux/Windows consoles and terminals) is buffered. Your program doesn't receive any typed input until the enter key is pressed. This gives the user the chance to go back and edit.

There is nothing in the C standard about getting unbuffered input. You have to go to a platform-specific API. In the web console, we would have the necessary hooks to do it...though it does start raising some questions (like what should happen if you copy a string and try to paste it? Do you get the first character of the string?)

Also, I would think that most "hit any key" situations don't actually want to echo the key to the screen.

So there are things to consider...all added on top of the idea that breaking the pattern of asking for text and then trying to run a TO on the datatype would have cost.

What's probably needed is an abstraction for pausing (perhaps MORE ?) which translates its behavior based on the availability of either a terminal API, console I/O, or something fancier. It may not be in ASK's job description.

2 Likes
#10

As a first cut of ASK I made it keep prompting you so long as there is an error:

>> ask ["Your age:" integer!]
Your age: asdf
** Script Error: cannot MAKE/TO integer! from: "asdf"
Your age: 44
== 44

But what about...

...if there's "no prompt, validates input", then you might think you've printed your own prompt...but they'll only get the prompt once.

#11

I guess if one wants a cross platform language or accept that different platforms have different implementations