[WISH] forward pipe operator?

Discuss the future of the AutoHotkey language
20170201225639
Posts: 144
Joined: 01 Feb 2017, 22:57

[WISH] forward pipe operator?

07 Oct 2021, 13:29

Could be simulated with preprocessing, but seems a very natural addition to an AUTOMATION-oriented programming language, as a lot of automation is just chaining a series of transformations together, feeding output of the previous transformation as input to the next one.

Very often, what we mean by "f(g(h(x)))" is more naturally represented by (e.g.) "x |> h |> g |> f",

For example, say you'd like to rotate a 2D array clockwise 90 degrees, then flip it horizontally, then flip it counterclock wise 90 degrees

Sth like this syntax

A:

Code: Select all

array  |> rotate(*, 90)  |> flip(*, "H")  |> rotate(*, -90)
(or:

Code: Select all

array  
|> rotate(*, 90)  
|> flip(*, "H")  
|> rotate(*, -90)
)


seems a more natural way to represent that procedure than:

B:

Code: Select all

rotate(flipH(rotate(array, 90), "H"),-90)
Objections:
1. "you can write a custom object and do 'method chaining' ". But piping could and should be pure syntactic sugar with no semantic consequences.

2. "you can write B over 3 lines". But then you have to keep track of extra variable names (or extra occurrences of the same variable name), which introduces more possibilities of error.

3. "You can easily do that with a preprocessor." Yes, but a pipe operator seems a very natural built-in feature for AHK as a language for automation.

4. "It's pure syntactic sugar. It would't be a genuine addition to the language". But the same is true of continuation sections ("Line continuation...This is achieved by preprocessing, so is not part of the language as such"), and is no argument against them.
iseahound
Posts: 1459
Joined: 13 Aug 2016, 21:04
Contact:

Re: [WISH] forward pipe operator?

07 Oct 2021, 16:18

The solution that other languages use is to have a list of functions, and apply each function from the list to the argument. This method already exists in AutoHotkey and is much more powerful than the syntax that you are proposing, because you cannot edit your "procedure" list, whereas a list of bound functions can be edited. (In practice, you should call .clone() to create a shallow copy of the object, not edit it directly.)

Code: Select all

list := Map(1, Rotate.Bind(, 90), 
            2, Flip.Bind(, "H"),
            3, Rotate.Bind(, -90))

str := ""
MsgBox apply(str, list)

apply(this, list) {
   for i, func in list
      this := func(this)
   return this
}

Rotate(this, degree) => this .= A_Space degree
Flip(this, axis) => this .= A_Space axis
edit: maybe apply should be renamed left fold? I know there's a name for the action of applying a list of functions to a single argument but its definitely not apply....
20170201225639
Posts: 144
Joined: 01 Feb 2017, 22:57

Re: [WISH] forward pipe operator?

08 Oct 2021, 02:59

I see your point about the list of functions method being more powerful. Func(apply).bind(list) is a composite function and a first class citizen in its own right. The pipe operator syntax is just syntax. The whole chain of pipes has no reality at run time and I can't give them a name or easily reuse it elsewhere. The difference is like that between (e ∘ g ∘ f )(x) and e(g(f(x) ))

But that's functional programming, whereas I'm only making a syntactic point. I have a lot of tasks to automate and I frequently have to write code like this:
x := fileread(path)
x := f1(x)
x := f2(x)
x := f3(x)
x := f4(x)
fileappend(x, path)

it'd be nice to have a way to *represent* this process in a "clean" manner, without needing to litter my code with the all those occurrences of 'x' (not to mention all the extra possibilities for things to break thereby introduce, and also the need to worry about whether naming it 'x' didn't overwrite any previous var in the same scope). Now there is in fact a way to avoid intermediate vars, namely: fileappend(f4(f3(f2(f1(fileread(path))))), where the information that would be conveyed by intermediate vars is instead provided by the *position* of the expressions. My only point is that it seems pretty arbitrary that in most programming languages (F# has a |> operator I'm told) we can't write that expression backwards, as it were, in the natural order (path > filerea > f1 > f2 > f3 > f4 > fileappend), which also has the added benefit of avoiding the need to match the parentheses.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: [WISH] forward pipe operator?

09 Oct 2021, 12:47

nice idea. if it were implemented in source, it would have less overhead. but if general operator overloading was implemented instead, it would be way more flexible(ud be able to (re-)define whatever operators u liked, |>, <|>, <*> etc), but it would have the same overhead as method calls do. regardless i think the latter would be preferable
gimmick with overloading .Call()(though, ull have to write ur functions with currying in mind, or hack together an autocurrying function):

Code: Select all

#Requires AutoHotkey v2.0-beta.1

(Object.DefineProp)(String.Prototype, 'Call', {Call: pipe})
pipe(this, ArraysOrFuncs*) {
	for Obj in ArraysOrFuncs
		this := (Obj is Array) 
			? pipe(this, Obj*)
			: Obj(this)

	return this
}

Rotate(degree) => (this) => this .= A_Space degree
Flip(axis) => (this) => this .= A_Space axis
NewLine(this) => this .= '`n'

RotateVerticallyInstructions := [
	Rotate(180),
	Flip("V"),
	Rotate(-180),
]

str := ""
MsgBox str(
	RotateVerticallyInstructions,
	NewLine,
	Rotate(90),
	Flip("H"),
	Rotate(-90),
	NewLine,
	RotateVerticallyInstructions,
)

another gimmick with meta functions(which wont work unless the functions/lists ure trying to reference can resolve to global var. so not very useful...):

Code: Select all

#Requires AutoHotkey v2.0-beta.1

(Object.DefineProp)(String.Prototype, '__Get', {Call: __Get})
(Object.DefineProp)(String.Prototype, '__Call', {Call: __Call})

__Get(this, propName, Params) {
	ArrayOrFunc := %propName%

	processArrayRecursionHelper(Arr) {
		for Obj in Arr
			this := (Obj is Array)
				? processArrayRecursionHelper(Obj) ; its an Array, call recursively
				: Obj(this) ; its a function, call it as is
		
		return this
	}

	if (ArrayOrFunc is Array)
		return processArrayRecursionHelper(ArrayOrFunc) ; its an Array, iterate over its elements
	else
	{
		; otherwise its a function, call it with whatever args were provided(if any)
		; since its a property square brackets .prop[arg1, arg2, ...] would have had to be used
		; because parentheses .method(arg1, arg2, ...) would have invoked meta-call instead
		return __Call(this, propName, Params)
	}
}

__Call(this, methodName, Params) {
	Function := %methodName%

	; if u decide to stick with the currying approach
	for param in Params
		Function := Function(param)

	return Function(this)
}

Rotate(degree) => (this) => this .= A_Space degree
Flip(axis) => (this) => this .= A_Space axis
NewLine(this) => this .= '`n'

RotateVerticallyInstructions := [
	Rotate(180),
	Flip("V"),
	Rotate(-180),
]

str := ""
MsgBox str
	.RotateVerticallyInstructions
	.NewLine
	.Rotate(90)
	.Flip("H")
	.Rotate(-90)
	.NewLine
	.RotateVerticallyInstructions

iseahound wrote:
07 Oct 2021, 16:18
edit: maybe apply should be renamed left fold? I know there's a name for the action of applying a list of functions to a single argument but its definitely not apply....
looks like a plain fold to me.. oh but who am i kidding, i know nothing about functional programming! its probably an endothermic copresheaf on the precategory of monozygotic burritos

Return to “AutoHotkey Development”

Who is online

Users browsing this forum: No registered users and 8 guests