Arturo is an another attempt at creating a minimal programming language.
I don't think it saw any use other than by its author yet, but it has a homebrew package (brew install arturo
) and even VSCode extension, so let's give it a try!
Hello, World!
We can start with the very obvious Hello, World! program.
This identical program works in so many languages, they mostly differ in newline being included or not, and if you need parentheses or if they're optional.
#!/usr/bin/env arturo
print "Hello, World!"
$ ./hello.art
Hello, World!
Obnoxiously, Arturo prints Windows \r\n
newlines even on OSX.
Arturo also has REPL with arturo
. It annoyingly erases the whole screen and prints massive banner, and also only quits on Ctrl-C instead of Ctrl-D. These might sound like minor issues, but they're highly disruptive if you want to get into and out of repl. REPL should not mess the screen, it should only show prompt (maybe one line for version etc., but I'd recommend against it) and quit on Ctrl-D. Not following these basic rules is wrong.
Operator Precedence
Like many other minimalist languages, Arturo makes the same mistake of not having operator precedence. However unlike every single one of them which then evaluates left to right, Arturo evaluates right to left, possibly the world's only such language.
Of course I'd strongly advise even "minimalistic" languages to get their shit together and implement proper operator precedence. A lot of languages like Smalltalk's progeny had to undo this madness, and this is always more painful than getting it right in the first place.
#!/usr/bin/env arturo
print 2 * 3 + 4
It prints an answer that's not 10
:
$ ./math.art
14
In practice this system just means parentheses everywhere, as it's not possible to overcome decades of math education like that for no reason, and without parnetheses everywhere you'll constantly be reading everything incorrectly.
Variables
Arturo variables are wild:
#!/usr/bin/env arturo
a: 1
b: 1
c: new 1
inc 'a
print a
print b
print c
What do you think this prints?
$ ./variables.art
2
2
1
WTF just happened? It goes beyond "modifying string literals" problem, we're modifying integer literals, and it wasn't even the same literal.
We can fix it by new
, but I'm completely baffled by why anyone even thought to make them work like this.
FizzBuzz
We can do it with if? condition [then-block] else [else-block]
:
#!/usr/bin/env arturo
loop 1..100 'n [
print [
if? 0 = n % 3 [
if? 0 = n % 5 ["FizzBuzz"] else ["Fizz"]
] else [
if? 0 = n % 5 ["Buzz"] else [n]]
]
]
]
Fibonacci
Arturo has string interpolation, with yet another syntax. If someone was interested in collecting all the variants, maybe there's a 100 string interpolation syntaxes by now.
It's also really damn slow, even this fib 1-20 takes 2s, fib 1-30 takes 4 minutes. By comparison Python does 1-30 fib in less than a second. So we're talking about 300x slower than Python.
#!/usr/bin/env arturo
fib: function [n] [
if? n =< 2 [1] else [(fib (n - 1)) + (fib (n - 2))]
]
loop 1..20 'n [
print ~"fib(|n|) = |fib n|"
]
$ ./fib.art
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34
fib(10) = 55
fib(11) = 89
fib(12) = 144
fib(13) = 233
fib(14) = 377
fib(15) = 610
fib(16) = 987
fib(17) = 1597
fib(18) = 2584
fib(19) = 4181
fib(20) = 6765
Unicode
Arturo correctly handles Unicode:
#!/usr/bin/env arturo
print upper "Żółw"
print lower "Żółw"
print size "Żółw"
print size "🍰"
$ ./unicode.art
ŻÓŁW
żółw
4
1
Maybe
Blocks are passed as variables, and we can just execute them with do
. In this example passing f
instead of [do f]
would also have worked.
#!/usr/bin/env arturo
maybe: function [f] [
if 0 = random 0 1 [do f]
]
loop 1..20 'n [
maybe [print n]
]
$ ./maybe.art
1
2
4
6
8
9
12
16
Wordle
Wordle is becoming the FizzBuzz of this series, so let's do one for Arturo as well.
By the way if you want to do a similar series, I think Wordle is a great toy problem, as it does file reading, string manipulation, user input, looping, Unicode, randomness, and all in very small amount of code.
Wordle in Arturo is quite decent:
#!/usr/bin/env arturo
words: split.words read "wordle-answers-alphabetical.txt"
word: words\[random 0 ((size words) - 1)]
guess: ""
while [guess <> word] [
guess: input "Guess: "
if? 5 <> size guess
[print "Guess must be 5 letters long."]
else [
loop 0..4 'n [
case []
when? [guess\[n] = word\[n]] [prints "🟩"]
when? [contains? word guess\[n]] [prints "🟨"]
else [prints "🟥"]
]
prints "\n"
]
]
$ ./wordle.art
Guess: arise
🟥🟥🟩🟩🟩
Guess: noise
🟥🟥🟩🟩🟩
Guess: guise
🟩🟩🟩🟩🟩
prints
is the non-newline version of print
and case
is what Arturo has instead of if/elsif/else
chains.
Should you use Arturo?
I don't recommend it.
If you try Arturo, operator precedence is likely going to be a huge pain, Arturo makes so many baffling design cohices, and it doesn't provide good feedback if you make mistakes.
If you want a minimalistic language to play with, I'd recommend starting with Ioke or Brat. If you tried them already, and want to try something else, then I guess Arturo is an option for another weekend.
Code
All code examples for the series will be in this repository.
Top comments (2)
Despite the "bashing" (lol) Arturo took in your unofficial... review, as Arturo's main developer, first, let me thank you for taking the time to have an (obviously quite good) look into it and writing this post. I really appreciate it - including the criticism - which for me has always something positive to learn from and move the project to the right direction.
I generally understand most of the objections you have with Arturo, as a language; some are quite obvious ones, some less so, but still...
Regarding the way the REPL behaves, I've been working on it like a lot in the past few months and I think most - if not all - of your recommendations could be implemented. A challenge here has always been Windows (whose terminals have made my life unnecessarily difficult), but what can I say. I bet anybody with at least some experience in terminal- (and even UI-) app portability can feel my pain... lol
Regarding the elephant in the room: operator precedence. I understand your issues, and I obviously get the math-side of things. But programming is not exactly maths. If it was only about multiplications/divisions/additions/etc, your argument would be totally valid. But, in programming, we usually have tens of different operators, with wildly different precedence rules depending on the language in question (I admit, I've never been able to memorize more than a few - and I honestly don't know why I have to...) and, yep, that usually ends in parentheses hell, indeed. Arturo's approach is quite simple: since symbols - even infix ones - are nothing but aliases to functions (yes, even
+
and*
, etc actually point to standard-library functions; in that case,add
andmul
respectively), why would they suddenly break the rule that exists throughout the language? So, the solution is quite clear-cut: we simply pick a rule and stick to it. (Consistency has been one of my main priorities since the very beginning, so here we go...). And, no, Arturo is by no means the only language out there to follow this example: REBOL, Red, Ren-C, namely (and the whole group of "sibling" languages, as a matter of fact) also do something along the exact same lines.As a sidenote, this for example:
if? n =< 2 [1] else [(fib (n - 1)) + (fib (n - 2))]
looks pretty much like the parentheses hell you describe; but it has nothing to do with math operator precedence per-se. I would say it's more about knowing how the language actually works. And once you know, IMHO, it's actually far simpler and straightforward:if? n =< 2 -> 1 else -> (fib n-1) + fib n-2
. (I honestly doubt whether anybody would argue this looks confusing or over-complicated...)As some interesting trivia, Grafito - mainly, the largest and most complete project fully-written in Arturo - in its 1200+ lines, you will notice there are some 15 pairs of parentheses in total. And, no, it's not like I was struggling to reduce them. That's pretty much what happened naturally.
Regarding variables, you do have a point as well. And I fully understand the confusion. It's one of the points that I do want to iron out. Only thing I just have to mention is that this behavior happens only when you change a variable "internally", that is: by passing it as a literal to a function. E.g.
i: i + 1
is totally safe and transparent. Things likeinc 'i
are tricky (essentially, it's like passing by-reference; it's a pointer, not a value). But quite powerful, once you actually know what you're doing.Last but not least, regarding your criticism about Arturo's speed, I'm totally with you. I'm not satisfied with it either. But given that I wanted to get the language (and its library) to a rather mature stage first, I guess I decided to put off the optimization of its VM. But it will happen. And soon. ;-) Generally, it's not that... horrible. It mainly depends on the use-case: admittedly, recursion and function calls are not efficient, yet; but, on the other hand, in many other cases, it already beats hands-down e.g. Ruby and the vast majority of interpreted languages (with the only exception, perhaps, being Lua - which is as lightweight as you can possibly get, with all the shortcomings this has, ofc). So, corner-cases aside, I think we're not too far off our goal - for a stack-based bytecode VM written from scratch that is pretty-much in a vanilla, non-JIT, no-nothing, unoptimized state, that is.
Again, thanks for taking the time to review Arturo - and all the constructive criticism. Needless to say, you are more than welcome to post any issue to GitHub as well (I look into all of them, one by one, and take them into account very seriously) and even participate by contributing code to the project.
Have a great day! :)
Yanis
(aka Dr.Kameleon)
By the only one I meant that Arturo is the only one I'm aware of that evaluates right to left. A few like REBOL, Smalltalk etc. go left to right. Self does another interesting thing, as it bans mixing of different operators.
2+3*4
just won't compile in Self, you need parentheses. Self was one of the languages I wanted to review, but there were technical issues that prevented it.Vast majority of languages follow math rules, or have syntax where it doesn't matter (Lisp, Forth, Assembly etc.).
I only mentioned speed issues for languages which are into "100x slower than Python" category. Speed is definitely a fixable issue.