Hello everyone. I’m a C# developer. I’m used to thinking in OOP when I write code, but I want to change my mindset to functional programming. I’m currently studying Haskell — the syntax is so different from C#, but I’m having fun writing basic programs. The problem is when I try to shift my focus to functional programming; it’s a little complicated for me. If anyone has any suggestions or advice on how to practice this new focus, I would be forever grateful for your priceless advice.
As a newcomer myself, I’d say that what helped me a lot was acquiring the habit of writing Haskell code. I don’t work professionally with it, so I solves multiple competitive programming challenges to get a grip on how to write functional algorithms, avoiding the do notation as much as possible.
The next step was to complete actual projects using Haskell. As a web developer, I went to develop web applications, and I’ve found it to be a blast.
Once in a while I also get some feature of the language or library and try to make a little proof of concept. Right now I’m studying lenses.
That’s my experience so far, a very rewarding one.
Cool! I think a really useful type of project to try is loading up some data from a file, parsing it (with parser combinators) and processing it in some way. For example, loading strings like “3 + 4 -5”, parsing them into an arithmetic language, and then evaluating them gives you a chance to interact with a lot of the core ideas in practical Haskell programming: parsing, recursion (via folds), streaming, IO, etc. And if you ask people for help with code here, you’ll get feedback.
I had real difficulty understanding how to actually do things in a functional paradigm, so I sympathize ![]()
As a hobbyist, I came to Haskell via C# but also a lot Excel (at work), and I found that making analogies with Excel helped (and that was before Excel became more like Haskell - A User-Centred Approach to Functions in Excel - Microsoft Research): what is in the spreadsheet’s cells is ‘pure’ but sometimes (often) you need to change the state of the world (VBA for Excel). Of course, Excel is Flatland to Haskell’s multiverse.
Like @reuben, I wanted examples of programs - but they seemed thin on ground compared to oodles of libraries. One of the reasons I first became interested in the Stack project - and stared at its code for a long time, trying to understand how and why it did what it did, is that is provided an example of code that actually did something.
An important realisation (at least, I hope it is often true) is that I could ‘trust the compiler’. I could separate, in code, the pure parts and the parts that changed the world and, somehow, magically, the compiler created something efficient out of all that.
Another important realisation was that there is a lot of useful things in the base package. If you are building larger things out of smaller things, often the smaller things are already in base, somewhere. Learning Haskell was, in no small part, learning what was in base.
I have 3y of experience with Haskell in a professional setting. So not a lot, but I don’t consider myself a complete beginner anymore.
Based on the way I work, I have two pieces of advice. The first is a “little manual” for small problems: that is, how I often approach little issues I face many times in a programming session.
The second is for larger problems: just a few tips on how to structure medium or large components in your application.
When you are working through small problems:
-
Think carefully about the data you are dealing with. What ADTs represent it in the most concise and faithful manner? Do you need more than one datatype, what should they look like?Write
dataornewtypedefinitions. Don’t think about encapsulation, coupling or responsibilities at this stage. Do think which states are valid and which are not: aim to make invalid states unrepresentable. However, keep in mind this may be impossible without using advanced type system features, so don’t sweat: put a pin on these problematic types and move on. -
Write types for the functions that manipulate your data. You should be able to give a type to these functions. If these functions can fail, you should wrap results in
Maybes orEithers. This will in turn make you think about how you will handle errors or exceptions. Don’t tackle this problem yet, this is a higher level problem. Use placeholders or typed holes (_) for things you will implement later, but try to have a complete skeleton for the entire solution earlier rather than later. This allows you to validate more quickly if your solution even makes sense. -
Try to implement the functions. You will find you missed some detail in the data definitions or that you need other functions too. Iterate on these 3 steps until you’ve got a solution. Congratulations! Maybe you should write a few tests now.
I think you can go very far just following these instructions. That is the main advantage of pure functional programming: I find OOP to be a nuisance when you are tackling small things such as these.
Now, on to the advice for larger parts:
-
Think about how you will handle exceptions and errors. AKA “what do I do with all these
Maybes andEithers?” Exceptions and errors are easier to think about at the level of your application or library’s architecture. It’s not something you want to think about in a vaccuum.
Some errors are unexpected, with these you often want to report as much info as you can and bail out. Others are expected, and your own functions should be able to handle them gracefully in a way transparent to your user. Consult what mechanisms your framework has (if you are using one) or choose one of the myriad ways of modeling effects in Haskell. At this point you may need to re-write some of the previous functions to use monads with failing capabilities. -
Organize your datatypes and functions into modules. This is where you can see that OOP and FP share many principles: you want to keep related datatypes and functions together (like classes) to make the APIs easier to understand. You may also think of modules as having “responsibilities”, like classes do.
Here is where you want remove the pins you placed on step 1: if some datatypes may be instantiated in a way that is invalid, make them abstract. Place them in a module and don’t export the constructors: write a smart constructor for them that may fail (this is equivalent to the factory pattern in OOP). You may also try to reduce coupling at this stage by using records with functions inside as interfaces (or typeclasses). -
Architect your application as you would any other. Seriously. You can adapt so many things from the OOP and general programming world. You can do MVC. You can do CQRS. In a backend application we had a
Domainnamespace for types that belonged to the business domain, following the terminology of Domain Driven Design. I just use whatever make sense and stop listening whenever objects and classes are mentioned.
I hope you find some of this advice helpful and I wish you good luck on your functional programming journey!
Thank you, my friend, for the advice. Maybe my problem is that I’m trying to solve complex problems instead of starting with basic ones, hahaha. It’s a good idea to practice by solving problems with parsing, recursion (via folds), streaming, and IO.