Is there a reason one cannot parse a JS string into a simple-ish JavaScript AST object, and vice versa? It seems like if the parser were written in a simple / reasonable way, one could also extend it with a macro system.
I don’t have any experience with them, but implementations of ASTs for JavaScript exist… https://astexplorer.net/ http://jointjs.com/demos/javascript-ast … also macros! http://sweetjs.org/
I’ve been alternately impressed and frustrated by Chrome’s ability to set breakpoints in CoffeeScript and/or JSX… Here’s hoping they’ll improve breakpoint setting as well!
… but the one thing I keep coming back to, that I believe has enduring value in almost all situations, is the audition project:
The most significant shift we’ve made is requiring every final candidate to work with us for three to eight weeks on a contract basis.
I don’t doubt it’s effectiveness at generating a realistic work sample, but I can’t imagine ageeing to a multi-week project as part of an interview. Even if I were between jobs, just one of these projects would uncomfortably limit my time for generating competing offers. Maybe I’m an outlier, but I have the feeling this would really shrink the application pool, retraining those who are more desperate or star-struck.
Shrinking the application pool may be acceptable or even desirable, if they’re telling themselves that they’re eliminating the not-best applicants…
I would love for this to be an urban fantasy animated film. Google is the villain, building up its wage-fixing cartel, when it is beset by teams of magical unicorns who smash in and rescue the hostage engineers.
“Muah ha ha, you’ll need Steve Jobs' personal permission to leave my castle! *crash* No! Guards, guards! Curse you, unicorns!”
Erlang making this hard is the biggest reason I didn’t continue to learn Erlang. I don’t understand how the “just crash” philosophy and actually returning useful error messages are supposed to intersect. I have just assumed since Erlang was made for telecom infrastructure, where I assume it’s reasonable to drop bad input rather than return errors, it just doesn’t have an ergonomic way to return errors. At least based on my experience with switches and routers, which tend to drop bad packets with only a few exceptions.
I’d be happy to learn I’m wrong though.
On the flip side, Swift
guardstatements make this pattern delightfully easy. In particular, I like that you can do aguard letand the assignment will occur in the outer scope, as opposed toif letwhich makes the binding local to the body to theif.For example:
Notice the
elseafter the guarded expression, that hints how the scoping works. Soguard letisn’t quite the same as what you might expectunless let/if not letto be. Aces.The way you tackle these things in Erlang is by tagging the return values. If you really, really need to return early, you can always
erlang:throw/1, which does pretty much whatreturndoes in javascript.Admittedly, you can end up with multiply-indented case statements in Erlang, too, but there are ways of dealing with that (depending on your style) that don’t involve non-local returns.
I’m familiar with return tagging. The nested case statements are what bother me. I appreciate the link but that strategy is so over the top and verbose that I absolutely don’t want to do anything like it for generalized input guarding. Throw might be what I want but it seems wrong, maybe because of how I’m used to using exceptions in other languages, maybe not. Like what if your caller doesn’t expect you to throw, and it isn’t your code? You’d have to wrap everything in a catching function. It feels like way more work than it should be just to have access to early returns.
I use the following construct in my code:
If you squint, it’s an early-exit chain of
>>=(monadic binds) on the squintly-typed Either type.Then, you can use the following pattern:
Any fun that returns
{error, E}will cause the entire chain to exit with that tuple. OTOH, returns of{ok, V}will pass on the bareVto the next element in the chain. This means you only code for the relevant path. You can then deconstruct the values in function heads to further reducecase-yness.Regarding the ‘unknown code crashing your process’ problem: yes, I hear you. There is no foolproof solution to this: sometimes you do have to explicitly try-catch, sometimes your running process doesn’t care and can just crash, because the supervision tree is built in such a way that it doesn’t matter.
I like this pattern overall, but I still wish it was more ergonomic. I believe the
|>operator in Elixir exists to essentially do this?Yeah, ergonomics isn’t Erlang’s strong suit. I does help if all your functions have a uniform return type, so you can get by with just referencing them:
where all the above functions are
:: number() -> {ok, number() | {error, any()}In Elixir, the
|>operator solves half of the issue (chaining), and thewithsyntax solves the other part of the issue (logic/dispatch based on previous return value). Yet, there is no succinct way of combining them, i.e. implementing something like Haskell’s>>=.*edited example function name
The fold strategy reminds me of
pipelinefrom Joyent’svasynclibrary. Node has a callback-based runtime that totally eradicates anything resembling a call stack whenever you have to do IO, so using a library that attaches context to function calls makes sense. Dynamically executing functions just to get a syntax you like seems silly though. What about generating macro code into a header?It’s been done: https://github.com/rabbitmq/erlando The issue with marco- and parse-transform-based Erlang libraries is that they don’t ‘stick’ in the ecosystem. My guess is that developers are too used to 1:1 mapping of code to bytecode (for reasons of debuggability). Also, parse transforms can be brittle/untestable. Basho’s
lageris pretty much the only parse_transform that I’ve seen embraced extensively in the wild.From a different angle, there’s nothing dramatically silly about dynamically executing functions in Erlang. There are tons of places all around OTP that do this: see the
{Mod,Fun,Args}interfaces to Supervisors and gen_servers, dynamic callbacks ingen_event, mnesia transactions, etc.fun(Blah) ->expressions even get compiled to ‘named’ functions during lambda lifting, and they are very efficient, unless of course huge environment captures are involved. Erlang is in fact very introspective and dynamic – I’d go as far as to say that it is almost a lisp, at heart. I’m certain Robert Virding played no small part in making it so.Yes that’s true. I was thinking about it purely from force of habit—I normally use C++. I just traced a performance issue in some code using
std::functiondown to an allocation in libstdc++ that only happens if the function is a lambda with captures. 8 bytes made all the difference. So you can see what I’m used to thinking about. ¯\_(ツ)_/¯I’ve generally addressed this with two solutions: 1) toss the nested handling into a function call 2) case on a tuple that has everything that can be evaluated in it and case on the dot product of options. 2 obviously only works if you nested cases don’t depend on each other.
Elixir addresses nested cases with the
withmacro.I should give Elixir a spin, given how much I love Ruby.
I’ve done the nested handling as function calls. It annoys me because it splits up all the code in a Node.js kind of way. But it’s decently ergonomic as the function names end up as comments for the early exit condition, and I think early exit conditions should be commented unless they’re really truly obvious.
I haven’t heard of doing a single case over all the inputs before. That’s a fantastic idea! My first thought is I usually order my checks and returns to avoid writing out the Cartesian product of their conditions in if/else statements, so this strategy could be cumbersome. But pattern matching, wildcard _ in particular, should provide opportunities to merge cases. I’ll definitely have to try it out!
Thinking about it also makes me curious how aggressively Erlang can optimize pattern matching. For example, suppose you have 4 independent options, one of which is expensive to compute, and there is only one valid case. If you just case over all 4 options anyway, will the expensive one only be computed in some cases? Or will all options be fully evaluated and bound before the pattern matching starts? Can Erlang detect some functions have no side effect? Of course you can manually optimize this pretty easily, but I’m still interested in seeing what Erlang can do there.