My First Pony
This weekend I found myself with a couple of spare hours available to squeeze in between some yard work and making sure the baby is still alive and fed. Typically, I would use this time to play with Go or continue my love/hate learning relationship with Rust. This weekend, I decided to play with Pony.
I’m Prejudiced and I Admit it
The best way to get over our prejudices is to admit them. I readily admit that anytime I see someone talking about a new language with an Actor Model implementation, I roll my eyes and think “YAAM” (Yet Another Actor Model).
I’ve done extensive work with the Actor model in Scala and Akka, and have built some basic apps in Elixir/OTP, and have used actor-like implementations like Channels in Go, and even toyed with a few “actor native” languages that no longer exist.
So, in a deliberate effort to overcome this admitted bias, I asked on Twitter if anyone had any reason to believe that Pony was more than just “Yet Another Actor Model”. I mean, if I can already code in Elixir, why would I use anything else?
I’ve been unable to track down the quote for attribution, but there’s a phrase floating around that goes something like, “…any sufficiently distributed system will eventually be thrown away and re-rewritten in Erlang…”
After getting some inspiring replies on Twitter from @ponylang and the Pony community, I decided to give it a go. I started reading.
While walking around the house picking up after the tornado of toys my daughter leaves, I read through the tutorials and other getting started guides on my phone.
I think the documentation could use improvement, but if this is the biggest problem a language has, then things are looking good. Documentation is easy to fix, bad language design isn’t.
I opened up vi (don’t judge me) and created my first main.pony file that printed “Hello World.”
As an author, I am intimately familiar with how utterly useless “Hello, World” is to the real world, but how essential it is to the learning experience. Without any autocomplete, I was about to guess my way through the first build, and I expected it to fail spectacularly. To my surprise, nothing failed!
Everything seemed pretty intuitive, but I hadn’t gotten into anything complicated yet.
Features that Intrigued Me
When you approach a new language, there are usually a couple of “wow!” moments. You’ll be reading along in a sample or some documentation and you’re getting bored. Then, all of a sudden, something catches your eye and you think “that’s pretty cool”.
If you have enough of those moments, you might continue learning. If you don’t, you walk away with your tail between your legs and go back to your old standby languages.
Things that stuck out during my first weekend with Pony:
- Compilation to a single, native binary
- This was huge. Any language positioning itself near Go needs to have this or it’s a non-starter.
- Apparently it requires some shared libraries, but I suspect this will get better over time.
- Strongly-typed, generics-aware Actors (more on this in a bit)
- Reference Capabilities
- Pattern Matching
- Classes - as a gopher and a learner of Rust, I initially saw this as a negative. I had to struggle to keep my mind open on this.
- Structural types (e.g. go-style interfaces)
- Traits that can carry default implementations (like Rust or Scala traits)
- Interfaces can carry default implementations as well!
- Partial application, anonymous functions, lambdas, etc.
- No data-races
As I mentioned, I’ve been learning Rust. Rust promises a lack of data races and safe memory utilization. It does so through the borrow checker, an entity I consider my arch nemesis. After weeks of spending my spare time in Rust, I still hate that damn thing and building anything in Rust feels very high-friction.
I was skeptical of Pony at this point - surely a language as safe and fast as Rust must be hell on the developer?
Thankfully, I was wrong.
I’ve written a number of applications still running in production today that were based on Akka. I presented at ScalaDays on multiplayer gaming with Akka as a back end. I was, and still am, a huge fan.
This contributes to my eye-roll when I hear “actor model”. I just assume someone is re-inventing the wheel as a square and want to move on.
Akka’s actors and Pony’s actors share similar inspiration, but they aren’t the same thing. There is no overarching actor system that I can globally manipulate, nor is there an underlying assumption that actors and messages can travel between nodes in a grid. This feels more like an actorized formalization of asynchronous, in-process, message processing and execution than a full-on distributed actor mesh.
I’m sure others will argue, but I think Akka is geared at solving a different problem.
For those who’ve used Akka, keep this in mind: Pony has generic-aware, strongly-typed actors that don’t suffer from type erasure!
No JVM and I get generics and Actors? Sign me up!
There’s always room for Go in our lives, and anyone who says different is lying. I love Go, and people will pry my golang from my cold, dead hands. That said, I’m also a polyglot and believe strongly in using the best tool for the job. Under the definition of “best” we often find developer productivity.
I can honestly say that I reached a point in 6 hours of working with Pony (while distracted by a toddler) that I have never reached in any other language. Period. And I’ve learned a metric crap-ton of languages.
Building a TCP Chat Server
When learning a new language, I used to always ask myself: can I write a game in this? Somewhere along the way, programming stopped being fun and I stopped asking myself that. Call it burnout, I suppose. Go made programming fun again for me.
When I read the Pony docs, I started getting that same inspired feeling I got from Go again. So I asked myself, Could I write a MUD in this? (If you don’t know what a MUD is, I’m old enough to be your ancestor).
Remember, I’m still only about 3 hours into my Pony experience, so I expected this to go horribly wrong. I adapted the
TCPListener sample from the examples directory to suit my needs.
I started by emptying my main.pony file of everything except the main actor. I don’t know if this is idiomatic Pony or not, but as a gopher I like my main files clean:
use "net" actor Main new create(env: Env) => try let cm = ConnectionManager(env.out) TCPListener(env.root as AmbientAuth, Listener(env.out, cm)) else env.out.print("unable to use the network") end
This is basically a bootstrapper to start up my
Listener class. I don’t want to suck up blog post space with all the code as you can see it all in my github repo.
One of the more important bits of the
Listener class is here:
fun ref connected(listen: TCPListener ref): TCPConnectionNotify iso^ => Server(_out, _cm)
When the TCP listener tells my class that someone connected, it returns a new
Server. These things are
iso, so there can only be one of them (The Pony team may have missed an opportunity to call
iso something like
highlander instead). In the sample above,
_cm is an actor I created to hold the list of all connected clients.
Because actors are passed around as opaque tags, I don’t have to worry about transmitting references to them (this is a huge advantage, with subtle but powerful consequences). It was looking at how to send messages to actors, and the use of
tag that led to my first appreciation for how amazing the reference capabilities system is (and how much more reading I need to do on it).
I put my connection manager actor in its own file. My favorite part of this actor is this one line of code, declaring the
let _players: MapIs[TCPConnection, Player]
let indicates that I’m leaving the value un-initialized, but that the constructor is required to initialize that value. (Remember there are no nulls in Pony).
Tags can be compared with the
is keyword. This means that every instance of every actor in the system can be compared for equality, regardless of the alias holding that reference. Again, this is huge. As someone who lost many hair follicles working with Akka, I love this behavior.
MapIs lets me directly map
TCPConnections (they are actors!) to players (also actors). You can think of the
_players variable as a tag-to-tag mapping. Tags are the least burdensome of things to reference and send.
So now when I get a notification that text came in from a particular connection, my connection manager knows how to dispatch that text to a player actor for input parsing:
be cmdreceived(conn: TCPConnection, cmd: String) => _out.print("[CM] received command text.") try _players(conn).parsecommand(cmd) end
With some really helpful tips from the community and digging through docs and experimenting in the Pony playground, I was able to get a fully functioning multi-user telnet chat server running in Pony. It would’ve taken me twice as long to do that with a language that I already know, let alone one I’m just barely starting to learn.
This little bit here was so satisfying to do after just a few quick hours of tinkering:
$ telnet localhost 53598 Trying ::1... Connected to localhost. Escape character is '^]'. Kevin You've chosen a name. Kevin has logged in. Hello! [Debug] You typed Hello!. Kevin typed 'Hello!'
After just a weekend (and only a few hours of it, really) with Pony, I am hooked. I now have a hobby backlog of a dozen projects I’d like to try out with Pony, and I’m probably going to continue messing with my chat sample to see how far down the rabbit hole I can go before I run into high-friction areas of the language.
A friend of mine once said you can’t really call yourself a (insert language here) programmer unless you can name 3 things you hate about the language.
So my new goal is to keep playing with Pony until I find something I don’t like about it… and then see if I can fix it :)