There are 2 pain points in our parser code that we’ve written in our previous posts:
- No error communication mechanism. Caller won’t know why the parsing failed.
- Custom operators. This is not really a huge pain, but let’s see if we can reduce the count.
In this post, I’ll try to address both the problems by rewritting the parser code a bit.
Posts in this series:
Till now, we have defined four custom operators:
<<~. Couple of days back, I was trying to minimize our custom operators count and
<?> looked like possible candidates. Thanks @mikeash for suggesting this.
The only use for
<?> operators is to inject the values in to our curried initalizers. So, if we remove currying altogether, we can get rid of these two operators.
How you ask?
As the name suggests, Failable Initalizers (
init?), are a special class of init methods that can fail. A failable initializer is not guaranteed to return an object every time it is invoked. Incase an object couldn’t be created, it returns a
This approach looks fine at first glance, but it has one major flaw. The failures are getting consumed inside the
guard statements. There is no way for the caller to know why the parsing failed. This will be problematic if we want to debug.
Why are we even using
guards in the first place?
To handle failure right? I mean,
get does 2 things:
- Retrieve the value from the dictionary. This can fail if key is not present or it has
- Cast the value (of type
AnyObject) to the required type. This can also fail if the value cannot be casted successfully.
Right now, we are handling these failure by making
get return an optional, requiring us to use optional binding via
Is there a better way?
Swift has a
try-catch error handling mechanism that perfectly suits this situation.
Basic control flow using errors is:
- If a function can throw error, it should be marked as such by using
- Inside a function, errors can be thrown using
throw <OUR_ERROR>. The
<OUR_ERROR>is one of the cases for our custom enum that is of
- For calling a function that can
throwan error, the caller should prepend the function call with
tryand the whole statement should be inside a
doblock followed by a
- If the function throws error, control directly jumps from
doblock to the corresponding
So, to adopt this, our first step would be to create an enum of
ErrorType for representing possible failure conditions:
- An invalid type case
keyPath methods need to be modified to throw
ParseError instead of returning Optionals.
It might look like we’ve done a lot of things, but all we did is:
- Mark our methods with
throwsindicating that this method can throw an error
- Identify possible failures and throw corresponding errors using a combination of
typealias thingy in each function might look a bit, I don’t know, odd?. We need that because of the generics. Please let me know if you can suggest a better approach.
Let’s modify our
init method to use our new error handling mechanism.
We’ve done few things here:
initis no longer Failable. Instead, incase of failure, it just forwards the error to the caller.
- The Optional value is handled by turning the error into an optional by using
All looks good, we’ve successfully parsed a JSON into a struct with out using currying and our
<?> operators. Finally, we can delete those.
We are left with our
<<~~ transformation operators. Let’s modify these too.
That’s it, we’ve modified our code to use our new error handling mechanism.
The remaining custom operators,
<<~, are not necessary for our parser to work. All they do is help you to write:
instead of equivalent longer version:
If you are comfortable with longer version, even these operators can be deleted.
Wohoo..! zero custom operators.
You can find the whole source code as Gist on GitHub
I’ve made this into a library called Banana. Contributions are welcome…