Let's talk education. If you want to learn programming without spending years on it, there are basically three decent ways that are proven to work:
- you start with Ruby to learn it properly in the best language, with proper TDD and so on (what the best bootcamps are doing)
- you start with HTML+CSS, then learn JavaScript (frontend route)
- you start with Python, in Jupyter style environment (data science route)
Most other languages are terrible for beginners, due to enormous amount of additional complexity that gets in the way of learning. Yes even the "good" languages. That's not to say Ruby, JavaScript, and Python aren't complex - but in their case complexity is largely opt-in, and beginners can get to employable levels of proficiency without ever creating a Ruby DSL, Python decorator, JavaScript prototype chain, or such.
There are a few more languages that are approachable for beginners (SQL, Spreadsheets, Logo), but they offer no easy path to real world development. All your elite Excel skills won't transfer into programming skills well.
Universities teach programming all wrong. They start with an extremely beginner-hostile language like C, Java, SML, Scheme, or whatnot, and the only reason programmers come out by the end is that they have years to do that.
So anyway, Pyret is a new programming language designed specifically for education, that tries to be far more beginner-friendly than any existing language, while teaching transferable skills. Let's see how it worked.
Hello, World!
We can start by writing a Hello, World!, and the language already fails, by printing a lot of irrelevant crap in addition to our message:
#!/usr/bin/env pyret
print("Hello, World!")
$ ./hello.arr
1/1 modules compiled (hello.arr)
Cleaning up and generating standalone...
Hello, World!
The program didn't define any tests.
We need to pass a bunch of flags (-q
for quiet, -k
for no tests) just to get hello world working:
#!/usr/bin/env pyret -qk
print("Hello, World!\n")
$ ./hello2.arr
Hello, World!
Fibonacci
Let's do the Fibonacci sequence!
#!/usr/bin/env pyret -q
fun fib(n):
if n <= 2:
1
else:
fib(n - 2) + fib(n - 1)
end
end
check:
fib(1) is 1
fib(2) is 1
fib(10) is 55
end
This part isn't too bad. fib
definition is fine, in some hybrid Ruby-Python syntax.
Pyret wants unit tests to follow the code with check:
block. In real apps, I don't like this mixing of app code and test code, but this is a reasonable thing to do in language for beginners. Pyret uses ==
for comparisons in normal program, but in tests it's is
, which seems like unnecessary complexity π€·.
Thing go downhill fast when we try to print it, in the same format I always use.:
for each(n from range(1, 21)):
block:
print("fib(")
print(n)
print(")=")
print(fib(n))
print("\n")
end
end
WTF is this? Pyret has no string interpolation, which is an insane obstacle to throw at beginners. There are no easy workarounds like passing multiple arguments to print, or automatically turning numbers into strings with "fib(" + n
. Oh and if you have multiple expressions in a block, you need to wrap the whole thing in a pointless block:
block. WTF?
OK, let's try another approach, how about we construct the string once, without interpolating?
for each(n from range(1, 21)):
print("fib(" + to-string(n) + ")=" + to-string(fib(n)) + "\n")
end
This is far less readable than string interpolation would be:
# Not Pyret
for each(n from range(1, 21)):
print("fib(#{n})=#{fib(n)\n")
end
And of course it even prints some crap with -q
, instead of doing the reasonable thin and only printing extra stuff when there are problems:
$ ./fib2.arr
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
Looks shipshape, all 3 tests passed, mate!
You can disable tests with -k
, but then you won't know if there are any issues. Overall, it's baffling design all over.
FizzBuzz
Here's one way to do the FizzBuzz. I'm converting n
to string inside fizzbuzz
function, as printing is otherwise such pain. Pyret doesn't have println
like vast majority of languages, and you can't print(n, "\n")
or print(n + "\n")
or even just have two print
without wrapping the whole thing in a block: ... end
.
#!/usr/bin/env pyret -q
fun fizzbuzz(n):
if num-modulo(n, 15) == 0:
"FizzBuzz"
else if num-modulo(n, 5) == 0:
"Buzz"
else if num-modulo(n, 3) == 0:
"Fizz"
else:
to-string(n)
end
end
check:
fizzbuzz(30) is "FizzBuzz"
fizzbuzz(31) is "31"
fizzbuzz(33) is "Fizz"
fizzbuzz(35) is "Buzz"
end
for each(n from range(1, 101)):
print(fizzbuzz(n) + "\n")
end
And again, there's an extra stupid line at the end:
$ ./fizzbuzz.arr
1
2
Fizz
4
Buzz
...
Fizz
97
98
Fizz
Buzz
Looks shipshape, all 4 tests passed, mate!
Defining a class
Let's define a Person
class! Oh wait, we can't. Pyret doesn't have classes, it only has ML-style data types:
#!/usr/bin/env pyret -q
data Person:
person(first-name, last-name)
end
check:
to-string(person("Alice", "Smith")) is "person(Alice, Smith)"
end
people = [list:
person("Alice", "Wonderland"),
person("Bob", "Marley"),
person("Eve", "Jackson"),
]
for each(a-person from people):
print(to-string(a-person) + "\n")
end
$ ./person.arr
person(Alice, Wonderland)
person(Bob, Marley)
person(Eve, Jackson)
Looks shipshape, your test passed, mate!
The logic of this choice is - why teach people OOP which pretty much every modern language is based on to some degree, when you can teach them dead 1970s' approach to programming, right?
Operator Precedence
OK, just one more thing, can Pyret at least do basic math:
#!/usr/bin/env pyret -q
a = 20
b = 4
c = 100
print(a + b * c)
Of course it cannot:
$ ./math.arr
Operators of different kinds cannot be mixed at the same level, but `+` is at math.arr:7:8-7:9 at the same level as `*` at math.arr:7:12-7:13. Use parentheses to group the operations and to make the order of operations clear.
There were compilation errors
Because obviously an average programming student never had a single math class.
By the way Pyret is not the only language which does this. Smalltalk-derived Self language does, because Smalltalk had an insane idea of not having operator precedence (2 + 3 * 4 means 20 in Smalltalk), so to dig itself out of this insane hole, Self forced parentheses in this situation. This way any Smalltalk code ported to Self, which didn't obey the usual rules, would need some extra parentheses one way or the other. Better explicit than broken. Pyret has zero reason to do this.
Should you use Pyret?
No. The language is terrible, and completely unsuitable for education, or for any other use. It's basically ML with nicer syntax, and nobody needs that.
One big area where languages differ a lot, and where a beginner friendly language could make a big difference is error messages. Experienced programmers can deal with cryptic errors, beginners really can't. Pyret fails at this as well, and its error messages are generally just awful.
The language is bad, and you should use Ruby, JavaScript, or Python for teaching beginners. Absolutely not this.
Code
All code examples for the series will be in this repository.
Top comments (1)
For education of the form "1st language beginner learns will be The language they'll later productively develop in" (which is certainly a sane default approach!), nobody should teach Pyret. Nobody will be hired as a "A Pyret developer".
But some of the people behind Pyret have been literally iterating for decades on how to teach programming, and investing in tooling for that, and they have a very specific approach.
Some of their courses take time to talk about what makes programming languages tick, and for this there is long history that using a "clean but powerful" toy language helps. (Indeed, universities can afford that only because "they have years to do that".) They were influenced by SICP which used scheme[1] β BTW by far best course I got in university. They built different course HtDP taking a very thoughtful[2] deviation from SICP, and invested a ton in the Racket teaching-friendly lang & IDE.
Conversely, they care about intro courses for non-CS majors, and have ongoing involvement in teach-algebra/physics-in-school-via-some-programming[3].
[1] There is now a JS translation, and it's probably a good tradeoff to make nowdays, but reading the comparative edition sicp.sourceacademy.org/ I must say JS adds a lot of obstacles to the goals of SICP.
[2] www2.ccs.neu.edu/racket/pubs/jfp20...
[3] bootstrapworld.org/
[4] pyret.org/pyret-code/
[5] dcic-world.org/2022-01-25/part_int...
LOL. See blog.brownplt.org/2018/06/11/philo...
These people invest so much in error messages they even worked on provably "optimal" algorithm for color assignment in error messages. (In their IDE, not terminal.)
I mean, I 100% get why their choices are somewhat contrarian and unobvious.
It's very hard for any special-for-education language to compete with top off-the-shelf languages like Ruby/Python/JS (and Python in particular always cared about learning curve).
But if it's "completely unsuitable for education", that's sad, because these people are some of the most deliberate and prolific builders of tooling-for-programming-education on the planet, and if they can't build a worthy custom approach, who can?
Mind you, I don't know what I think of Pyret itself ;-P