Closed jpco closed 3 days ago
Okay, I have just realized that this is called, in most functional languages, a "pipe operator" (like https://elixirschool.com/en/lessons/basics/pipe_operator). Obviously it shouldn't be called that in a shell, but the concept is the same (and somewhat distinct from function composition operators).
Yeah the pipe operator (aka reverse application) is a popular feature of functional langs like F#, OCaml, Elixir, Elm.. First seen in Isabelle and popularized with F#.
I will be throwing tomatoes not discussing code, apologies! They will be small and sweet though as I'm mostly in agreement :)
There's another approach which more resembles your de-sugared version, called threading, in Clojure (https://clojure.org/guides/threading_macros) I think it's nicer this way for es purposes, because it feels more contained, and doesn't interact too much with the shell dsl syntax -- it's just a builtin function. In es, returns are mostly useful as a language (not interactive shell) feature, so I think threading should be too, if that makes sense. We have builtins that don't have corresponding syntactic forms already, like %flatten and %split.
FWIW composition is just threading/reverse-application with the initial argument missing:
f = g ∘ h
f x = x ▹ h ▹ g
in es, where partial-application isn't automatic and functions are variadic, threading makes more sense.
This raises a question which is relevant in es but not in any of those langs: how should piping work with these variadic functions? should $1
be passed? $*
? $*($#*)
?
In es, returns are mostly useful as a language (not interactive shell) feature, so I think threading should be too, if that makes sense. We have builtins that don't have corresponding syntactic forms already, like %flatten and %split.
You're most likely right about this (though I personally use return values in an interactive context fairly often -- but I'll admit I'm probably not the average user!).
My main problem with the Clojure threading-type setup is that, without support in the parser, here's what you get:
; fn-%pass = $&noreturn @ v cmds {
for (cmd = $cmds) $v = <={$cmd $$v}
}
; local (words = one two three) %pass words %count echo
3
This is all well and good, but as soon as you move beyond single-word expressions, it starts looking like
; local (words = one.two three.four)
%pass words @ {%split '.' $*} @ {%flatten '-' $*} echo
one-two-three-four
which, with all the lambdas and explicit arguments, is fairly noisy to my eye. It may not be so bad as to justify extra syntax, though.
(If it's relevant, here's the equivalent command using =>
as implemented here)
; let (words = one.two three.four)
%split '.' $words => %flatten '-' => echo
one-two-three-four
It would be very neat if this could be a proper "function-composition" operator such that this
fn-whatis = %whatis => echo
Okay turns out this is actually really easy to add, at least in a vaguely-working form, to the existing code in this PR:
/* in parse.y */ assign : caret '=' caret words { $$ = $4; } | caret '=' caret words PASS words { $$ = mkpass($4, $6); }
# es ; fn foo {result FOO} ; fn-bar = foo => echo ; whatis bar %pass {foo} {echo $-} ; bar FOO
Super neat -- proper function composition! Now I'm way out in total brainstorming mode, though. This would only make sense as part of some general "advanced assignment syntax" feature which included other things like
fn-blah = foo | bar
and other syntactic constructs --- and REALLY I think it would make sense only within a user-definable syntax system.
I have changed my mind and no longer want this in the shell :)
This would be new syntax for the shell -- begin throwing tomatoes as desired. People seemed mildly interested in the idea in #53 so here it is.
Note: This shouldn't be merged as-is! At the very least it would need documentation!
To demonstrate the syntax via an example, take the following command:
This is essentially equivalent to
The command with the
=>
syntax is desugared into the followingwhere
%pass
is defined asBecause of the use of a dynamic binding for
-
, you can also put$-
in the middle of subcommands (and if you like, wrap a subcommand in a thunk to prevent appending the$-
at the end)though it's really more hygienic to use a lambda:
I took as much care as I could to make this interact correctly with redirections.
I also made it permissive with other syntax, like assignment here:
This assignment is odd to read, but can come in handy. I would respect wanting to make at least some of the syntaxes into errors instead. These are all valid:
It would be very neat if this could be a proper "function-composition" operator such that this
worked (producing something equivalent to
fn-whatis = @ *{echo <={%whatis $*}}
), but that would require additional parser magic, and starts to look like a whole different language at that point.