Fighting trees - Pet project series - 1/x
Wow, the first week is behind us. That was fast.
Let's quickly go over the first week:
Right after publishing the article I pull Playground and start blasting coding.
Identifier
One thing I love in Swift are small wrapper types.
For example Identifier
wrapper
struct Identifier<T, Raw: Hashable>: RawRepresentable, Equatable, Hashable {
typealias RawValue = Raw
let rawValue: Raw
init(rawValue: RawValue) {
self.rawValue = rawValue
}
}
class MagicPony {
typealias ID = Identifier<MagicPony, UUID>
let id = ID(rawValue: UUID())
}
By wrapping your string / uuid ids into type specific wrappers you let swift type system prevent mistakes like putting User.ID
into Admin.ID
instead of relying on developers.
Side note: I found out that Swift has built-in Identifiable
and ObjectIdentifier
.ObjectIdentifier
applies to classes only and does not persist between app runs. So it makes it unusable for me right now.
Licenses
For a longer period of time I was almost always ignoring licenses of libraries and software that I use / write. This time before uploading repo I decided to get a better picture of the licensing world. And hell it's complicated! Kudos to Em Lazer-Walker for her article: Picking an OSS license for your iOS app After hours of reading the BSD vs GPL holy wars, that was a breath of fresh air.
Right now I do not expect someone else to contribute to the project and honestly, even use it at all. So I decided to go with GPLv3 as it's not AppStore friendly (not like it will prevent someone from releasing it under their name, but I would be in the right, sadge). In future I may change the license of the project or release part of it under a different license like MIT or similar. If you are interested in using something from the project, do not hesitate to reach out.
Okey, okey, back to interesting stuff - programming.
SPM
For this project I want to stick with Swift Package Manager as much as possible. That's where the first disappointment awaited me. Although SPM allows you to create executables, it still cannot produce iOS apps. After a little research I stumble on this thread: Use SPM to build iOS target - Using Swift - Swift Forums
tl;dr;
Swift ≠ iOS Development
Although it would be cool to write apps using only SPM, building an iOS app requires a lot of additional steps, like copying resources / signing / etc, that are not related to Swift in any way. And it makes total sense.
Overengineering is our bread and butter
Remember when I said "no overengineering"? Keep it simple?
Easier said than done!
I just found myself writing TopologicalOrderSequence
after understanding that TopologicalIterator
is not enough for me.
Do I really need it? No.
Should I do it right now? No.
Is this the best solution for my problem? Most likely no.
But here I am.
It sounds interesting. It sounds complicated. It sounds "engineery" and "cool".
And I think it's a problem with developers in general.
No one gonna brag to friends / colleagues about one more function they wrote, but wrap it in fancy words and uncommon technologies and now we’re talking!
Complexity of init
While writing Engine
I start preprocessing data and recipes in init
for simpler future calculations. Now looking at it, I would say it's a bad idea.
A lot of developers expect init
to be an O(1)
operation, which is not always the case. How many times have I heard: "I'm just creating one object here, it's fine", to later find out that the program spends seconds (!!) in this init
.
It may sound like pre-emptive optimizations, and honestly in 99% it is, but I consider it good practise and part of consistency.
Recursion vs loops
During implementation of add(ingredient:...)
I notice that I unintentionally prefer non-recursive algorithms over recursive ones. A possible reason is without having limits on the amount of ingredients, we cannot guarantee that recursion will fit in the available stack. Again, it's a case of "better safe than sorry".
Most compilers are smart enough to apply tail-recursion optimizations, so in most cases it's not something you should worry about. But not in my case.
First "real" issue
Recipes planning went very well until I stumbled on recipes that produce not 1 thing but multiple (remember 4 nails from 1 iron ore?).
Right now if we have a recipe: 2X -> 1Y, when the user adds 1Y in inventory, the app will add virtual 2X as well, because it's X that already "used" for Y.
If the recipe is reverted: 1X -> 2Y, when user adds 1Y in inventory, it's not enough for full recipe so virtual X's are not added.
So far so good.
But what if the user adds 1Y again? If we look at the transaction outside of context, it's exactly as before, no X to add. With context we have 1Y from before, that gives us 2Y in total and 1 virtual X to give.
To solve this calculation issue we could go 2 ways:
- Split user inventory between "real" and "virtual".
Whenever the user adds an item to inventory, we update "real" inventory, flush "virtual" inventory and recalculate it from "real".
I don't really like this approach, because it feels "too heavy" to recalculate from inventory on each update instead of doing smart half tree recalculations.
- Instead of recalculation inventory, recalculate requirements (
needed - whats_in_inventory
).
This could lead us to a situation when the user has more subproducts that are needed and can be really confusing.
Because of how hard it is to properly communicate to the user what's going on in the 2nd case (even I myself do not fully understand it yet), I decided to go with the 1st approach.
What to expect next?
Currently I don't like how ingredient.id
is used everywhere and probably would do something with that next week.
If you missed the first post of this series, I got you: Starting Pet project series 0/x | ManWithBear
See ya!