Julia: First Month

500 views
Skip to first unread message

Abram Demski

unread,
May 6, 2014, 11:15:36 PM5/6/14
to julia...@googlegroups.com
Hi all!

I've been using Julia for a little over a month now, and I thought it would be fun/informative to write a little post about my experience.

I'm mostly a Common Lisp guy. I write AI code in an academic setting. However, I recently became enthusiastic about Clojure and was trying to start a project in that.

Indecisive as usual, I continued poking around looking for the best programming language to do the project in, even as I became fairly committed to doing it in Clojure.

I had heard about Julia some time ago (looked at it very briefly), but looked at it a second time when a friend mentioned it on twitter earlier this year.

Looking at it again, I realized that:

1) Julia is "essentially a Lisp", IE, it is homoiconic (despite not appearing so) and has the metaprogramming capabilities I am used to. (I don't need these often, but if a language lacks it, I feel like I'm missing an essential tool.)
2) Julia's type system and multiple dispatch capabilities give me much of what I liked about Clojure's multimethods and protocols.
3) Julia is significantly faster (at least for many things).

I decided to start hacking out my project in Julia, abandoning the Clojure code I had started.

After using it for a bit, I feel like it's been much easier to pick up what I need to know than it was with Clojure. Both Julia and Clojure have the deep, elegant stuff I like in a programming language; however, it seems like Clojure creates a rich, interlocking set of concepts which you must learn in order to write very much code, whereas Julia has a gentle learning curve, facilitating "normal" programming and allowing the user to learn the deeper features as they become useful. At least, that's been my feeling.

Monkeying around with the metaprogramming has taught me that it's a bit less convenient than Lisp. Thinking about expr.head and expr.args is not as intuitive as composing expressions as lists, I think. It's not a big obstacle, though.

I've also found it a bit annoying that array access is not the same as function application, again a feature of Clojure. Being able to treat arrays and dictionaries as functions is convenient for certain higher-order functions like map.

Overall, although I admit I'm judging Julia by Lisp/Clojure standards, it comes out rather favorably. :)

--
Abram Demski
Leave anonymous feedback: http://www.admonymous.com/abramdemski

Jason Merrill

unread,
May 7, 2014, 3:12:44 AM5/7/14
to julia...@googlegroups.com
Glad to hear that you've been enjoying picking up Julia. I've felt the same way about Julia having a more gradual on-ramp than some other cool languages. You don't have to wrap your head around a new paradigm to get started, but there are lots of nice advanced features waiting for you when you're ready.

Re homoiconicity: I know a lot of people have been saying that Julia is homoiconic, but I don't really buy it. Is the claim that any language that gives you access to the parser and eval at runtime is homoiconic?

Let's consider Javascript for a second. 5 years ago, I don't think anyone would have said that Javascript is homoiconic. But now there are javascript parsers like Esprima and Acorn written in javascript, so you can take a string of JS code, parse it into an AST at runtime, traverse and manipulate the AST the same way you traverse and manipulate other objects, and then emit and eval new code. Did JS become homoiconic when someone wrote Esprima? I don't really think so.

In my mind, syntax is relevant to homoiconicity. For a language to be homoiconic, the map from syntax to AST should be very simple; or in other words, the parser should be fairly trivial. This is true of Lisp, but not really true of Julia. Incidentally, the parsers of concatenative languages like Forth are even simpler than for Lisp, and more modern concatenative languages like Joy and Factor take advantage of this in really cool ways.

All that said, I think the people who say Julia is homoiconic would be right if they instead said that Julia gives you the power to do most of the things that people take advantage of homoiconicity to do in homoiconic languages. Meta-programming Julia really isn't too bad at all.

Ivar Nesje

unread,
May 7, 2014, 4:03:53 AM5/7/14
to julia...@googlegroups.com
Great!
Finally I got a definition of homoiconic that Julia does not fulfill. According to Jason, homoiconicity has nothing to do with language features/standard library features or how you are encouraged to structure your code to get an idiomatic program, but is solely a vague measure of the complexity of the parser for "syntactic code"->AST transformation.

By that definition Julia fails, because the syntax is created to be simple humans to write and read to express ideas, and the AST is designed to be easy for a human to work with through a machine. The transformation between is complex and not one to one.

Ivar

Isaiah Norton

unread,
May 7, 2014, 7:48:03 AM5/7/14
to julia...@googlegroups.com
The word "homoiconic" was removed from the official docs because it was not very informative and was occasionally contentious.

Mike Innes

unread,
May 7, 2014, 9:37:20 AM5/7/14
to julia...@googlegroups.com
You could certainly define homoiconicity as "zero syntactical distinction between data and code" – Clojure would pass this test because writing code and writing a list are exactly the same, whereas Julia would fail it.

This definition is perfectly valid, so the problem isn't that it's incorrect so much as that it's a really uninteresting criterion. I for one am not nearly as interested in syntax as the ability to produce and manipulate expressions, which is something that both languages obviously share.

Perhaps a better definition would be "the ability to quote language expressions", e.g. '(foo x y) or :(foo(x,y)). Clojure and Julia pass, JavaScript does not – and that actually tells you something about the abilities of the language.

Jason Merrill

unread,
May 7, 2014, 10:24:29 AM5/7/14
to julia...@googlegroups.com
On Wednesday, May 7, 2014 6:37:20 AM UTC-7, Mike Innes wrote:
You could certainly define homoiconicity as "zero syntactical distinction between data and code" – Clojure would pass this test because writing code and writing a list are exactly the same, whereas Julia would fail it.

This definition is perfectly valid, so the problem isn't that it's incorrect so much as that it's a really uninteresting criterion. I for one am not nearly as interested in syntax as the ability to produce and manipulate expressions, which is something that both languages obviously share.

I think being able to produce and manipulate expressions means being able to parse the language into a tree, and then manipulate the tree. Are there any languages that make it actually impossible to implement a parser for themselves? Pretty much every language can manipulate trees. Of course once you've manipulated a tree, you want to be able to execute the new tree, and not every language has eval, so that at least seems like a useful distinction.
 
Perhaps a better definition would be "the ability to quote language expressions", e.g. '(foo x y) or :(foo(x,y)). Clojure and Julia pass, JavaScript does not – and that actually tells you something about the abilities of the language.
 
Do you see a fundamental distinction between :(foo(x,y)), and parse("foo(x, y)")? I agree the first one is aesthetically preferable, but they both get the same job done, right? And it's possible to make the second one in JS, even though JS doesn't have the first one as a syntactical construct.

Another nice thing that Julia has going for it is that it's very easy to traverse the AST. Everything is either a leaf, or is an expression with a head and args, so you can walk the whole tree by walking all the args of every expression you encounter.

Some ASTs aren't like this: e.g. the JS AST that people are settling on has different names for the "args" of expressions with different "heads", so you need a lot more logic to make sure you traverse everything. For example, their IfStatement has the properties: type (the head), test, consequent, and alternate (the three args) [1]. You have to know all these names when you encounter an IfStatement in order to keep walking all of its children. But that isn't really a feature of the language, it's just a feature of a particular AST representation that some people are starting to use. It would certainly be possible to create a more Julia-like AST for JS without changing the language.

It's hard to talk about things like "the definition of homoiconic" without appearing to be trolling, but it actually is useful (to me anyway) to get to the bottom of which features are critical for effective meta-programming, and which features aren't.

[1] https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API#Statements

Mike Innes

unread,
May 7, 2014, 11:29:07 AM5/7/14
to julia...@googlegroups.com
I see what you're saying – in principle any Turing complete language can have metaprogramming capabilities, in the limiting case by implementing an interpreter for the language within itself.

But the logical extension of this attitude is that all languages are the same, which clearly isn't right – or we'd all be using assembly. So I'm not thinking about what's possible so much as what's accessible, which admittedly isn't a wholly objective thing. For example: there are libraries designed to make functional programming possible in Java, but you couldn't really argue that Java is a functional programming language.

I actually think JavaScript is a pretty nice language for metaprogramming, but the techniques you'd naturally use in JavaScript and Julia are very different from each other. You don't need expressions and eval when you can inject any closure into any object (although JavaScript still sorely misses macros).

Jason Merrill

unread,
May 7, 2014, 2:27:46 PM5/7/14
to julia...@googlegroups.com


On Wednesday, May 7, 2014 1:03:53 AM UTC-7, Ivar Nesje wrote:
Great!
Finally I got a definition of homoiconic that Julia does not fulfill. According to Jason, homoiconicity has nothing to do with language features/standard library features or how you are encouraged to structure your code to get an idiomatic program, but is solely a vague measure of the complexity of the parser for "syntactic code"->AST transformation.

That's a little bit snarky, but I guess it's pretty much a correct characterization of what I was saying. The "icon" part of homoiconic is supposed to evoke "writing", so I really do think the term was supposed to have something to do with how the code is written, i.e. the syntax. Specifically, the internal representation is supposed to be "the same" (homo) as the way that the code is written (icon).

If you accept that, then everything hinges on what "the same" really means, which I agree is a little bit vague. Obviously it can't just mean that there is a mapping from syntax to internal representation, because there always is. So I think it must mean that the mapping must be unusually simple.

The simplicity of this mapping actually does matter, to some degree. Quick quiz: what is the output of

julia> :(x<4).head

Maybe you know the answer already, but you generally have to try these things out to find out the answer. In a language like scheme, there is no translation layer where you might not know what the AST of some syntactic construct looks like. In that way, it's easier to meta-program a language like scheme than it is to meta-program a language like Julia that has much more diverse syntax. Not to say that it's onerous to meta-program Julia--it really isn't--but there definitely is more cognitive overhead along this one axis.

There's also an idea floating around that homoiconic means that code can be represented (internally?) using the primitive datastructures of the language. This seems like a pretty empty requirement to me. What language doesn't have datastructures that are up to the task of representing an AST?

Do you prefer some other definition of homoiconic?
 
By that definition Julia fails, because the syntax is created to be simple humans to write and read to express ideas, and the AST is designed to be easy for a human to work with through a machine. The transformation between is complex and not one to one.

Yes, exactly. "homoiconic" is not a synonym for "good". It's a tradeoff between uniformity and expressiveness, or uniformity and readability, or uniformity and familiarity, or something like that.
 

Ivar Nesje

unread,
May 7, 2014, 3:58:08 PM5/7/14
to julia...@googlegroups.com
I had never heard the word homoiconic before the discussion about the documentation of Julia claiming that Julia could be described by that word. After thinking about the various opinions my hunch is that the problem is that homoiconic is on the borderline between describing the language design concept (simple transformation rules from written code -> a AST representation) and a style of programming (where a program modifies its own source before evaluating the resulting code).

The question is not really how do you define homoiconic, but what you use the word to describe. I don't think it is very useful to describe a language with such abstract words, and interpret such claims as describing a idiomatic programming style. You might disagree on that point, and I guess we might have a different perspective when we use words to describe a programming language.


PS: I got the quizz wrong :(
It was not :call

Stefan Karpinski

unread,
May 8, 2014, 2:26:40 AM5/8/14
to julia...@googlegroups.com
I like the civility of this discussion of what has, on occasion, been a rather contentious topic. It's really the only way to make any progress on these things. David Nolen and I had a similar discussion some time ago and came to the conclusion that homoiconicicity is a bit of a continuum – Lisps are very homoiconic, C is not homoiconic at all, and languages with some level of run-time reflection and code generation are all somewhere in the middle, with Julia being on the rather homoiconic end, but not as far as Lisp – largely because of the greater complexity of syntax to AST mapping.

I answers a slightly related question on stackoverflow the other day:


What does JavaScript use as symbols? Strings? (If strings, then what does it use to represent strings?) Could something as simple as having a first-class representation of variables be at the crux of the matter? It's the kind of thing that is awkward (but not impossible) to graft on later.

Stefan Karpinski

unread,
May 8, 2014, 2:47:53 AM5/8/14
to julia...@googlegroups.com
Thanks for reporting these first impressions – I find this sort of thing really helpful and interesting. We did try really hard to make the learning curve for Julia very gentle, the idea being that people who don't care about fancy language features should be able to use it productively too and learn about new feature only when they become useful. It's nice that this comes across.

The business of function application and array indexing being different has come up a number of times recently – someone mentioned it to me in comparison with J/APL programming. And obviously in Matlab they're the same syntax, but since neither functions nor arrays can be used in a higher order fashion, that hardly matters. It makes me wonder about a design where they're the same thing and what problems that would solve and introduce. It would require some degree of overloading of function application syntax, which we've contemplated but don't do. In the meantime, we can pretty easily leverage multiple dispatch to make map(a, v) work as desired when a is an array. Which highlights one obvious issue: in a language with multiple dispatch, if lots of things are callable as functions, it makes a lot of higher order programming signatures pretty ambiguous. Map is easy – the first argument is function-like – but other functions that have higher order and non-higher-order variants require more thought. Is it better to make function application and array indexing the same thing and have that similarity fall out everywhere but also force users to deal with that ambiguity when writing method signatures, or is it better to go through the standard library and make sure that every place where a function could be used in a way that an array would also make sense, we allow it? I'm honestly not sure.

Mike Innes

unread,
May 8, 2014, 4:29:36 AM5/8/14
to julia...@googlegroups.com
Am I right in thinking that the issues you have with function call overloading are equivalent to those involved with multiple inheritance? In the sense that overloading function application effectively makes an array (for example) a member of both AbstractArray and Callable, which will create ambiguities in functions which dispatch on both.

In that case, it may be best to separate these two issues. So you can overload x(y) and it will work when you directly call x, but x::Callable will fail. Then you declare x<:Callable, and the multiple inheritance part of the type system kicks in to help you resolve ambiguities.

Another option (in terms of multiple inheritance) might be to have some kind of priority for parents: "x is an AbstractArray first, and a Callable second", i.e. if there's any ambiguity dispatch as AbstractArray. In the sense it's the same as the way children are prioritised over parents already. This would would make multiple inheritance very low-effort at the user level, but the implicit-ness might end up being a bad idea if it makes things hard to reason about (I don't know if it will).

Pierre-Yves Gérardy

unread,
May 8, 2014, 5:33:30 AM5/8/14
to julia...@googlegroups.com
On Thursday, May 8, 2014 8:47:53 AM UTC+2, Stefan Karpinski wrote:
[...] And obviously in Matlab they're the same syntax, but since neither functions nor arrays can be used in a higher order fashion, that hardly matters.

I don't have Matlab at hand, but IIRC this produces the expected output. There may be a better way to express it [], I'm a Matlab n00b, and I haven't touched if for at least five years.

function oOut=makeConcat(in)
  function iOut=concat(first, second)
    iOut=[first '... ' second];
    return
  end
  oOut=@(x)concat(in,x);
  return
end

combiner1 = makeConcat('Let''s try');
combiner2 = makeConcat('And it doesn''t mess');
combiner1('It Works!!')
combiner2('anything...')


Not that I'd recommend doing that...

Now imagine a world where SICP has been written for Matlab rather than Scheme.

:-D

—Pierre-Yves

[] make concat  a function of one parameter and return its @handle, maybe?
Reply all
Reply to author
Forward
0 new messages