Wednesday, March 30, 2016

Money Conversion - More Swifty

My reaction to this post Swift: Money with Phantom Types is that functions like convertGBPtoEUR() are not very Swifty.

While Phantom Types are an interesting concept, I feel that some well-placed extensions can make for some better results.  Here are the two motivating examples of using currency conversion:

To get to the point, here is what I end up with for usage:

I find this syntax to be much clearer, and we can get the money in the currency we want more directly, without having to write a unique function call for each currency we add to the system. So, let's look at how we get here.
The key function driving this is:
func convertTo(Money.Currency) -> Money?

Here, Money is a struct containing both Currency and Amount as in the original post. And I add the convertTo() function as an extension. This one function uses a switch on the tuple of the previous and next currency enum values to define all the possible conversions we need to support. This is a great advantage to us as a maintainer of this currency conversion code, for a few different reasons:
  • We have one function point to do the currency conversion work we want between currencies here.
  • When I add a new currency, I will get a compiler error on this switch statement so I'm forced to add the exact code that I should add when a new currency enters this system
With the function-per-currency-conversion paradigm and Phantom Types that have no correlation to each other, I can add a new currency and get zero compiler errors. That may seem nice, but it makes it very simple for someone new to this code (including my future self who has forgotten) to add a currency and not realize that I should write more conversions for that new currency.
This function can be even more powerful if I make it return Money instead of the optional Money? and thus require myself to provide conversions between each of the currencies that I support.

Ok, so hopefully I’ve made a good case for the centralized convertTo() function. Now that we have that in place, we can add the syntactic sugar to convert values with dynamic vars. Here’s the code for that:

Here, I add three extensions. First, convenience constructors from Int and Double to allow me to make a Money value from a simple number like: 5.USD or 4.72.GBP
Second, conversion from one Money currency to another, yielding syntax like 5.USD.GBP to get a Money value for 5 US dollars in Pounds. Again, by making convertTo() return Money instead of the optional, then these conversion extension vars to Money will return an optional.

UPDATE: Looking at some of the responses to Natasha's original post, I came across this code by Dave Delong https://gist.github.com/oisdk/569de8286f9f706749b7d0668836706a#gistcomment-1737857 which has some similarities to my own solution proposed here. I like that he is using a currency factor on each to ensure that each currency can be converted between without any special logic, though I'm far outside my domain of expertise with currency conversion. I have some strange feeling that it may not always be simple enough to peg everything against the US Dollar, that exchange rates might actually vary uniquely. Of course, depending upon how sophisticated the need for currency conversion in practice would drive how sophisticated the code needs to be.

No comments:

Post a Comment