Is Haskell / Plutus hard to learn?

Photo by ThisIsEngineering on Pexels.com

Click here for the YouTube version of this blog post.

Today will be talking about a question I have been asked a lot. Is Haskell, the programming language behind Cardano smart contracts, hard to learn?

Now before I dive into it, I think it’s important that I let you know what my general level of experience is with computer programming. On the one hand, I’ve been coding on and off for 20 years, and have dabbled in a whole host of languages with Visual Basic, C# and Python being my main ones. But on the other hand, my heavy duty coding experience as a professional C# programmer only lasted a few years as I got into data science and so my more recent experience has just been in Python. And I’ve never really gotten into Java – too many curly brackets and boiler plate code for my liking. However I think I’m a good representative of your average programmer.

With that said, let’s dive in.

Part I – Is Haskell/Plutus hard to learn?

Haskell is a mainstream programming language that has been around since 1990, and Plutus is a version of it that has been built for smart contract programming on Cardano. I’ll mostly refer to it all as Haskell for the purposes of this discussion. One key point to understand about Haskell is that it falls under the family of programming languages known as functional programming, where as most programmers learn to code in imperative (or procedural, which is another name for it) languages such Java of Python. More on the merits of one over the other later.

Now, on the Cardano Reddit forums an often asked question is, “Is Haskell hard to learn?” usually with the subtext of “Will it mean less people develop on Cardano and therefore hamper its growth?”. As we all know, you can’t have a successful video games console without games to play on it, and you can’t have a successful smart contract blockchain without dApps to use on it.

There’s a lot of back and forth over this, some people say Haskell is harder to learn, others its just a different way of thinking. In fact I found this old Reddit post to be a very entertaining back and forth on the subject.

But let me just give it to you straight. Yes Haskell is harder to learn, though I’m not yet convinced it’s hard to learn. It’s harder to learn if you are new to programming and its harder to learn if you’ve been doing years of imperative programming. One online user explained it as:

“Imperative programming just maps naturally to how we think — we perform tasks as series of steps. If functional programming was natural for most people, a cooking recipe for a pie would look something like this:

A pie is 45 minutes of 400F heat applied to 100mg of final pie dough.

100mg of final pie dough is a mix of 99mg of pie dough stage 3 and 1mg of salt.

99 mg of pie dough stage 3 is a mix of 79mg of wheat flour and one egg.”

Krzysztof Kozielczyk

So the reason why Haskell is hard to learn is that it’s a different programming paradigm to what most people are used to or naturally think in.

Where as imperative languages are much more free flowing and let you do whatever you like for most part, functional programming are rooted in mathematics and hence follow a strong mathematical formula.

Who remembers the good old f(x) from calculus and the more advanced g(f(x))? Remember the first time you tried to get your head around what it represents only to realize that f(x) is just “some function” applied to x, and that g(f(x)) would mean yet another function on top of that? You may have gotten frustrated and asked “Why don’t they just say, take x, do this, then do this”. Well that’s the feeling you might have when learning a functional programming language such as Haskell for the first time.

That being said for me personally thinking about things through a functional paradigm was less of a challenge than simply getting to grips with the syntax. Haskell has its own notation system that is different to most other languages I’ve worked with. Where as other languages pay homage to C, granddaddy of a lot of modern day languages, Haskell pays homage to System F – aka Polymorphic Lambda calculus. Er…yeah.

However once I got over that hump, I’ve been finding myself enjoying the learning. You go from looking at a page of Haskell thinking what is going on, to being able to get a sense of what new code does without having to turn to documentation.

I can for example look at this function defintion:

func :: [a] -> a

and think ok it looks like it takes a list of anything and outputs one of the elements of that list. Where as the first time I looked at it I was thinking what the hell is the double colon, What the hell is the minus and right triangle bracket? How does it differ to =>? What is a? Does it have to be a? Why not b? And so on.

In the end I spent around 3-5 days just understanding the basics of Haskell, and that helped hugely. You can find a link to my notes on the front page of my blog.

After a few weeks of being on the Plutus Pioneers course and doing my own learning on the side, Haskell is feeling a lot more like playing with lego. Functional programming has a concept of “no side effects” So if you see a red, rectangular lego brick, you can be sure its always going to be a red rectangular lego brick. It won’t change its form after you’ve built something and if it breaks it’s not going to impact the color and shape of all the other bricks around it. So you start to appreciate that simplicity after a while.

But it’s still early days and Plutus could benefit from better documentation, as you’ll see from the online exchange I’ve included at the bottom of my blog.

All that being said, what I think we should really be asking is this: Is functional programming a good fit for smart contracts?


Part II Is functional programming ideal for use in smart contracts?

Functional programming is said to reduce the number of bugs in code due to the fact that they instill a deterministic approach. There are no “side effects” as I mentioned, so if you give a function the same input, you will always get the same output.

I decided to look to see if there was any research that proved out that functional languages have less bugs than imperative / procedural ones. I came across one bit of research that did indicate it they do, albeit somewhat inconclusively. It read:

“There is a small but significant relationship between language class and defects. Functional languages are associated with fewer defects than either procedural or scripting languages.

“… However, we caution the reader that even these modest effects might quite possibly be due to other, intangible process factors, for example, the preference of certain personality types for functional, static languages that disallow type confusion.”

Ray, Posnett, et al. (2017)

However that research was performed across all Github projects, not just specifically for blockchain dapps, so it would be interesting to see that research repeated for dapps.

With blockchain programming, code is immutable. Which means once it goes live, it is hard to change; you cannot apply a patch to fix things. Updates are possible but depends on how the governance model has been set up. For it to be considered decentralized, the token holders would have to approve of the changes.

This is an important distinction of blockchain programming. If you have a bug in your code, and you deploy it, you can’t change the original code. It’s fixed on the blockchain. Therefore the imperative is to check, double check, and triple check your code before you deploy it rather than discovering bugs later.

The Ethereum blockchain has a long running history of bugs, though let me stress, bugs can happen on any blockchain, Ethereum just happens to be the most popular dapp platform right now.

Let’s briefly take a look at two of the most famous ones:

The Ethereum DAO hack

Ethereum’s decentralized autonomous organization (DAO) was intended to act as an investor-directed venture capital firm. People would buy DAO tokens that would fund the investment vehicle and in return they would get voting rights on how those funds were deployed. The DAO raised $150 million USD worth of ether (ETH) at the time, but less than three months after its launch, the DAO was hacked and $60 million of it was stolen. The attack exploited a combination of vulnerabilities in the code. There is an entertaining story of group of white-hat hackers that tried to save the funds. It’s worth a read.

We felt like the worst hackers in history…We were foiled by bad internet and family commitments.”

The Ether Thief

Now it’s important to stress this was a bug in the application not the underlying Ethereum platform itself. Just like a software application running on Windows might have a bug in its code, which is nothing to do with Windows. However due to immutability of blockchain code, the hack led to the entire Ethereum blockchain, being forked. Imagine upgrading everyone to a new version of Windows every time there was a bug in a software application! The philosophical differences of whether forking to get around these types of issues also lead to a deep split within the Ethereum community and is why we now have an Ethereum and an Ethereum Classic blockchain.

Ethereum parity bug

Not again: Hackers steal $32 million worth of Ethereum. That’s the second major ether theft this week.

Machable.com

The quote above is from July 2017 and relates to a bug in the Ethereum wallet known as Parity. And it’s not even the main bug I’m want to discuss. A good write up of this first exploit can be found here and recommended reading for anyone looking to have a deep understanding of blockchain security. But it was the fix for this exploit, that lead to the second Parity bug, 4 months later.

In November 2017, a user of Ethereum’s Parity wallet stumbled upon a way that allowed him to take control of multisig wallets (wallets that require multiple people to authorize the moving of funds) and was able to make himself the owner. Stunned by what happened, he then inadvertently killed the contract, immediately rendering all associated wallets unusable. The user later reported the bug as a ticket on Parity’s GitHub, simply stating “I accidentally killed it.”.  However, without any way to access the Ether inside, millions of dollars were already locked inside hundreds of frozen wallets…On November 8, the company behind Parity released the results of a comprehensive audit revealing that the 578 wallets holding a total of 513k Eth were now frozen.

Wow. 513k Eth, which was worth $280m USD at the time and $1.2bn USD at the time of this writing this. Interestingly a large portion of this amount belonged to the lead developer Parity, a certain Gavin Woods. Yes the same Gavin Woods that went on to found the rather complex Polkadot blockchain).

Parity addressed the situation by saying:

“Following the fix for the original multi-sig issue that had been exploited on 19th of July (function visibility), a new version of the Parity Wallet library contract was deployed on 20th of July. However that code still contained another issue – it was possible to turn the Parity Wallet library contract into a regular wallet and become an owner of it by calling the initWallet function” (Parity)

Just how prevalent are software bugs in Ethereum smart contracts? Well this research carried out a year later in 2018 found that:

“On a subset of 3,759 contracts which we sampled for concrete validation and manual analysis, we reproduce real exploits at a true positive rate
of 89%
, yielding exploits for 3,686 contracts. Our tool finds exploits for the infamous Parity bug that indirectly locked 200 million dollars worth in Ether”

Let me repeat that in plain English. The researchers found that 89% of the the smart contracts that they sampled, had bugs in them that could be exploited. No wonder there is a lengthy list of Ethereum smart contract exploits.

For blockchain technology to flourish, it needs to mature into a stable and safe industry.

Bugs are a much bigger issue than anyone admits. Listen in to the Ethereum developer call on 23rd of July at the 35:40 mark, to hear them struggle to answer a simple question of “what do we do if we find there’s a bug after we deploy EIP 1559?”

Functional programming appears to be somewhat better for reducing bugs, and even imperative languages such as Java, have started being more functional like, in recognition of this.

And within the world of blockchain, where such bugs can result in millions of dollars being lost, I feel functional programming is even more important. The amount of bugs on Ethereum contracts is simply unacceptable.

Cardano apps will be easier for auditors to audit, and easier to do formal verification on. This is a process by which you mathematically prove out

“Put simply, formal verification uses math to specify and analyze a program for errors in logic. However, because of the time and cost involved, formal verification is best reserved for situations where human life or large sums of money are at stake.

Currently, formal verification is used to verify the correctness of high-risk code in transportation, the military and cryptography. Chip companies use it to fortify algorithms before embedding them in silicon. And banks use it to develop financial algorithms.” (Bitcoin Magazine)

Ask yourself this. If you are choosing to invest hundreds, thousands, or even millions of dollars, what worries you more? That the programming language wasn’t as easy to pick up for your average programmer, or that the application was built in a robust manner?

Now, caveat. Given that Cardano is only just launching its smart contract capability, it remains to be seen how much more bug free smart contracts built on Cardano are. But Cardano’s focus on software security and it’s choice of Haskell as a language is certainly reassuring

Addendum

I thought I would include this interaction I had with a Haskell user as I think its a good demonstration of the complexities of the language. I actually enjoyed digging into this to understand how it works but that might just be my personality type. Also worth noting is that the exchange below may have been circumvented with better tooling to make it easier to trace inputs/outputs.

ReddSpark: Can someone help me understand this:

logError = Contract . L.logError . toJSON

I believe what is happening is that the result of the toJSON gets fed into logError — all good there — and that logError returns one of 4 parameters that Contract takes (w s e a) , and therefore the end result is a partial / curried Contract that has the first parameter filled in? (I feel like im wrong but that’s the best of my understanding to date)@ReddSparkCan someone help me understand this: logError = Contract . L.logError . toJSON I believe what is happening is that the result of the toJSON gets fed into logError — all good there — and that logError returns one of 4 parameters that Contract takes (w s e a) , and therefore the end result is a partial / curried Contract that has the first parameter filled in? (I feel like im wrong but that’s the best of my understanding to date)

KernalJ: It’s important not to get confused between the type and the constructor of that type, which often happen to have the same name. If you check the type of Contract constructor:

> :t Contract
Contract
  :: freer-simple-1.2.1.1:Control.Monad.Freer.Internal.Eff
       (Plutus.Contract.Types.ContractEffs w e) a
     -> Contract w s e a

You can clearly see it only takes one parameter to produce something of type Contract (with relevant type parameters). Or if you look in :i Contract you’ll see the definition

newtype Contract w s e a
  = Contract {unContract :: freer-simple-1.2.1.1:Control.Monad.Freer.Internal.Eff
                              (Plutus.Contract.Types.ContractEffs w e) a}

In this case Contract is just a newtype wrapper around some other type. This is of course compatible with the type returned by L.logError.

ReddSpark: Thanks. So I see that it takes:

freer-simple-1.2.1.1:Control.Monad.Freer.Internal.Eff
       (Plutus.Contract.Types.ContractEffs w e) a

Is that all just treated as 1 param then? The logError returns

Eff effs ()

I can’t tell if that equates to

freer-simple-1.2.1.1:Control.Monad.Freer.Internal.Eff
       (Plutus.Contract.Types.ContractEffs w e) a

KernalJ: Yes in this case Eff is freer-simple-1.2.1.1:Control.Monad.Freer.Internal.Eff, effs = Plutus.Contract.Types.ContractEffs w e and a = (). If I recall correctly the types starting lowercase are ‘universally quantified’ and the process used to refine them is called ‘unification’.

Published by ReddSpark

Follow me on Twitter: https://twitter.com/Redd_Spark or YouTube https://www.youtube.com/@ReddSpark

Leave a comment