Julia as a CLI Calculator
krasjet.com
krasjet.com
This has always been a huge complaint for Julia and it seems like one of those things that happens to new languages. People are excited, there are a few big rough edges that drive people away, they get ignored or underprioritized for WAY too long, and a giant window of opportunity is missed. I think of D with weeding out garbage collection, go with generics, D with tools and infrastructure, Zig with its refusal to parse carriage returns or tabs, Rust with compilation times, everyone with a lack of an easy road to a GUI, Jai not being released at all...
I think instead of getting lots of areas to a decent stage, languages end up trying to be the best in a single area and placate their hardcore users, when things like C++ succeed by not having huge blindsides and pitfalls, even if it ends up a little rougher in many areas.
what??
edit: also, time-to-first-plot is a lot less than 30 seconds for me these days, even without PackageCompiler. (time-to-second-plot is, as usual, instantaneous)
The only real problem I have experienced in Julia is using plotting libraries and those you can include in your Julia image with PackageCompiler.
$ time ~/Desktop/Julia_Releases/Julia-1.4.app/Contents/Resources/julia/bin/julia -E "1+1"
2
~/Desktop/Julia_Releases/Julia-1.4.app/Contents/Resources/julia/bin/julia -E 0.14s user 0.08s system 65% cpu 0.342 total
In about 0.5 seconds, I can actually do something non-trivial that actually needs non-trivial compilation - like summing the sin of a million random numbers $ time ~/Desktop/Julia_Releases/Julia-1.4.app/Contents/Resources/julia/bin/julia -E "sum(sin.(rand(1000000)))"
460165.3756259715
~/Desktop/Julia_Releases/Julia-1.4.app/Contents/Resources/julia/bin/julia -E 0.59s user 0.13s system 93% cpu 0.769 totalEdit: Now I bothered to measure. You were saying?
~ time julia -E "1+1"
2
julia -E "1+1" 0,59s user 0,14s system 153% cpu 0,476 total
~ time julia -E "sum(sin.(rand(1000000)))"
459807.21199623536
julia -E "sum(sin.(rand(1000000)))" 1,01s user 0,17s system 125% cpu 0,948 totalMy main point was that while half a second (~0.5s) is perceptible startup time, ~0.1s is not. Is it a much older system than mine? I have a 2.3GHz Core i5 from about 2017.
I don't even think 0.5 seconds are that noticeable...
This doesn't really bother me, but maybe I should try PackageCompiler or adding `using OhMyREPL, Revise, Cthulhu, BenchmarkTools` to a userimg.jl for when I build from source.
Half a second is precisely what is meant by "excruciatingly slow". If I need a simple calculator, I expect to be able to use it to compute intermediary results in a shell loop. If my loop has one thousand iterations, you are spending about 10 minutes just initializing the interpreter. This is absurd. Of course, you can rewrite your whole loop upside down, so that the interpreter is called outside the loop. But this kind of defeats the goal of considering julia to be a general-purpose calculator, orthogonal with its usage patterns.
No offense but that must be one of the dumbest ideas I have heard in a long time. The reason you write something in a shell script is because it is portable on all Unix systems. If you add a dependency to a particular programming language like Julia, then that portability is out the windows. You require installing users to install Julia first.
If you require users to install Julia, you might as well ditch the shell script all together. All shell languages suck anyway for anything non-trivial.
I have rewritten plenty of shell scripts to Julia. It is much faster to write. The code is easier to read and understand and it executes way faster than any shell script.
Running a complicated shell script that calls out to Julia 1000 times makes absolutely NO SENSE! It is a completely contrived example with no practical application.
Besides when people say "calculator," I think most reasonable people assume an interactive system for doing calculations, not a glorified math library. Julia offers a great interactive environment for doing calculations.
Thanks. This is not the first time I've been told that.
> The reason you write something in a shell script is because it is portable on all Unix systems.
This is definitely not my case. I write plenty of shell code at work, but rarely distribute it. It is just for arranging data around, preparing one-off experiments, and so on.
> Running a complicated shell script that calls out to Julia 1000 times makes absolutely NO SENSE!
I agree that this may be a rare use case, but it is legitimate nonetheless. Moreover, the best tools are those that can be used successfully in ways that the creator of the tool would find abhorrent.
> It is a completely contrived example with no practical application.
This was a concrete example actually. I had a shell script that cropped, denoised and registered a collection of a few thousand photos scattered in a directory tree. It was a four-line pipeline that called a few command line tools from imagemagick and gmic to do the image processing work. At one point, I realized that I had to apply an homography to the images (given by a 3x3 matrix that was stored as 9 numbers in a text file) to map them to a different coordinate system. This involved a 3x3 matrix multiplication for each image. I did this multiplication in julia, my favorite calculator. The running time of the whole thing went from 10 to 20 minutes. Doing the "correct" thing and rewriting everything in julia would be extremely painful.
> Besides when people say "calculator," I think most reasonable people assume an interactive system for doing calculations
When you say "CLI Calculator", you can also think about a calculator that you can call from your shell command line. This is the case for example for the classical calculators bc(1) and dc(1), which are non-interactive, and purposefully intended to be called inside loops in a shell script.
Not necessarily: https://docs.julialang.org/en/v1/manual/running-external-pro...
And like I've posted above, if you use certain packages frequently and restart the REPL frequently, why not use PackageCompiler to compile your hot paths into your binary, like described in the article (admittedly at the end)? That way, even the first call in a new session will be fast because it has already been compiled. On top of that, the core devs are very much aware that this is a problem and it's actively being worked on.
You have a very peculiar use case that most other users don't! ...either you write a shell script, or a Julia script. It's preposterous to want everything to "start instantly", you'll only get stuck with the simplest most primitive tools this way. We'd all be writing C, Bash and PHP by this reasoning.
or hack up a daemon-process thing where you can start it up outside the loop, then call it inside the loop
I suppose Snail could be used in a same way (https://github.com/gcv/julia-snail)
The beta version of version 1.5.0 starts up in 0.25 seconds with --compile=min and I believe it's the same on 1.4.0
Asking for a friend...
For more information, see the colophon [1] and karasu [2].
When designing your personal/corporate website/blog, how much weight do you attribute to users whose browsers refuse to load remote fonts (by preference or otherwise)?
I ask because I've noticed recently that some websites (notably, many google sites) are unusable, because they depend on remotely-loaded icons, which are replaced with unreadably-stretched alt-text with unintended form factors.
On this blog, for example, the default preformatted-font didn't seem to be monospaced, and the result is that the "julia" name is rendered incorrectly.
All this is meant to ask, do you ever test your designs with browser-default fonts? If not/so, is this conscious? Why?
To be clear, I intend for this to be a survey, not an inquisition. I'm trying to understand this trend better.
For my own answer. At my place of work, in spite of an avowed obsession with accessibility, our designs are regularly sabotaged by browser-default fonts and poorly-rendered alt-text.
What struck me was the [ t a b ] stuff as well as the Latex reference, which looks to be embedded img in src, so there must be some sort of preprocessor?
The downside: it’s not free (as in beer or speech).
- https://www.wolfram.com/raspberry-pi/
- https://blog.wolfram.com/2019/07/11/mathematica-12-available...
If you just need to a calculator to do everyday arithmetic or unit conversions, or even simple physics calculations with units, it’s serious overkill... I certainly hardly ever need it when I’m wearing my programmer hat.
I guess to answer is that the boundary between a calculator and a full blown computational environment is very blurry to me.
On an unrelated note, (as a mathematician) I have started using Maple a lot more as my analytic computational helper. Never really gave it the time of day in years past - it is quite powerful and intuitive. Funny note: I recently copied and pasted output of a Maple command into my Julia code and it was syntactically correct!
I use the Emacs calculator (M-x calc) and XCalc (http://www.tordivel.no/xcalc/) as I like to use RPN calculators. The Windows 10 calculator is painful and slow. XCalc has a mini mode, where it will sit as a small, single line without distracting you too much. I like that feature very much.
One thing with Emacs is that I forget the shortcuts for non-frequent calculations that I'd have to go and search for it.
$ alias units='units --verbose' # in your shell rc
$ units
Currency exchange rates from FloatRates (USD base) on 2019-06-05
3460 units, 109 prefixes, 109 nonlinear units
You have: 56 kibibit/second
You want: hour/20 mebibyte
reciprocal conversion
1 / (56 kibibit/second) = 0.8126984126984126977077949 hour/20 mebibyte
1 / (56 kibibit/second) = (1 / 1.23046875) hour/20 mebibyte
Note that if you are on macOS, you want to install a more recent units via homebrew. The one shipping with the base system is positively ancient.During grad school, I went through a period of trying many different GUI based calculators, until I realized what I really wanted was a language's repl.
See Andrew Hyatt's Emacs calculator for Financial calculation tutorial: http://web.archive.org/web/20171218022230/https://plus.googl...
(Link rot, Google Plus, web.archive.org!)
The other thing I do frequently is fire up a python3 repl for quick hits. I just did it yesterday for a complex mass file rename I couldn’t figure out how to do with shell variable replacement. With python it was easy with glob and os.rename.
$ ev() { echo "scale=3; $*" | bc -l; }
$ ev 22/7
3.142$ math() { echo "scale=2 ; $" | sed -e "s:x::g" | sed -e "s:,::g" | bc; }
$ math 5,382 x 48,927.3
263326728.6
$ math() { echo "scale=2 ; $*" | sed -e "s:x:*:g" | sed -e "s:,::g" | bc; }
$ math 5,382 x 48,927.3
263326728.6The one downside is you can't do unit conversions of a mathematical expression, just of a number. (i.e., 2^2 m/s does not work but 4 m/s does).
Whenever I have to do something more complicated I usually use M-x calc or maxima with ezunits.
The non-RPN one that I use the most is SpeedCrunch (http://speedcrunch.org/), Insect (https://github.com/sharkdp/insect/) and Frink (http://frinklang.org/). All have unit conversion too.
Other than that, I use scratch in elisp mode and eval. Not as fluent as magnars, but this is a good example of what I mean (towards the end): https://youtu.be/jNa3axo40qM
Qalculate takes unit-aware calculation to the next level by including an algebraic solver. You don't have to rearrange everything to put your unknown on one side - you can just chuck 'x' into your equality somewhere and it'll work it out. For example, to verify the what-if xkcd assertion that an A10's main gun will propel Randall's car up to 7mph in 3 seconds:
(60/s) × (x s) × (395 grams) × (1010 m/s) = (3500 lbs)(70 mph) ≈ x = 2.0754319 s
2 seconds! Maybe Randall's car is heavier than 3500 lbs. How much heavier?
(60/s) × (3 s) × (395 grams) × (1010 m/s) = (x lb)(70 mph) ≈ x = 5059.1879
More like a Dodge Durango. Unless he's accounting for friction.
You see how nice it is to be able to swap around the unknowns in an equality. Other great features are a large number of built-in constants, an astounding variety of built-in functions - it's practically a CAS - and a command history, which is where I pulled the above example from. It also has a plugin for Plasma's krunner, so you can do quick calculations right there in the dropdown. To summarize: Qalculate is the shit.
Otherwise I use WolframAlpha a lot.
julia> sin(1.461920290375737576933544899379e+31)
-0.9468766486679395
julia> sin(parse(BigFloat, "1.461920290375737576933544899379e+31"))
0.6864670207863400975666631018263839509022548965872940746039593018855528710432756
Fricas is better in this regard, some links:https://en.wikipedia.org/wiki/FriCAS
https://github.com/fricas/fricas
http://www.euclideanspace.com/prog/scratchpad/fricas/
EDIT: some info about FriCAS: it's implementation is currently based on Common Lisp, on top of which a language called "Spad" (short for Scratchpad) is implemented. Fricas is a fork of the Axiom computer mathematics system, which in turn is a continuation of Scratchpad. EDIT: and, at least with SBCL, it can use GMP for bignum.
I only skimmed the article, but I believe Fricas supports all uses of Julia mentioned in the article, and more, like advanced symbolic integration.
julia> sin(big"1.461920290375737576933544899379e+31")
0.6864670207863400975666631018263839509022548965872940746039593018855528710432756
to succinctly parse arbitrary precision literals.Care to expand on that? Are you advocating for interval arithmetic or maybe decimal floating point?
For what it's worth I think that one would rarely need to interactively deal with so much data at once that using, say, 10000 bit binary floating point would be prohibitive. It is difficult to lose the necessary significant digits with so much "safety margin", and one still has space for about a million of such high-precision floating point numbers.
Overall I like Julia - and use it as a CLI calculator as well - but there are several things that prevent it from being a good general purpose language:
- OOP support is "meh".
- str.parse(Int).abs is more readable than abs(parse(Int, str)). And this is a very simple case, the readability difference is greater in more complex expressions.
- 1-based indexing.
- Lack of good support for nullability, think of "if let", "guard let", "??", "object?.property".
- There are some more, this is just from the top of my head.
EDIT: A better example of why Julia's syntax is less readable:
`images.first.resize(100, 100).crop(25, 25, 75, 75)`
versus
`crop(resize(first(images), 100, 100), 25, 25, 75, 75)`
Oof, beauty is in the eye of the beholder. I appreciate the regularity of abs(parse(Int, str)) rather than remember what to call as a function, and what must be called as a method of some object. Of course my preference would be for a syntax that has the added benefit of getting rid of extraneous commas:
(abs (parse int str))
> 1-based indexing
Again, beauty is in the eye of the beholder. A lot of relevant math is 1-indexed. Also, I guess Julia supports arbitrary indexing, though I understand that defaults matter.
I guess that's true, from my experience Julia's way is simply less readable. It also make IDE autocomplete much more useful.
> A lot of relevant math is 1-indexed.
Initially I kind of liked it and disagreed with people who didn't. But as I used Julia over time, I regularly encountered situations in which it was really frustrating and enacted a mental tax. The opposite never happens.
The opposite would be true for a large swath of people coming from scientific computing backgrounds if Julia defaulted to 0-based indexes. That said, in my experience people doing scientific computing have a poorly calibrated sense of what constitutes “hard.” I’m sure plenty of breakthroughs born of brilliant and horrifying code were created over sleepless weekends by grad students who didn’t understand that what they wanted to do was a mental tax.
I guess I don’t hear a lot of people doing scientific computing complain about indexing schemes. I prefer 1-based indexes, but 0-based indexes is a small and trivial issue in my list of grievances against, say, Python.
TaylorSeries.jl is a great example of this. x = x_0 + x_1 t + ... Then indexing into it with x[0] would give the zero-th order Taylor coefficient.
it's not an OO language. It's got a fantastically rich and powerful type system, but the true beauty of it is the way that it allows you to compose functionality. This is having a staggering impact on the evolution of the ecosystem and the progress that Julia libraries are making in creating functionality that now rivals code bases that have been in dev for 10+ years in the Python landscape.
>- str.parse(Int).abs is more readable than abs(parse(Int, str))
I respect your opinion, but mine is different. Perhaps other people find it easier to read asb(parse(Int,str)) as I do?
>- 1-based indexing.
You can change it and choose.
>- Lack of good support for nullability, think of "if let", "guard let", "??", "object?.property".
I don't know anything about this one! Fascinating!
I know, not sure if it this would make things better or worse overall.
> I respect your opinion, but mine is different. Perhaps other people find it easier to read asb(parse(Int,str)) as I do?
I like when the code reads like a sentece, it's just less mental overhead.
>I like when the code reads like a sentece, it's just less mental overhead.
But the OOP version is further from a real sentence here. You're taking the absolute value of the string parsed as an integer, not "for the string grabbing the parser to apply the change to an integer to take the absolute value".
Let's say someone told you (making up random stuff):
"The absolute value of the sum of the mean of the square of two vectors mapped over the product of X and Y."
Try to think about that in your head. By the time you've reached the end of the sentence, there have been so many steps and no context values that you can't even follow.
If you take the opposite though:
"Take the product of X and Y, map them over two vectors, square it, sum that, and then return the absolute value."
(Again, can't guarantee these are semantically equal to each other, but you get the idea)
Then you know "Okay, starting from these things, first I do this, then I do this.." whereas the opposite way, you're given a ton of directions with zero context, and then have to try to apply them all in reverse once you're finally given the starting value.
You don't give directions to someone starting from the destination and working backwards.
abs(sum(mean(abs2,x .* y)))?
It's probably even less readable to be honest. And it would mean there are now 2 ways of doing something which makes the language irregular (unless everyone switches to this syntax).
I would just read it as: "Take str, convert it to an int and take the absolute value." It also corresponds to the order of steps as it actually happens.
Another example where the readability difference is clearer:
`images.first.resize(100, 100).crop(25, 25, 75, 75)`
versus
`crop(resize(first(images), 100, 100), 25, 25, 75, 75)`
The former one isn't just more readable, it's also easier to write.
Interesting. I love Julia specifically because it doesn’t use an OOP approach. At least for the kind of work I do, I have found multiple dispatch to be far more powerful and cleaner to reason about than dispatching on the type of an implicit first argument.
“str.parse(Int).abs is more readable” No, but it makes sense if you also think that OOP makes sense.
You can index based on anything you want.
I know that I can set indexing to be 0-based. Not a good solution.
You got to compare it to Ruby, Python etc.
Also I have no idea what an interface definition would look like in Julia. A function in Julia isn't bound to one specific type like in an OOP language.
interface AnimalActions
function walk(x::Animal) end
function call(x::Animal, y::Animal) end
function eat(x::Animal, z::Food) end
end
struct Dog <: Animal
name::String
end
AnimalActions.walk(x::Dog) = true
AnimalActions.call(x::Dog, y::Animal) = true
AnimalActions.eat(x::Dog, z::Food) = true
setinterface Dog AnimalActions end
and then I guess using it like using AnimalActions
d = Dog("rufus")
walk(d)I can't tell you how many times I've seen disparate packages written by different authors with no explicit effort towards interoperability "just work" together in Julia (e.g., [1]), something I have never seen to a comparable degree in any other language.
crop(resize(first(images), 100, 100), 25, 25, 75, 75)
this will likely get better when julia implements proper pipes, IIRC coming soon in the plans (like elixir does): images
|> first
|> resize(100,100)
|> crop(25, 25, 75, 75)
If the language bothers to implement an inspect method (again, like elixir) this is vastly superior to . chaining because you can do this: images |> inspect
|> first |> inspect
|> resize(100,100) |> inspect
|> crop(25, 25, 75, 75) |> inspect
trivially with multiline cursor (and also trivially ninja'd out with multiline cursor) and have full introspection on the evolution of your data through the pipeline. This is impossible in oo languages (well maybe you could do it in a prototyped language) because dispatch of the .inspect method would have to be implemented on each object. julia> x = [3,2,4,5]
4-element Array{Int64,1}:
3
2
4
5
julia> x |> sort |> x->reshape(x,(2,2))
2×2 Array{Int64,2}:
2 4
3 5
works.It gives a syntax error if the pipes are on the newline. Is that your gripe?
Or is it that it doesn't automatically partially apply the function, requiring you to use an anonymous function, like `x->reshape(x,(2,2))` instead of as you have it `resize(100,100)` implicitly. I prefer the explicitness.
You don't know what you're missing by not having an inspect function. In elixir it's styled "IO.inspect", and it pretty-prints the value as a side-effect, and returns the value unchanged. You can pass a "label" option as your second parameter, and that will add a label to the pretty print. For example, assume I have a module M with the expected functions:
value = 10
|> M.add_3 |> IO.inspect(label: "A")
|> M.times(4) |> IO.inspect(label: "B")
|> M.minus_2 |> IO.inspect(label: "C")
will print: A: 13
B: 52
C: 50
and value will be assigned 50.Now, the great thing about that is that you can blat a bunch of IO.inspects simultaneously using multiline cursors. That low friction 1) causes you to actually use it, and 2) cause you to also set your code up so that it's easy to IO.inspect, which typically also means cleaner, and easier to read code. It is IMO the most powerful debugging tool that I've ever used.
Example: https://github.com/ityonemo/ex_dhcp/blob/master/lib/ex_dhcp....
That small pipeline does a pretty complex set of actions - deserialize a raw binary to a structured datatype, then call a handler, then process a handler return result. There are four datatypes that run through that pipeline. Nonetheless, I can inject IO.inspects into that with six keystrokes (I have a vscode snippet), run the code, and watch as the data are transformed, and immediately identify what corner case I missed and write a proper test for it.
And yes, it is far less powerful if the pipes can't tolerate the newline. (though you usually can still multicursor on a single line).
Anyways, I'm not saying this to rag on Julia. I'm just saying I think julia can and SHOULD have this, and it would be awesome. Also, elixir needs to learn a thing or two from julia's repl.
@_ images |> filter(size(_,1)>100, __) .|>
resize(__, 100,100) .|>
crop(__, 25,25,75,75)
with Underscores.jl. Not exactly `images.first.resize(100, 100).crop(25, 25, 75, 75)` anymore, to point out that this doesn't need every function to prioritise one argument, the pipe feeds into __ in any position. And that .|> applies them to each element.It's possible in languages which support extensions, such as Swift, Dart or Kotlin.
But maybe I don't understand your point, if so, tell me what exactly would be impossible to implement in a language like Swift, Dart, Kotlin.
And boy, am I ever glad it is.
But multiple dispatch support is great!
Swift syntax is really a pain to deal with. I programmed Objective-C for many years, and that syntax makes sense for object-oriented programming. But it is a disaster for functional programming.
Making closures and calling function objects gets really confusing with Swift syntax. Swift methods cannot naturally be passed around like a Julia function.
> str.parse(Int).abs is more readable than abs(parse(Int, str))
The problem is that the Swift approach entirely lacks composability. Julia's `abs` and `parse` can easily be passed around as first class objects. Besides a lot this readability cases can be solved with the `|>` operator like this:
parse(Int, "-12") |> abs
The Julia approach makes it easy to use functions as arguments to higher order functions such as map and broadcast. Using dot (.) is shorthand for broadcast which lets you do stuff like:
abs.(parse.(Int, ["-12", "-3"])) parse.(Int, ["-12", "-3"]) .|> abs
Both cases show parsing an array of numbers and taking the absolute value.
> 1-based indexing.
I know many people hate this but I don't get it. I have spent most of my life with 0-based indexing yet this represents no obstacle for me. When I use Julia I just think as if I am writing math. All my math text books are 1-indexed based and that is just something you get used to. In fact after using Julia for a long time, I frankly prefer 1-based indexing.
0-based indexing IMHO is quite nice when you are dealign with inserting numbers into arrays, or with C-style loops. But neither is usually done in Julia.
> Lack of good support for nullability, think of "if let", "guard let", "??", "object?.property".
That used to bother me as well coming from Swift. But this request actually makes not sense in a dynamic language. What you are basically asking it to have programming constructs to give you type safety at compile time. But Julia is a dynamically typed language, so you will never have this anyway. It if a fools errand.
Julia is built to catch type problems at runtime, not at compile time. Those problems will be caught at runtime in Julia even without `if let` and `guard let`. They don't actually add any value to a language like Julia.
Swift NEEDS these constructs because statically typed languages are quite poor at catching type problems at runtime. They are very dependent on catching type problems at compile time.
> OOP support is "meh". I kind of view that as a positive. I have spent years doing object-oriented programming but I must say the Julia way just makes a lot more sense. It is so much easier to work with. I find OOP inherently bad for a REPL environment. And REPL environments make me a lot more productive. You can built up functionality in a far more incremental and iterative fashion.
I still quite like Swift, but I feel it is going in the wrong direction. I tell tale sign for me when going back to Swift code these days is being reminded just how complex the language and the tools have gotten. If you do Swift everyday, that is easy to forget, but the whole toolchain is actually kind of complex.
For instance getting a closure setup correctly with appropriate tagging for life time/ownership of data is not easy. The Schizophrenia between trying to be both an object-oriented language and a functional one becomes more and more apparent.
Sometimes I wonder if Apple made the right choice and whether going with something more akin to Objective-Smalltalk would have been better. It would have been a more natural fit with the Objective-C syntax, which feels very wrong in a functional setting.
I think it is because it was initially developed for people primarily working with matrices.
Most of the things I do is actually based around multidimensional arrays so when I do operations I almost always want element-wise operations. Unfortunately you often don't even realise you do the wrong operation, just the result is wrong. So I have spend lots of time debugging, until I realised I had a missing dot somewhere, and then spending again significant time trying to find where the dot is missing.
I therefore always found the complaint about np.dot(x,y) being too verbose interesting, because I like that, it makes it explicit and is difficult to overlook.
I personally like the dot syntax. It's ergonomic, and provides cool stuff like this: https://julialang.org/blog/2017/01/moredots/
I don't know of an equivalent functionality in any other language.
Everyone I know always looks for missing dots if they encounter a some nonstraight forward bug in matlab (I don't know any Julia users).
For multi-dimensional array matrix it provides the fastest computation as well, even compared to Chapel and Julia [2].
[1]https://tour.dlang.org/tour/en/gems/uniform-function-call-sy...
[2]https://dlang.org/blog/2020/06/03/a-look-at-chapel-d-and-jul...
> 2 3 + 4 *
as one line, press enter, and get 20. This lets you take advantage of the no-parens nature of RPN while viewing the expression on one line rather than flipping through the stack. Only Windows PowerToy scientific calculator had this mode, that I know of.
A minor quibble in the typography - the use of small caps for every initial letter is slightly catching my eye every time I see it. Perhaps I don't see enough similar typography on the web to make it feel more natural ¯\\_(ツ)_/¯ ...
I may switch to Julia, though. I really prefer the language. Wish I could use it at work.
$ math "999.999 / 3"
333.333
Granted, I don't really do any complex calculations in my terminal.Same. I use insect — https://github.com/sharkdp/insect
J in its simplest use acts just like a desk calculator.
If you are working at a terminal, type the following input, pressing the “enter” key after each line :
3+4
7 5*20
100I therefore use Pari/GP for my personal choice. I like the symbolic nature of Pari/GP over Sage or Julia, even though I spend nearly all of my life programming in Julia, and love the language a lot.
However, for computations of trivial things that I want to run really fast, I put them in a function in Julia (so that they are jit-compiled), since nothing will come close to that, by some orders of magnitude.
1. The data I want to work with tends to be in my text editor so it makes sense to have the calculator there too. Macros can be used to fix up formatting to input numbers
2. Speed of input makes it so much nicer to use. If I look at the examples in the article, here is a comparison. Julia:
7 / 4
x = ans + 2 im
exp(x)
A = [...]
A * [...]
[a^2 for a \in A]
Emacs: 7 RET 4 /
(0,2) + s t x (but I probably wouldn’t use a variable)
s r x E (if I used the stack this is just “E”)
[(same but RET or , not SPC)] s t A
s r A [...] *
s r A V M I Q (that’s vector map inverse square root)
These examples make emacs look bad because I’ve replicated saving things to variables it actually I’d just use the stack. I guess in Julia you can press the up arrow. There are other examples like 1 |> exp |> sin is just 1 E S in Calc (and those operations broadcast over vectors automatically, although I think emasculated does the right thing and doesn’t broadcast exp over matrices).Skipping the massive tangent of non-calculator things about Julia, we move on to a discussion of plotting. There isn’t a Unicode-art plotting mode in emacs because we have had graphical terminals for over 30 years. In emacs you plot with g f and can then modify your plot with other commands. In just about every language you need to call a function called plot so you’re already losing on characters before you’ve even started. If plotting is fast and easy then exploration is easy.
3. The calculator has lots of useful features that fit well together. Plotting is built in. As are unit conversions, a bunch of symbolic algebra (simplification, differentiation, basic integration, curve fitting, solving equations, numeric integration/solutions, and a super nice facility for editing expressions). There’s a bunch of other functions for stats, vectors (both linalg and things like map/reduce), number theory, physics, etc. There are language modes for things like latex output too. Most stuff works on a bunch of number types (bigints, rationals, arbitrary precision floats, infinity, intervals thereof, complex numbers, error forms, symbolic expressions, dates)
4. I want to reiterate the point about speed. The thing I care about being fast is telling the computer what to do. It takes basically 0 time to do most mathematical operations so latency is dominated by input. So even though emacs uses interpreted emacs lisp and doesn’t even use hardware floating point numbers, it still feels faster than Julia with its optimising JIT. If I want to do something with a large amount of data then I can either wait a few seconds or realise that I’ve gone beyond calculator work and use something better suited.
I think Julia is a great language and it has great advantages over emacs for numerical computing. A big one is that the language and type system is much more naturally extensible for this sort of thing. But I don’t feel that it makes a particularly good calculator. The main reason to think that it is a good calculator is that the alternatives (bash/bc/python/matlab) are so bad.