Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make strings uncallable #552

Closed
xiaq opened this issue Jan 1, 2018 · 7 comments
Closed

Make strings uncallable #552

xiaq opened this issue Jan 1, 2018 · 7 comments
Labels

Comments

@xiaq
Copy link
Member

@xiaq xiaq commented Jan 1, 2018

Strings are callable in Elvish. This is an obvious strategy to support traditional command forms like echo this and vim that, in which echo and vim are strings. This can be problematic with Elvish's lexical scoping rules and lead to some confusing results.

Suppose a module m contains the following:

fn call-f-foobar [f]{
    $f foobar
}

That is, call-f-foobar takes a function and calls it with foobar. A user of the module may try to do the following:

use m
fn f [x]{ echo "called with "$x }
f foobar # will output "called with foobar"
m:call-f-foobar f # won't work

The last but one command works because when Elvish tries to resolve f, it finds a function named f in the current scope and calls it correctly. The last command won't work, because the string f is passed to call-f-foobar as is; when call-f-foobar tries to resolve the command f, Elvish cannot find a function called f in its scope and fails.

To make things more confusing, m:call-f-foobar put will work; this is because put is a builtin command and can be correctly resolved everywhere. This discrepancy between builtin commands and user-defined functions is quite unpleasant and can mislead users.

Instead of making strings callable, we can special-case literal strings when evaluating command forms. That is, commands like echo this and vim that continue to work because the commands are literal strings. However, code like cmd = echo; $cmd this will no longer work; this is intended to prevent mistakes outlined above.

@zzamboni
Copy link
Contributor

@zzamboni zzamboni commented Jan 1, 2018

I'm not sure I agree with this. I think the ability to "call" variables is an important one. Losing the ability to do put commands in variables and call them like in $cmd this would severely detriment Elvish's functional programming abilities. Unless I misunderstood something?

@xiaq
Copy link
Member Author

@xiaq xiaq commented Jan 2, 2018

This does not affect the general ability to call variables. For instance, x = { echo x }; $x will continue to work, so will x = $put~; $x. What won't work is x = put; $x, because strings per se are going to be uncallable, and a special rule for allowing literal strings to behave as if callable will be in place.

@jiegec
Copy link

@jiegec jiegec commented Jan 2, 2018

Maybe we can introduce funcall(Okay I came from GNU Emacs world) or cmdcall to explicitly call strings.

This issue is just about choosing between Lisp-1 and Lisp-2 Hahhhh

@xiaq
Copy link
Member Author

@xiaq xiaq commented Jan 2, 2018

@jiegec The idea is exactly to forbid calling of strings because it leads to confusing results outlined above and introducing a way for calling strings defeat the purpose. The most common usage of using literal stings is introduced as a special case.

This issue is also different from the problem Lisp-2 has. Lisp-2 has two namespaces for values and functions, but both namespaces can be lexically scoped.

Emacs Lisp is dynamically scoped.

@jiegec
Copy link

@jiegec jiegec commented Jan 2, 2018

Emacs Lisp has lexical scoping already. Just need to manually toggle it file-wise.
I prefer to add a layer of indirection for calling strings, even without having two namespaces for values and functions.

@zzamboni
Copy link
Contributor

@zzamboni zzamboni commented Jan 2, 2018

@xiaq How about the idiom (common in scripting) of allowing assignment of commands to variables to support different systems independently of PATH settings, e.g. something like this:

GREP = /usr/bin/grep
...
$GREP foo /bar

Not sure if this is a strong enough argument, but it's probably worth asking.

@xiaq
Copy link
Member Author

@xiaq xiaq commented Jan 2, 2018

Good point; I added an external builtin to construct callable external commands explicitly. Your example can now be written as GREP = (external /usr/bin/grep). I would recommend you to use grep~ = (external /usr/bin/grep) instead, as you will be able to call it simply with grep.

It's perhaps worthwhile to clarify the bigger picture. After the change, it is still possible to use strings as commands, provided that it is a string literal. When used as a command, a string literal cmd is either equivalent to $cmd~, or failing that, (external cmd). If you want to "use strings as commands", you should use one of those. Examples:

f~ = [x]{ echo '$f~ '$x }
f foo # equivalent to "$f~ foo"
fn g [x]{ echo 'g '$x } # defines $g~ under the hood
g foo # equivalent to "$g~ foo"
vim foo # equivalent to "(external vim) foo" because $vim~ does not exist
@xiaq xiaq closed this in 2e30264 Jan 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants
You can’t perform that action at this time.