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