Rolling the Dice with Pony

Kevin Hoffman
5 min readJul 14, 2017

--

Before I get into the technical details at the heart of this blog post, I want to take a quick tangent to talk about failure and community. One universal truth to which I cling at all times is that humans learn through failure. Nowhere is this more apparent in my life than in learning programming languages (as well as spoken languages).

In order to grow, improve, and gain wisdom about how to apply our new knowledge, we need to fail. We need to fail often. This is where community comes in. A community around any new thing that requires difficult learning must be aware of this and embrace it. A community needs to know that new learners are going to fail, and fail often. This community needs to help these new learners through the failure process. If a community is insulting, disparaging, or otherwise unhelpful during these early iterations of stumbling, getting up, and stumbling again… new learners will simply walk away from the community and take their enthusiasm and support elsewhere.

People in the Pony community like Joe Eli McIlvain (jemc) have been remarkably patient with my newbie questions and failure. Every budding community needs knowledgeable, patient people like him.

Now to the technical stuff: I have been working on a Pony port of a library a friend of mine and I wrote called d20. This is a library that converts expressions well-known to dungeon masters like “3d10” or “2D6” into random rolls of the dice and simple addition/subtraction. You feed the roller with your expression and you can get back a result:

let roller = Roller("3D10+5")
let result = roller.roll()
Debug.out(result.total().string())

I didn’t do this because I thought there was any urgent need for such a library, but it presented enough of a technical challenge that I felt it would be a great learning exercise to port d20 from its original Rust implementation.

To do this, I had to create my own iterator that runs through regular expression matches in a loop. I won’t bore you with all the code, but you can find it in my github repo. As my match iterator went through a list of regex matches pulling what I call “terms” out of the “die roll expression”, it parses individual pieces. At some point, I end up converting “3D10” into a class that has a multiplier (3) and a number of sides (10). For the D&D impaired, this basically means “roll a 10-sided die 3 times and add the results”.

Probably the most illustrative class in my project is the Roll class:

class Roll
let _expression: String
let _values: Array[EvaluatedTerm]
let _sum: I64
new create(expression': String, terms: Array[RollTerm] box) =>
let rand = MT(Time.micros())
_expression = expression'
_values = Array[EvaluatedTerm]
for term in terms.values() do
_values.push( ( term, _DiceEvaluator(rand, term) ) )
end
_sum =
try
Iter[EvaluatedTerm](_values.values()).fold[I64]( {(sum: I64, x: EvaluatedTerm):I64 => sum + x._2.i64() }, 0)
else
0
end
fun rollterms() : Array[EvaluatedTerm] box =>
_values
fun values() : Iterator[EvaluatedTerm] =>
_values.values()

fun total() : I64 val =>
_sum

I really wanted to embrace immutability here and I truly love the idea of a “set once and never again” class field (the 3 let fields). There’s something fairly subtle going on here to make this work that I’ll talk about in a minute. But then there’s this:

_sum =
try
Iter[EvaluatedTerm](_values.values()).fold[I64]( {(sum: I64, x: EvaluatedTerm):I64 => sum + x._2.i64() }, 0)
else
0
end

Pony won’t let me get out of my class constructor without assigning a value to _sum, but this use of fold has a couple method calls that could fail. So here I’ve actually used the result of the try expression as the value to be put in _sum, and that satisfied the compiler.

Having to convert _values into an iterator, and then use Iter[EvaluatedTerm](…) to convert it into an Iter just so I can gain access to the fold method felt a little more awkward than a more functional-friendly language might… but it works. Every once in a blue moon I miss Scala’s implicit conversions which take code that would ordinarily look like the above and hide the complexity. Then I remember debugging Scala… and I stop missing it.

💡 SUPER IMPORTANT LEARNING!! 💡

My big takeaway from this learning experiment was not how to parse strings or generate random numbers. My big “lightbulb” moment was when I was trying to set _sum by invoking other methods that accessed class fields.

When I was doing this, I would get all kinds of bizarre (to me at least) errors about how functions I only tangentially used didn’t match capabilities and blah blah can’t infer types and blah blah “you’re an idiot, Kevin, give up now” type error messages.

This all comes down to the fundamental rule that Pony will never allow a partially initialized object to go anywhere. You cannot pass an incomplete object to any other function. All functions on a class are actually methods. This means that the first (hidden and implicit) parameter to all of these functions is this, the object doing the calling. In Pony parlance, this is the receiver.

So, if I need to call _dosomethingrandom() from inside my constructor, and _sum hasn’t been set yet (because I haven’t called dosomethingrandom!), I can’t pass this as a parameter to that function because it hasn’t yet been initialized!

The solution to this was to pass all of the necessary things I needed to these functions, like the random number generator, as parameters and not as class fields. Once I had been able to refactor out the field access from those functions, I could label them as fun tag, which means they don’t access fields and so the compiler doesn’t need to give them the this parameter. What this really means is that I can call these functions prior to this being considered initialized or complete because that call wouldn’t violate Pony’s rules.

With a set of functions that no longer needed access to my class fields, I was able to refactor that code out into a primitive with a function that I call to do my dice rolling:

_values.push( ( term, _DiceEvaluator(rand, term) ) )

Notice here how the random number generator is a parameter to the dice evaluator primitive and not a class field.

So what’s the point of this blog post?

The moral of this story is that through my own failure to get a seemingly simple piece of code working, I ended up having a fairly amazing leap in understanding about how Pony manages class instantiation that will serve me well in the future. After this exercise I feel like I’ve climbed another rung on the ladder, and the way I see the Pony language has been irreversibly changed.

Failure is good. Embrace failure. Fail often, learn often.

--

--

Kevin Hoffman
Kevin Hoffman

Written by Kevin Hoffman

In relentless pursuit of elegant simplicity. Tinkerer, writer of tech, fantasy, and sci-fi. Converting napkin drawings into code for @CapitalOne

No responses yet