You’re not far, but a couple corrections:

1. While a decent, native “compose” function would skip straight to point
B, and as such would also accomplish point A, even with generics this is
cumbersome.

2. Point A describes implementation of an infix pipe-operator, something
implemented by nature of stack-based languages, but also in Bash, Elixir,
Haskell, F#, Hack, R — a closed-alpha language sponsored by NoRedInk, Roc,
even supports both left-right and right-to-left piping. Often the symbol
used to signify the pipe operator is a vertical bar, with or without an
angle brace. I am suggesting the “<“+”-“ symbol because it already has a
meaning that is similar. Point B does bring up compositionally creating a
new function, but point A is just dealing with the pipe semantics.

3. Where you write “value := (F <- G)(args…)”, you are describing the
suggestion of point B, not Point A — a visible invokation must occur, per
A, to clearly communicate that a function execution is occurring. The
distinction only matters if Point A becomes accepted without Point B.

4. I don’t recognize your point about Go being somehow unfit for the
pattern. A package written in an OOP style may benefit only rarely, but
when a package is more a library of combinators and propagators, such as in
ML and HPC, it is quite normal to encounter a pile of elipses, with some
tokens buried in the middle. I would use the heck out of this tool of
expression.

5. I fail to see the point of raising the question of calling convention. I
understand that what I’m suggesting in Point C requires new behaviors on
behalf of the assembler, and many more tools besides.

6. I again fail to see your point by complaining of errors. An error is a
value, and that is all. If one of the functions panics, emitting the
correct stacktrace would certainly be another point of cost for implementing
point C,  but for the example “v, e := f0 <- f1 <- f2(x, nil)”, if all of
the stages can fail, an effective pipeline would be composed of stages
which each short-circuit if the second argument is not nil,  returning zero
values and the received error. This is how one currently composes
pipelines. The difference proposed by point A is that it’s much more
readable.

Obviously, Points B and C depend on Point A being recognized

On Sat, Apr 2, 2022 at 5:26 PM Bakul Shah <ba...@iitbombay.org> wrote:

> If I understand you right, you seem to want to use "<-" as the "compose"
> higher order function but as an infix operator. That is, instead of "value
> := F(G(args...))", you want to be able to write "value := (F <-
> G)(args...)". Further, "F <- G <- H" means "F <- (G <- H)". F, G &H are
> functions such that F(G(H(args...)) is a valid expression, if args... is
> valid.
>
> My gut instinct says Go is the wrong language to extend for tacit
> programming. If you look at existing code, my guess is you won't find very
> many instances of code that can be mapped to tacit programming. So low
> "bang for the buck". Second the optimization that you talk about in point
> C. is likely not very important or even meaningful. Typically it is the
> *called* function that pops the stack, not the caller and unless you can
> inline things like (F<-G<-H) you can't collapse three pops into one. Third,
> in Go you can't short circuit error returns. Thus if H can return an error,
> you can't use it in a pipeline! You have to handle the error at the call
> site of H. Ideally one (or at least *I*) would want something like
> F(G(H(...)) to return to the call site of F with an erorr, without
> executing F or G in case H fails.
>
> On Apr 1, 2022, at 3:41 PM, Sam Hughes <sam.a.hug...@gmail.com> wrote:
>
> Point-free programming, or "tacit programming",  is a convention that
> highlights the intent without syntactic noise.
>
> For those unfamiliar, wikipedia:
> https://en.wikipedia.org/wiki/Tacit_programming
>
> I want better function composition. "Write a helper function to route
> values out to values in, like a normal person." Sure, but who needs Go when
> you have C, eh? A tacit sugaring merely provides a lexically distinctive
> means to indicate the intent that function f0 should be called with the
> return value of f1, and f1 of f2, and so on, with reduced syntax.
>
> There are a couple layers here, there's an easy win, a possible next step,
> and well, madness.
>
> A. Go uses a thin-arrow, "<" + "-", as a receive/send operator and to
> signify directionality of a channel. This same operator could be used like
> so, "c := a <- b()" to assert the following:
>     1. Token "a" resolves to a function pointer
>     2. Token "b" returns none, one, or many values, such that "a(b())" is
> legal.
>     3. Token "c" can legally be assigned the value of the desugared
> expression.
> This suggestion imposes ambiguity between the meaning of "(chan
> int)(x)<-y(z)" and "(func(int)int)(x)<-y(z)", and I have chaotic intuitions
> about the meaning of "(func(int)int)(x) <- (chan int)(y)", or even
> "(func(int)int)(x) <- (chan int)(y) <- z", but x(<-y) is clearly
> (func[T](T)T)(x)(<-(chan[T] T)(y)). A minimally disruptive solution, then,
> is to assert that the tip of such a tacit chain must be a full-syntax
> invokation, e.g. for "f0 <-...f<N-1> <- ?", only "f<N>(...)" is valid. This
> means expressions like "c0 <- f0 <- f1(f2 <-f3(<-c1))" are unambiguous
> without a mountain of ellipses.
>
> B. At cost of introducing a new concept for Go entirely, it would be
> convenient to declare a function as "f0 := f1 <- f2 <- f3", resolving as
> suggested by the statement: "reflect.TypeOf(f0) ==
> reflect.FuncOf([]reflect.Type{reflect.TypeOf(f3).In(0), ..<etc>},
> []reflect.Type{reflect.TypeOf(f1).Out(0), ...<etc>})". The straightforward
> path to implementation would resolve that naively, as suggested by the
> following: "f0 := func[T handWaving.inT, U handWaving.outT](a... T) (...U)
> {return f1(f2(f3(a...)))}". A statement like "go f0 <- f1 <-f2 <- f3",
> assuming "go func[T handWaving.inT](a...T) {f0(f3(a...));}" is legal, would
> be an attractive pattern.
>
> C. Naively, point B suggests that the functions thus concatenated could be
> assembled as to preallocate the stack for the entire concatenation,
> omitting the allocations and moves/copies between function calls, and
> rather writing the return values to the stack for the predecessor function,
> by analogy, like a reverse closure or a heterogenous recursion. For the
> example "f0 := f1 <- f2 <- f3", because I expect that statement to only be
> legal if "f1(f2(f3(..args..)))" is legal, the out-signature of f3 is
> assignment-compatible with the in-signature of f2, and f2 to f1. Concerns
> such as an element in the concatenation being recursive, blocking, or
> corrupting only apply to the function at stack-head; pre-allocating even a
> potentially large stack exposes only risk shared with allocating the same
> stack progressively, but with lower instructional segmentation. A possible
> but unlikely edge-case, if for some reason, a generic function cannot be
> appropriately stenciled, the optimization being suggested might only
> partially be applied, or else not applied at all.
>
> Ellipses are basically universal for "I give you control, but I expect it
> back". Loops don't give up control, rather push it on a swing like a child.
> Go-func commissions an agent, expecting no control. Point A would change
> the expression somewhat, but by requiring the head of a concatenation to be
> an execution, the language of "I give, but I expect" is kept, but uses the
> existing language of sending on channels,  "B to A, your turn A", and is
> suggested as unambiguously extending the "I give, I expect" agreement
> backwards towards the tail. The symbols suggested are certainly not sacred,
> either.
>
> Point B and C are harder to recommend unequivocally, but could be potently
> powerful tools for communicating very long but uncomplicated calls. Point B
> has very few downsides, but represents introduction of a case where a human
> might understand an expression differently than a parser, while Point C
> introduces a layer of complexity to serve a use case that, for better or
> worse, could serve to push a new idiom onto the Gopher community.
>
> These three points present no risk to the Go 1 compatibility promise, but
> I will admit before anyone else that I haven't proven that Point C is worth
> the investment, and I expect some difficult questions about Point B.
>
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts+unsubscr...@googlegroups.com.
>
>
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/ab4e2604-99aa-4164-a2bf-a9da29d9e0d2n%40googlegroups.com
> <https://groups.google.com/d/msgid/golang-nuts/ab4e2604-99aa-4164-a2bf-a9da29d9e0d2n%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAAxgZyTXE3OEx_RLR6ijWHwh6Vi4jZRTJ03ahahje48e8RbPpQ%40mail.gmail.com.

Reply via email to