Elixir Payroll
Starting with a clean slate, I’m going to try to sketch some payroll in Elixir. As the title and blurb suggest. I should pay more attention. My rough plan is to sketch things in iex, the Elixir command line, and then ideas to one or more exs files as they seem to gel.
I’ve only got a few hours of Elixir, so I’m expecting lots of mistakes. I’ll show you the interesting ones. I’ll spare you some: you’ll be able to tell by skipped line numbers in the iex listings when that happens.
My basic thinking is that I’ll treat payroll as a transformation from a collection of time cards into a collection of pay stubs. This is different from how I usually approach it, where I more commonly work from the inside, calculating some decent pay values, then a pay stub, then finally deal with multiple employees.
The rough plan is that the time cards and the employees will be in collections, and that I’ll use the employee id in the time card to find the employee record, which will have the pay rate (and perhaps other info as time goes on). At a wild guess, the time card collection will be a list, and the employee collection will be some kind of dictionary map kind of thing, so I can fetch the right employee given their id.
Even more rough is my idea on the overall flow, which will be something like
Payroll.time_cards -> stubs -> base_pay -> gross_pay -> fed_tax
And so on. We’ll create empty stubs and then fill them in serially, in the order payroll calculations should be made.
I have only the most vague idea on the details of doing this.
Make some data
Since I ended yesterday’s experiment with a module defining an employee struct, let’s run with that idea and create both a time card and an employee, like this:
defmodule Employee do
defstruct id: 0, name: "", rate: 0
end
defmodule Timecard do
defstruct id: 0, regular_hours: 0, ot1_hours: 0, ot2_hours: 0
end
That compiles into iex and seems to be what I wanted. Now I’ll make some time cards. I’ve not figured out if there’s a way to make a module export variable names into iex, so I’ll define a function to create the list and call it from inside iex.
defmodule Pay do
def timecards do
[
%Timecard{id: 1, regular_hours: 40, ot1_hours: 0, ot2_hours: 0},
%Timecard{id: 2, regular_hours: 40, ot1_hours: 10, ot2_hours: 5}
]
end
end
I note that I’m not at all sure this is the best way to do it. It is a way to do it, as shown in iex:
iex(2)> c "payroll.exs"
payroll.exs:1: warning: redefining module Employee
payroll.exs:5: warning: redefining module Timecard
[Pay, Timecard, Employee]
iex(3)> Pay.timecards
[%Timecard{id: 1, ot1_hours: 0, ot2_hours: 0, regular_hours: 40},
%Timecard{id: 2, ot1_hours: 10, ot2_hours: 5, regular_hours: 40}]
Sure enough, the payroll file compiles, with some friendly warnings about redefinitions, and when I say "Pay.timecards
I get the timecards I expect.
Now to work on transforming those into pay stubs. First, for typing convenience, I’ll save the time cards and then I guess I’ll just map them into something, maybe a list of integers or something to begin with.
It turns out that there are some modules for processing collections, Enum and Stream. They contain all kinds of useful functions, including one called “map” that creates a collection that is a function of the original. We’ll try that:
iex(5)> Enum.map(cards, fn(card) -> card.id end)
[1, 2]
Sure enough, we map our list of cards into a list of integers, the card is. Let me explain the syntax to the extent that I understand it. Enum.map takes two parameters, the input list, and an anonymous function to be applied sequentially to each element of the list. That’s the fn(card) -> card.id end
bit. For each card, we return its id. (I used the most convenient of the four ways I know to get the id out of the struct, as described in the previous article.)
I imagine I can return a list that looks a bit like a pay stub pretty easily, by making the function a bit more elaborate:
iex(6)> Enum.map(cards, fn(card) -> [id: card.id, base_pay: 0 ] end)
[[id: 1, base_pay: 0], [id: 2, base_pay: 0]]
Nice. Now we have a little list of items, with a fake base pay in it. Now my first inclination is to make a Paystub struct and start filling it in. But I think that’s premature. Instead, I’m going to work with a plain list for a while, and see what happens. I’ll be building a paystub kind of thing but I’m not going to nail down its exact contents.
I think the biggest problem just now is that my pay functions will need access to the employee. Let me mumble a bit about the future here. This is absolutely speculation, based on limited understanding of how Elixir works, and limited understanding of what I need to do. So expect this to be wrong. Feel free to say “That’s not gonna work” if you wish.
When you define a function in Elixir, it inherits the context where you defined it. So, for example, if I have an employee collection, like the time card collection, and I store it in a variable in iex, my function inside the Enum.map will be able to see it. I’m going to start there. That means I should be able to find the right employee, somehow, and use his base rate in the calculation.
Looking forward, I figure I can either pass the appropriate employee to an inner function to compute things, or, better yet, bind it inside the map and just reference it inside all the pay functions. We’ll see how that goes: I surely don’t know exactly how to do it.
Learning, remember? This is how I do it. Probably you have a better way, and if so, I hope you write it up. Be sure to start with a language you don’t understand at all.
I’ll put the employee records into the payroll file. I know how to make a list but I think I’ll try to learn enough to make a dictionary for direct access.
[pause] Well, no, not there yet. I’ll need to read some documentation before I know a good way to do this. However, here is one way to do this:
iex(7)> joe = [name: "joe", rate: 10]
[name: "joe", rate: 10]
iex(8)> sue = [name: "sue", rate: 12]
[name: "sue", rate: 12]
iex(11)> emps = %{1 => joe, 2 => sue}
%{1 => [name: "joe", rate: 10], 2 => [name: "sue", rate: 12]}
iex(12)> emps[1]
[name: "joe", rate: 10]
Lines 7 and 8 define two employees, joe and sue. Joe earns a paltry $10 and hour, while sue earns $13. Still slave wages but it keeps the calculations small enough to check.
Line 11 defines a map, mapping ids 1 and 2 to joe and sue respectively. This defines my database, so I can, I presume, use it in a more advanced version of my payroll calculation Enum.map. Here goes:
[long pause] I just spent quite a while reading strange messages from Elixir. The basic issue turns out to be that when I defined those employees, I made them lists rather than maps, so when I tried to get employee rate with ee.rate, I got messages that meant nothing to me. I think I’ll go back and redo the employee set and start from there.
In doing this I’m also learning that I’d do well to put snippets of code into files and reload them, so I’ll try that while I’m at it. It turns out that if you compile a file, with c, local variables aren’t included, but you can say import_file
and they will be. So
joe = %{name: "joe", rate: 10}
sue = %{name: "sue", rate: 12}
emps = %{1 => joe, 2 => sue}
Gives me a map version of the employees table. Now I can try my basic pay function again. It goes like this:
pay = fn(card) ->
ee = emps[card.id]
[id: card.id, name: ee.name, base_pay: ee.rate*card.regular_hours]
end
This just defines ee (the international standard abbreviation for employee) as the employee with the same id as the input card, and then produces a list element with the same id, the employee name, and base pay calculated without regard to overtime (yet) and the answer is:
iex(38)> Enum.map(cards, pay)
[[id: 1, name: "joe", base_pay: 400], [id: 2, name: "sue", base_pay: 480]]
Yay! Joe gets paid 400, or $10 times his 40 hours, and sue gets paid 480, her $12 time her 40 hours of regular time. We’re not yet paying overtime, so that’s the right answer.
Summing up
This is a good spot to stop, not least because it’s coming up on lunch time, and it’s sunny out, so I can buzz somewhere with the top down. But also because we’ve created the simplest complete payroll I can imagine:
- We have employees with name and pay rate;
- We have time cards with hours worked;
- We have a function creating a list that is much like a pay stub;
- We can map all the time cards into a corresponding pay stub.
That, ladies and gentlemen, is a payroll. Just run it, and manually cut checks for joe and sue, using the numbers printed. Nothing to it!
That took longer than I’d have liked: almost two hours of continuous work except for when I was writing this article, reading documentation or typing things into iex and wondering what it was telling me. But I learned a number of useful things:
- Don’t hesitate to type useful functions into files and import them: it’ll save editing.
- Try to be a bit more clear on differences between Elixir’s lists and maps and structs. They are not a triumph of uniformity in the language but they do have useful properties.
- Functions, as I thought, can bind a variable and then use it later. But we’ not yet tried calling an inner function to see that it knows the same variable in context. Maybe next time: we’ll see what the code wants to be.
This is enough for now. I hope you had more fun than I did: it was a bit confusing there for a few minutes.