Feeling in a refactoring mood I decided to bravely tackle some performance bottlenecks that were present in Genie’s codebase. I decided to go for the low hanging fruits and squash The Evil eval.
A quick search revealed 3 big (anti) patterns involving eval. Some of them are pretty old in my codebase and I googled them many times, without being able to find a definitive (or even recent) solution.
#1. dynamically invoking functions.
Once upon a time there was invoke. It got kicked in v0.5 and no clear upgrade path was indicated (at least not for me when reading the “what’s new”). Hello eval!
Fortunately, this issue was greatly addressed by @StefanKarpinski in a StackOverflow reply - the way to do it is with getfield.
My only comment here is that invoking nested functions (like A.B.C.foo) gets horribly complex - and to be honest it took me a while to understand why Julia throws an error. The issue is, getfield has no support for nested fields - it would be awesome if it did, nesting 3-4 levels of getfield is horrible.
#2. dynamically generating functions (and optionally exporting them)
This is a common design pattern when developing DSLs. For example, Genie provides a templating system which allows writing views in pure Julia while still looking like an HTML structure (think Ruby’s HAML).
It looks something like:
body(:class => "foo", :onload => "...") do
h1() do
"Welcome!"
end
p() do
# more stuff here
end
end
All these functions are simple utility methods that delegate to a function like:
function elem(content::Function, html_tag::Symbol)
And I have an array of html tags, like:
[:body, :h1, :p]
What is the right way of generating (and optionally exporting) the utility functions, based on the array, without using eval?
#3. dynamically include and using modules
I’m using these a lot in a factory style of design pattern. Basically I have various modules that rely on “adapters”. For example for caching or for sessions. Both cache and sessions can be stored either on the file system, a RDBMS or a NoSQL backend. The adapters expose a common interface and the framework just delegates to the underlying adapter for persisting and retrieving the data.
The adapters are user configurable in a settings file - but as the configuration is loaded very early in the app’s life cycle, the actual modules are not available yet. So the config uses Symbols to reference the adapter name - which later (when/if needed) has to be actually included and referenced.
What’s the right way of doing it, without using eval?
===
Sorry for the long write and thanks in advance for your time – if you have other design patterns please add them, I think that having a reference for this in one place will be very useful. I’m happy to aggregate it and turn it into a page we can put into the docs or on Julia’s blog. I think many users make the mistake of reaching for evil eval, especially when coming from interpreted languages.
===
Btw, here is Genie’s repo, if you feel so inclined to take a look at the code and help track performance issues I’ll be forever grateful, I’m very good at taking constructive criticism
GitHub - GenieFramework/Genie.jl: 🧞The highly productive Julia web framework