Interesting language, originally a math/statistics package but now as general-purpose as any lang. More or less Pythonic, though it has some type-annotation stuff, and heavily-optimized Julia looks like a mess of annotations with your code buried somewhere inside.
The Mac version comes as a dmg with an app (which I'd prefer for easy install/uninstall), or brew (which I prefer not to use). The app just launches a single command in a new Terminal window; add that path/bin to the PATH in your .profile, e.g.:
export JULIA_HOME="$HOME/Applications/Julia-1.0.app/Contents/Resources/julia"
export MANPATH="$MANPATH:$JULIA_HOME/share/man"
export PATH="$PATH:$JULIA_HOME/bin"
And now in Terminal:
% echo 'println("Hello, world!")' >hello.jl
% julia hello.jl
Hello, world!
The only way I can see to make it compile to a binary is embedding, and I'm not clear on how you package that with a full Julia distribution yet. That's unfortunate. I like REPL workflows as much as anyone, but binaries are what "normal" people run.
Getting Started
<voice tone="excessively chipper"> Let's read the manual! </voice>
Syntax is nicer than usual: function/end, if/elseif/else/end, for/end, while/end, begin/end, let/end, which beats the hell out of Python's def, if/elif/else; to say nothing of abominations like Swift's "func". No do/while loop, which is annoying especially for file processing, but I suspect that can be fixed with macros.
There's a lot of ways to write functions, which is nice but allows some ugly choices. Anonymous functions are x->x^2
or function(x) x^2 end
; named functions can just be assigned f(x)=x^2
or written in full:
function f(x)
return x^2
end
Whitespace is not significant, and indentation is not enforced, which is a major bummer for style-enforcing-structure, but I'm sure sloppy jerks will love that.
You can use tuples for multiple returns, or as ad-hoc structures:
> point(x, y) = (x=x, y=y)
point (generic function with 1 method)
> p = point(13, 2)
(x = 13, y = 2)
> a, b = p
(x = 13, y = 2)
> a
13
It's pass-by-reference, not copying, so be careful with mutable data.
My only real kvetch so far is that arrays are 1-indexed and column-major, like FORTRAN, not 0-indexed and row-major, like C. For a numeric package, that makes sense, but for other programming tasks it's frustrating and error-prone, see EWD 831.
This is a functional language, and there are no classes/inheritance/methods, however "methods" are functions which are overloaded based on types, and can be used like class methods:
> quack(x::Int64) = "int $x"
> quack(x::Float64) = "float $x"
> quack(1)
"int 1"
> quack(6.66)
"float 6.66"
As well, you can use closures to make pseudo-classes, the same way you do in Scheme:
let state = 0
global counter() = (state += 1)
global counterReset() = (state = 0)
end
struct
(immutable) and mutable struct
make "Composite types", and can make objects the usual way:
struct Point
x
y
end
pointDist(p::Point) = sqrt(p.x^2 + p.y^2)
Base.show(io::IO, p::Point) = print(io, "{$(p.x),$(p.y)}")
> p = Point(6, 6)
{6,6}
> pointDist(p)
8.48528137423857
The default constructor can be overridden at the end of the field list, it's defined as Point(x,y) = new(x,y)
. The "toString" equivalent there is ugly as hell, but there's a ton of options for overloading it by type of output.
There's a lot of fucking around with generics and strong typing (for weak minds), but ignore all that crap.
Interfaces are a somewhat messy use of several methods to create pseudo-types; define the basic interface methods for your type, and most things calling those interface methods will work. So, a couple iter() functions and you have an iterable, and so on. This would work much better if Julia had an actual OOP class system and real interfaces, but Python half-asses interfaces the same way and aside from being 1000x slower than you'd like, it gets by.
Quickly skimming modules, seems pretty standard import mechanism, but I don't see any way to make something private. OK, I'm bored of reading docs. Let's do something. Something semi-practical here, my standard RPN calculator, one command per line.
Docs/libraries are kind of a mess, Vector is discussed in Base.Arrays, push!/pop! methods are discussed in Collections (an interface). parse is under Numbers, not Strings, as one might expect.
eof() does the extremely unfortunate thing of blocking for input, so it's utterly useless in a main interactive loop.
… About 30 minutes later, I have a working, final version. Well, that was pretty easy, and it's a clean implementation, other than the interactive loop.
Next time I open this, I'll put it in a module, and tokenize the line instead of requiring just one token per line, and have some command-line argument to suppress help and prompts.
I should also investigate IJulia which is a Jupyter notebook, which seems like the "expected" way to make it interactive and handle graphics or media.
RPNCalc.jl
#!/usr/bin/env julia
# RPNCalc.jl
# Copyright ©2018 by Mark Damon Hughes. All Rights Reserved.
stack = Vector()
function checkStack(n)
if length(stack) < n
error("Stack underflow: Needs $n values")
end
end
function parseLine(s)
s = strip(s)
if s == "+"
checkStack(2)
b = pop!(stack); a = pop!(stack)
push!(stack, a + b)
elseif s == "-"
checkStack(2)
b = pop!(stack); a = pop!(stack)
push!(stack, a - b)
elseif s == "*"
checkStack(2)
b = pop!(stack); a = pop!(stack)
push!(stack, a * b)
elseif s == "/"
checkStack(2)
b = pop!(stack); a = pop!(stack)
push!(stack, a / b)
elseif s == "="
checkStack(1)
println(stack[end])
else
push!(stack, parse(Float64, s) )
end
end
function main()
println("RPN Calc: Type numbers or operators (+, -, *, /) one at a time, = to show top of the stack, ^D to end.")
while true
print("> "); flush(stdout)
s = readline(stdin, keep=true)
if s == ""
println("Goodbye")
break
end
try
parseLine(s)
catch e
println(e.msg)
end
end
end
main()