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.

Reply via email to