Alexander Granin Profile picture
May 23, 2023 35 tweets 14 min read Read on X
Programming in C++ is like being a patient in a mental hospital. 🧠👩‍🦽

Functional Programming in C++ is when those patients organize a secret mental hospital within the mental hospital and start pretending they are doctors.

🏗️Advanced FP I did in C++ ⬇️ Thread 🧵 Image
You might have heard of the wonderful book "Functional Programming in C++" by @ivan_cukic (by @ManningBooks). I highly recommend it. This book will show you many nice FP tricks and tips.

It highlights most of what we call FP, but I went much further in exploring this area. Image
I've implemented a custom monadic Software Transactional Memory in C++ with my own approach based on Free monads.

The idea of interpretable Free monadic eDSLs helped me to implement this concurrency model in a really simple way compared to the mature STM libraries. Image
In the pictures, a classic concurrency problem known as "Dining Philosophers" is solved in both Haskell and C++ with custom STMs I made.

This is a transactional model of the task. There are philosophers, and there are forks to eat spaghetti. Forks are a shared resource here. ImageImageImage
My custom STM eDSL should have 3 methods to deal with transactional variables and the retry combinator as it's in the native Haskell's STM:

data TVar a
type STM a

newTVar :: a -> STM (TVar a)
readTVar :: TVar a -> STM a
writeTVar :: TVar a -> a -> STM ()
retry :: STM a
With these methods made monadic, my STM would be minimal although completely viable and highly convenient.

I made this easily in Haskell. A custom interpretable STMF language and STML Free monad over it, and also, a simple interpreter to make it actually work. And it worked. Image
In C++, there are no ADTs and no Free monads.

I needed all this, so I first implemented ADTs and then implemented the Free monad itself.

Here I should have done:

- interpretable ADT for eDSL
- recursive interpretable Free type (also an ADT)
- monadic facilities for eDSL + Free
Algebraic Data Types

C++ doesn't have ADTs as we know them in functional languages.

Structures may be considered a product type if used as immutable values.

std::tuple is also a product type although very inconvenient compared to tuples in normal languages. ImageImage
Sum types in C++ are sick.

Dynamic sum types aka OO hierarchy could work, but it's not the same as static sum types from the FP PoV.

We have to use std::variant, a very clumsy tool that requires a lot of ceremonies. ImageImage
Here, Human and Animal are different types in C++, and Species is also a separate type.

In Haskell, there are no Human and Animal types. They are value constructors (aka object constructors in C++) of the Species type.

In both examples, we can construct a list of those values.
The differences become significant when it comes to pattern matching.

In functional languages, pattern matching over ADTs is essential.

In C++ (old versions), there is no pattern matching. There is std::visit (Visitor design pattern) that does a similar thing. But it's crazy. ImageImage
This is where things are getting out of control. Implementing all those STM methods in C++ meant I needed 4 structures for my custom STMF ADT:

NewTVar
ReadTVar
WriteTVar
Retry

And, what a pity, I couldn't just have

struct NewTVar {}

for deep reasons.
All these types should be parametrized as it is for their Haskell counterparts. As you can see, Haskell STMF ADT is this:

data STMF next where
NewTVar :: a -> (TVar a -> next) -> STMF next

where 'next' is a type parameter of a user type not known upfront.
This method is also "hiddenly" parametrized by the type variable 'a'.

This makes my STMF type to be an existential ADT.

Existential ADTs are an advanced feature of Haskell. They are not commonly spread and are very sophisticated by nature.
In C++, there is no such thing at all.

This means I should do a lot of tricky non-trivial template metaprogramming alloyed with tricky non-trivial functional programming made through the tricky non-trivial C++ mechanisms.
In fact, I made this STM library for my talk at @cpp_russia , and when I first submitted the talk, I had nothing except the idea.

I had a strong belief that I can implement STM with Free monads despite the mature STMs are complicated on their own.

I was brave - and I did it. Image
Unfortunately, the code listings are extremely long. For example, these are the methods of my STMF algebra, and even so, I dropped a lot of constructors and destructors to make it shorter.

All these structures are expected to be used as stack-located immutable values. ImageImageImageImage
Notice the std::any type. This type helps to work with unknown user types uniformly.

This is actually another trick I needed in both Haskell and C++ STMs. It's the so-called Typed-Untyped design pattern that I invented. It has many other uses in my Free monadic methodology. Image
Now it's time to actually have the STMF ADT.

Don't be misled by its simple look. This is actually an existential ADT because it only exposes the Ret type variable and hides the Any type variable inside the structures. Image
The next step was to implement a generic Free type in C++.

This straightforward Free ADT has two constructors: Pure and Free. And it's generically recursive.

I failed: I couldn't make it statically generic. So I made my Free type highly entangled with the STMF language. ImageImage
But this is a basic Free type. Being a monad, it is slow: it has the O(N^2) complexity in monadic binding.

So I implemented another Free type for my STMs. This one is known as a Church-encoded Free monad, and it's faster.

So my STM library supports 2 engines now. ImageImage
Church-encoded Free type is much more tricky. It represents one big complicated continuation (function) that has the ability to join (bind) two separate actions of my STMF eDSL.

In a basic Free type, this binding works by keeping the usual values of STMF, not functions.
My monads however would not work without many important functions for doing the stuff behind the scene. All monads are so: there is some functionality a developer doesn't see, but this functionality defines the behavior of the monad.
First, the STMF eDSL should be a Functor. Not a functor in C++ meaning, but rather a Functor in FP meaning.

And this is where I have to have a visitor instead of pattern matching. This visitor visits every action of STMF and does some needed conversions of it with a function. Image
The Functor instance for STMF (the StmfFunctorVisitor) is rather long and complicated.

Today, 5 years later, I don't really remember how it works. I only remember that I was shocked when it started working.

You know, it's C++. Undefined Behavior all the way (cc @Nekrolm).
The same is true for the 'bind' function - the essence of every monad. For the Free monad, my monadic binding should traverse a monadic STML scenario (transaction) and join it with another STML monadic scenario (transaction) thus having the final monadic STM scenario.
The 'bind' function is also a visitor implemented differently for the regular Free monad and for its Church-encoded version.

Don't ask. I have no idea how it works.

I don't think I'm able to repeat this journey again. And I'm not sure what money could even motivate me to try. Image
But once these facilities are done, we actually have everything we need to start writing STM scenarios which are monadically combinable transactions.

The library provides tons of monadic functions that allow one to write various behavioral models. Image
C++ doesn't support the do-notation.

Do-notation in Haskell and for-comprehension in Scala make working with all monads so easy and convenient that I strictly believe this feature should be introduced for all programming languages. ImageImage
I personally tried to implement a generic do-notation with C++ macros, but couldn't make it work. I've seen some other attempts for C++ as well, all are very unsatisfactory.

For Rust, you can see this attempt by @politrons:

The final piece of the story of how my custom STM works should be about interpreters. As long as STMF, STML and Free are interpretable declarative eDSLs / ADTs, there should be some way to interpret/run them against a real environment and do the actual work. Image
Oh, this is another mind-blowing piece of code.

I invented a way to store transactional variables (TVars) of a custom user-defined type (hint: with the Typed-Untyped pattern and Typed Avatar design patterns).

Also, I made it transactional and thread-safe with certain tricks. Image
Today, after years of this endeavor, I believe this was a true enlightenment that allowed me to implement this library. My path wasn't free of local deadends of course, and sometimes I had a feeling that I'm in deep trouble. After all, I submitted a talk and was afraid to fail.
I especially remember when I first hit the need for existential types in C++. It was a cold shower to realize because I had no idea how to even approach the task.

Fortunately, the C++ language was insane enough for this to be possible, so I managed to overcome this obstacle.
I made STM in C++ for giving a talk.

But my meta-goal was to strengthen the methodology I call Functional Declarative Design.

Today I can say: functional C++ was a really interesting mental hospital to me. I appreciate it a lot but will never visit it again.

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Alexander Granin

Alexander Granin Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @graninas

Feb 2, 2023
Remember I was very active in 2022?

No? But I was. I've been posting about 1-2 tweets every day, and around a long technical thread each weak.

In December and January, I posted 10% of my usual tweet numbers.

And here is the proof I was suppressed till October.
Total impressions in 2022:

January - 175K
Februrary - 119K
March - 121K
April - 69.1K
May - 34K
June- 47K
July - 55.8K
August - 63.4K
September - 75.7K
October - 201K
November - 201K
December - 36.8K (almost no posts)
January 2023 - 106K (almost no posts)
There can be different reasons:
- cut off the whole Russian audience when Twitter was banned (very likely)
- shadowbanning for me as a Russian (?)
- shadowbanning for the only mentioning
@jordanbpeterson (less likely)
- mass-reports of mine for something (less likely)
Read 4 tweets
Jan 26, 2023
Nice writing. I wish Scala to survive.

In fact, I don't believe that any similar posts can help Haskell.

Scala is on the edge between life and death but is fighting.

But Haskell...

Haskell has crossed this line already. Forever. With no possibility to resurrect.
Haskell is dead.

God sees we, reasonable and pragmatic people, tried.

There were clear problems and we knew it.

We were alarming. We were signaling problems. We were suggesting solutions.

But we weren't heard. Never been heard. Neither by Haskell elites nor by the community.
And it turns out that my early prediction of this demise in form of the post 'What killed Haskell...' was very accurate.

Haskell is dead by the inability of the community to solve social problems.

No. Wrongly said.

By the unwillingness to solve those social problems.
Read 12 tweets
Aug 7, 2020
I'm learning ZIO (by @jdegoes), and I can say this framework has so much in common with my frameworks.

ZIO is based on Free monads.

All my frameworks (Node, Hydra, frameworks at @juspay) are based on Free monads.

1/
ZIO and Hydra programs are values which can be interpreted due to the Free monadic nature. Free monads is the best approach to separate interface and implementation. Free monadic programs are simple, maintainable and testable.

2/
In ZIO, there is a notion of Runtime. It’s a place for keeping resources, such as a thread pool.

Not by chance, my frameworks share the same design. There is the Runtime structure which hides a lot of implementation details about the internal stuff.

3/
Read 9 tweets
Feb 3, 2020
"Never use Free Monads.

They are slow.
They produce boilerplate.
They require functors. Functors can’t be composed.
They require interpreters. Interpreters can’t be composed.
So FMs can’t be composed."

These statements are all wrong or completely misleading.

1/
Unfortunately, the FUD about Free Monads was significantly boiled by several articles in which the reasoning was based on the mathematical nature of Free Monads rather on the applicability in a particular tasks. (I won’t provide links to avoid unnecessary conflicts.)

2/
In software design, nothing should be assessed from the theoretical point of view. It’s not a rare case when a theoretical statement on some topic turns out to be completely misleading because of the impurity and limitations of the real world.

3/
Read 20 tweets
Jun 23, 2019
I’m pretty sure there is a big demand for materials about high level architecture and design of big applications in pure FP (Scala, Haskell). This is what I'm actually doing for a long time.

Let me describe the materials I have to the present moment (ongoing thread).

1/
1) Functional Design and Architecture book

In this book, I develop a complete set of tools and approaches for building complex applications in a pure functional language (Haskell).

I’m working on the book currently. 5 chapters are available already:
graninas.com/functional-des…

2/
It tells how to make a finely structured, layered applications composed of subsystems and services, with database support, multithreading, safe concurrency. The code should satisfy requirements: testability, maintainability, simplicity, should be easy to understand.

3/
Read 29 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us!

:(