A case for eval, or dynamic code evaluation

Propose new features and changes
Springwight
Posts: 7
Joined: 02 May 2015, 04:43

A case for eval, or dynamic code evaluation

Post by Springwight » 28 Feb 2023, 17:48

[Mod edit: Topic moved from 'AHK Development'. ]

So eval has received a lot of slack over the years. It’s insecure, unhygienic, and generally bad for everyone’s health.

Every language that has this notorious function — PHP, Python, JavaScript, VB, Bash — bemoans its existence. They also warn of the terrible consequences that should arise were the reader foolish and foolhardy enough to invoke it.

While I could speak in favor of eval in those languages, I’d like to tell you a story from my youth instead. There was (and, some say, still is) a little program called MapTool. This was a virtual tabletop application of the olden days, before the Cloud when ports had to be opened and routers configured. The whole thing was generally a considerable fuss.

The community of that program was rich and vibrant. We, for I was one of its members, partook in many games and enjoyed them greatly. As with any virtual tabletop, one of the most prized features of MapTool was its dice roller. It started as something similar to an IRC command:

Code: Select all

/roll 1d20+1d8+1
But the user base demanded more, and so with the grace of the devs; the macro was born. This was a method of rolling dice and embedding the result into a chat message. At first, one might write:
Boromir attacks the orc with [1d20 + 16 - 5] and [2d6 + 5 + 16 -2 + 25 + 19 + 127] damage.
Soon, however, things such as this became commonplace:
Gornak the Destroyer lifts his Dreadhammer of Darkness +3 in two ham-like fists, the weapon trailed by wisps of shadow in a color deeper than the black of night. With a chilling roar (*Chilling Roar (Ex): Will save [CasterLevel / 2] or be shaken for [1d4] rounds) he brings the weapon down on the hapless [TokenUnderCursor.name], rolling [1d20 + Strength + BAB - PowerAttackBonus + 3] for attack. Gornak’s Dreadhammer bears down upon the [TokenUnderCursor.name], tearing apart flesh and sinew and carving through bone, dealing [2d6 + 2 PowerAttackBonus + 1.5 Strength + 4d6 + 2d8 + 1d6 + 1] damage.
However, that stuff became pretty silly if Gornak the Destroyer missed, so after a while, an if expression was born, allowing Gornak to act accordingly:

Code: Select all

[
	if(AttackRoll < TokenUnderCursor.AC,
	"But Gornak underestimated his own incredible might, causing his weapon to smack into a nearby wall instead.", 
	"Gornak hit the thing, roll damage.")
]
However, there was a catch. Although undoubtedly useful, the power of if was limited. For all its promise, it couldn’t execute code conditionally, designed as it was for a template system for generating text. It could only choose the more righteous value. It couldn’t even roll any dice.

And yet, built upon the foundations of such a simple command, great frameworks would be born. You see, in their pursuit of greater automation, a few canny dungeon masters turned to an ancient power. It was a word that, when wielded properly, allowed them to transcend the bounds the devs inflicted upon them and weave works of greater and greater intricacy.

Code: Select all

[
	Eval(
		if(AttackRoll < TokenUnderCursor.AC,
			"But Gornak underestimated his own incredible might, causing his weapon to smack into a nearby wall instead.", 
			"Gornak hit the thing, dealing [MyToken.damageRoll] damage."
		)
)]
You see, using the primeval power of eval, entire libraries of functions could be created wherever one could store text. It expanded every command it touched, imbuing it with meaning beyond the limits imposed by the devs. As great though they were, they could not foresee what the userbase needed to complete their works.

And lo, soon the devs saw the works of the masters, and so the CODE command was brought into the world. Making that use of eval unnecessary, it allowed storing and executing code as a more fundamental concept, including to be used in that if expression.

And so the eval command was put aside, for after enduring the most painful debugging sessions, you can imagine the masters understood its reckless power. It was set aside — but only to be wielded once more for another worthy cause.

And what became of MapTool? Well, roll20 came out, and everyone just stopped using it because of how frustrating the networking was.



TL;DR

I’m not sure why I had to write that. Sometimes I just have to appease the demons that live in my head.

Anyway, the point I was coming to in the narrative is that eval lets users go beyond the limitations of the language. Using this ability, users can implement the functionality they need, informing the development of the language.

It’s true that most languages that have eval hate and fear it, but these are generally established languages used in production. Most of those languages started as scripting languages, and as they grew, eval was used freely to cover the gaps. In some languages like JavaScript, it’s fair to say it’s commonly used in production, though largely by dependencies of whatever you’re writing.

Although the MDN article has countless warnings about its use, JavaScript’s eval is there to stay forever, not just because of some ancient code that has to keep functioning but because many JavaScript libraries use it for all kinds of things to this day.


Usecase in AHK2

Here is a simplified example. I want to write a function called FuncRefArgsToArrayReturn(f). Here is what I want the function to do in pseudo code:

Code: Select all

FuncRefArgsToArrayReturn(f) {
	toArrayReturn() {
		f(&a1, ..., &aN)
		return [a1, ..., aN]
	}
	return toArrayReturn
}
I want to convert between:
  1. A function with N output arguments, and
  2. A function returning an array of N elements.
I could only do this by manually writing out the cases for each arity, metaprogramming, or urm, asking the AI.

It’s possible to solve this issue without adding eval, but eval gives way more functionality than just this. You can also use it to:
  1. Turn a function taking n arguments into one taking n-1 arguments.
  2. Do the opposite.
If eval functioned similarly to a bash subshell and calling code could control the executing code in some way (even just setting a timeout), it could be used to create a script manager or test framework.

The use cases for eval are limitless. Although it’s said that eval is evil, in the words of the C++ Super-FAQ, it’s normal to use evil constructs

Alternatives
Generally, there are ten better alternatives for anything dynamic code evaluation accomplishes. For example, a carefully designed metaprogramming system, various forms of reflection to create objects at runtime, and things like that.

I'm suggesting it and not something else because there isn't much to be designed -- it's just another way of invoking the language -- and it's usually fairly simple to implement in languages with an interpretation stage.

It can create a host of terrible bugs, but from my experience, people tend not to use these special language features if they are frustrating enough, and anyone who has ever tried to use eval in any language for any purpose knows it's plenty frustrating. Bearing that in mind, perhaps it can also be given a very long-winded name.

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: A case for eval, or dynamic code evaluation

Post by lexikos » 28 Mar 2023, 02:00

I don't have anything against the existence of eval. It isn't implemented because:
  • The current codebase can't easily support it.
  • Current memory optimizations rely on the fact that the "precompiled" code never needs to be freed before the program exits.
  • AutoHotkey_H supports similar functionality, though with caveats.

Post Reply

Return to “Wish List”