Should I Refactor My Application to a Functional Paradigm?

Hello All,

I'm attempting to create my own Productivity application to eventually replace Todoist in my workflow (keyword eventually).

ClearHeadTodo (please tell me if you are unable to access the repository).

I'm proud of the current implementation. My goals were to make something that is:

  1. clean, I want others to be able to easily see the implementation and build on it should they wish
  2. Stable/Tested: I have gone to great lengths to ensure every edge case is tested and that the entire program is bug-free (and from my view i've done that so far)
  3. Fast: The reason i'm using rust for this project is that I want it to scale going into the future. I want a power user to be able to create/edit 1000's of tasks/stories without a hiccup going into the future

Currently, the application is built in an object-oriented fashion, utilizing a "task_list" struct which contains "task" structs.

I'm using clap to give this a command-line front-end for now, but I intend for this basic layer to be able to take many forms in the future (web app, mobile app, etc...)

As i begin to scale the project, i'm sitting at what I see as the final point-of-no-return between implementing the application in an object-oriented way, or converting to a more functional paradigm where I simply pass around a vector filled with hash arrays that represent tasks.

One could easily imagine a refactor where we simply create a NEW vector with slight variations whenever I edit, create, or delete a given task.

Have I been listening to too much Rich Hickey? Should I continue using structs as I have? or would a transition to functional programming (aka, almost no structs and pure functions) increase interoperability, reduce complexity, and make a more testable product?

OR should I continue down the path I've created for myself and start the process of creating story structs which own a vector of task structs?

I know this is a super open-ended question, but I wanted to get the community's perspective since I'm in this project for the long-haul, and want to ensure i'm building the best product I can for the long-run.

Thank you in advance to anyone who takes the time to look at the repo and give their perspective.

If there is any problems with repo access please feel free to DM me and I will ensure you have access quickly.

Yes.

... I did leave a pregnant pause for effect :)) I’m glad you read it as such.

... you might enjoy reading: Algebra Driven Design. It takes you through a couple of concrete projects that apply what is also known as denotational design. It gave me a better appreciation for how to better think uniformly about types with just the right amount of traits... outside of thinking about state.

What I miss in Rust is a routine for thinking through state management. Most languages are horrible with this. So, Rich has it right. Perhaps surprisingly, outside of Haskell I put my experience with React/Redux as a good example of how to more formally delineate functions with no side effects from state. XState also deserves mention.

Finally, as someone who has been working on a start-up/project for over 4 years now, it’s rare to have the opportunity to choose; so build it in a way that makes you think the way you want to*... the way that inspires you. The source of that energy might be the enduring dominant choice. From what I saw of the code base it looks clean and straightforward. It also looks like now that you have a better understanding having actually built it, you could rebuild it to scale any way you like.

*

Since you asked about my preference, I’m big into FP for many reasons. State management is one. But for whatever reason I loathed for i j loops and loops in general. They seemed clumsy and just got in the way of how I thought about things. So you can imagine how I feel about map/reduce etc... just make sense. Values as functions (e.g., closures) with control over evaluation (lazy) is hyper powerful if you want to minimize the state footprint. Finally, the abstractions in FP are inherently more robust because they represent something more fundamental, more the essence of what computation is about in a more unifying way (for how I think about it). Despite being open minded for how we all approach the world, Monoids matter and should be understood by every software engineer; why not, no harm no foul. I could go on :))

1 Like

lol, such detail, are you just a functional programming believer or did you look at the repo?

General philosophical answer (I didn't look at the repo):

Remember the magic of Rust is that you can do both. If you're not sharing things, then Rust's rules remove most of the problems that happen with mutability. If you are sharing things, then I'd definitely encourage you to do things more functional-style, because doing the Arc<Mutex<_>> dance everywhere is a pain.

But don't just use one of them. Use each in the place where it's best.

1 Like

I'm not sure whether you are asking for the full story, or only about immutability and purity. It looks a bit like the latter – functional programming has so much more to it than using persistent data structures.

I'm not sure that it is worth the effort to refactor an entire application so that it uses immutable data structures. Traditionally, pure functional programming and immutability was touted because it avoided the problems with shared mutability, by outright banning any sort of mutability. That doesn't really apply in Rust, though: the language already bans dangerous shared mutability, while still letting you use unique mutability, so that you can express code in that style when it makes more sense in terms of programmer productivity or performance.

Bottom line: Rust is not a pure functional language, nor is it a pure object-oriented one, and you don't have to feel guilty if you don't follow either paradigm to the letter.

2 Likes

as a task manager, the tasks will be altered and changed quite a bit, this is why object-oriented made sense initially, because I was having trouble imagining a way to easily alter a list of tasks without doing quite a bit of work up-front. However, I am concerned about increasing complexity as I add more layers (such as project abstractions, along with the concepts of labels).

On the other side, I would be concerned about the performance overhead of destroying and re-creating a new vector every time I alter a single task, especially as the size of a task list might scale to 1000's of tasks

I responded in the original post. Having scanned some of the responses, I thought how it might be useful to separate how you ultimately update and record the action items. To the user of the state keep it pure: (state, action) -> state... immutability is maintained by providing what can be treated as a new copy. Worry about how/where to update the collection separately?

Something like the im crate can help with this: After a clone, you have two vectors that act independently from each other but share any unmodified items.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.