How do old technologies become relevant dozens of years after their conception?
Since their origin in telecommunications, Erlang and its VM have gone a long way. But everything that we think of as great was set in motion more than 20 years ago, through the power of permissive design and good decision making.
In this article, we are going to look at the origins of Erlang, how it enables the functional programming paradigm and the actor model.
Together with you, we will also look into the BEAM: the abstract machine that powers Elixir and Erlang, and how it allows software engineers to focus on business logic instead of the computational plumbing.
Finally, we’ll talk about the emergence of Elixir with its cleaner and leaner standard library, easier metaprogramming, and the best web framework known to humankind.
First off, though, a brief venture into computer games.
Video games have always utilized the capabilities of computers to their fullest degree. Take the first popular game: Spacewar. It used the advanced input handling of PDP1 to allow two players to battle in outer space.
Conversely, computer games also have informed the development of hardware.
Data storage and memory. Setting popular games aside, the necessity to extend the capabilities of computers goes as far as the midforties. Then, in a failed attempt to make a flight simulator machine called “Whirlwind”, significant proceedings were made in the field of reliable magnetic data storage.
Cocomputing with GPUs. Once computers became more and more alike, the idea of playing video games using them became more appealing. In 1994, Sony released PlayStation featuring a separate processor that was crunching the video rendered in games, coining the term “GPU”.
We didn’t have to wait for long for realtime video processing on “personal computers”. Nvidia released its first GeForce board in 1999. Over time, computer games became more and more demanding. It forced Nvidia and ATI to work hard on parallel processing of video data. Soon, scientists and software engineers realized that not only video data can be crunched on GPUs. For instance, you can contribute to dealing with the 2019 strain of coronavirus, which looks to be the bane of 2020, using your GPU, of course.
Why do we mention computer games, you might think? For the power of analogy. The evolution of BEAM languages has traced the same path: a technology made for highperformance computing in a comparatively niche domain morphs into an industry powerhouse after decades of gestation. Witness.
To get to Elixir, we first have to start with its predecessor, Erlang. Therefore, let’s move to the stormy shores of Scandinavia. It’s 1986, and Ericsson is trying to solve the problem of reliable phone switching. The most important success criterion is that such applications have to have zero downtime. In the words of late Joe Armstrong, one of the founding fathers of Erlang, it was a quest to write programs that run forever. The outcome of this effort was Erlang, which, not unlike computer games, precedes a lot of approaches and tools that are used in modern computing.
Let’s delve into Erlang’s history and notice those things.
Green threads/fibers. A key design aspect of Erlang as a language is that of a process. It abstracts away operating system processes, bringing the notion to the language level. All the computations in Erlang are done within those abstract processes. Of course, Erlang wasn’t the first language to take this route. However, its design exploits such abstraction to its fullest. Erlang processes are basically green threads.
AMQP. Erlang uses message passing between processes instead of allowing locks over shared resources. It also has socalled mailboxes, which are queues attached to every process. As the process handles the messages, mailboxes get emptied. An overflowing mailbox can be interactively inspected in runtime, enabling great maintenance flexibility. Inspired by experiments with Smalltalk language, it preceded message queue systems. One of the most popular of those is called RabbitMQ and is, incidentally, written in Erlang. Erlang’s message passing is basically AMQP.
Continuous delivery. Telephony applications have to keep running. Thus, the design of Erlang also provides developers with a way to update configurations and even modules live. Erlang code update is basically continuous delivery.
Functional programming. Finally, Erlang is compatible by construction with functional programming. As a matter of fact, Erlang also took a lot of inspiration from logical programming. One can feel it in its “weird syntax”. The first implementation of Erlang was a metainterpreter written in Prolog. Therefore, Erlang is basically a functional programming language.
All these properties were great from the programming language theory standpoint, but there was a problem – the systems run via Prolog metainterpreters were painfully slow. To address this performance issue, a virtual machine called JAM (Joe’s Abstract Machine) was introduced and implemented in C by Mike Williams. It was good enough for industrial prototypes, but performance was still an issue.
Fortunately, a beam of hope for wider industrial usage came from the replacement of JAM with the BEAM (or, as it was known at the start – Turbo Erlang) by Bogumil Hausman in 1993. It was a highly efficient, threaded abstract machine that is a direct predecessor of the modern Erlang VM, which is also – albeit somewhat nonimaginatively – called the BEAM.
Other than the improvement in speed, continuous work on Erlang managed to yield other benefits as well:
Concurrent, parallel, and distributed computing. Concurrently (no pun intended), distributed Erlang was developed, allowing for firstclass clusterization of programs and multithreaded reductionbased scheduling. As long as there are no Byzantine actors, software compiled to the BEAM can be run across multiple computers without the need for OS configuration. Distributed Erlang is basically Kubernetes and Apache Spark in one.
Scalability. Together with zero downtime configuration updates, distributed Erlang allows for transparent scalability in both directions! It can automatically upscale and downscale your system. You can achieve both vertical scalability by feeding more cores to the scheduler and horizontal scalability by adding more servers to a cluster. A perfect property for a startup, isn’t it? Erlang is basically AWS Autoscaling.
In addition to astonishingly expressive firstclass abstractions in the language, Erlang also has excellent facilities for handling failure. One such facility, introduced in 1998, is Open Telecom Platform.
The name might be confusing at first, but it actually is just a part of Erlang’s standard library that was used for years to ensure the fault tolerance of telecom systems. OTP gives the user building blocks for arranging processes into a supervised hierarchy. With it, one can define a failure mode in which processes should cause restarts of parts of the system. This way, predicted failure is always contained and the system keeps running. Cascading failure can either be contained by a subsystem restart, based on how the OTP hierarchy is structured in the application. No failure should prevent the system as a whole from being in an operating state.
Aside from that, once a failure is detected, it is possible to remotely attach to any node and inspect any process, even visually, as the system runs!
In 1998, Erlang (for reasons, such as the difficulty of maintaining a proprietary language) was banned for new product development at Ericsson Radio. This led to the creation of Open Source Erlang. While one may initially view it as a bad thing, it did ultimately contribute to the spread of Erlang outside telephony into other areas where highperformance computing is necessary. Hooray for open source!
Slowly, Erlang found its way into highvolume Internet applications that involve messaging, distributed computing. In the meanwhile, Ericsson themselves decided that they can’t do without Erlang. And for reasons.
I’ll ask you a question. How do you support 450 million users with only 32 engineers?
The answer starts with Er and ends with lang.
Erlang might be a great language, but it is also one of the most dreaded, according to a Stack Overflow survey. Do you know which one is one of the most loved? Elixir.
When José Valim released an early version of Elixir, it felt like a breath of fresh air. In contrast to Erlang, Elixir had a promise for a more streamlined programming experience that would be more convenient for a modern developer that has used languages like Ruby and Java. But in contrast to them, it would build on the basis of Erlang VM and all the great things it entails.
Elixir delivered, for several reasons.
With its standard library, Elixir ships some handy data structures. It also eliminates boilerplate when it comes to declaring OTP hierarchies and other commonly performed standard function calls.
Elixir has very powerful and safe metaprogramming. Thanks to the quote/unquote functionality, it’s firstclass, as opposed to tricky metaprogramming in Erlang. Some practical and ingenious usage of Elixir’s metaprogramming can be seen in the unicode.ex module of Elixir’s standard library.
Finally, Phoenix is hands down the best web development framework that is out there. It offers the convenience of your regular framework while having the powerful BEAM to back it up. (In our list of future blog posts there is one on making a PWA with Phoenix, so stay tuned.)
These days, Elixir is a featurecomplete, stable language that respects backward compatibility.
In the words of a great man, “Elixir” is basically “Tooling”.
Not unlike games, the world of telecommunications has given us technology that has bloomed while enabling reliable, multiuser high load systems. For example, WhatsApp built its entire startup on Erlang and FreeBSD. (Erlang was phased out by Facebook in favor of inhouse C++ libraries only to get back to Erlang some years later.)
Among other companies that have successfully used BEAM languages to create largescale systems that work reliably and serve large numbers of users every day, we can name Facebook, Pinterest, Klarna, Discord, and Grindr. As Alice (in Wonderland) might attest, using an elixir is the best solution to any scaling issue.
Seeing as computer games were one of the main factors pushing the technology forward (because who doesn’t like to entertain themselves), they also probably indirectly contributed to the possibility of BEAM languages jumping from telephony to conventional computers.
These days, BEAM languages are actively contributing to the development of them. For example, World of Tanks has an Erlang implementation of chat and signal management systems. There are companies, like GrandCru, that write entire backends for multiplayer games in Elixir.
And hence, the answer to the question made at the start of the article. If you make something that is useful, solves a certain problem much better than anything else, and take care to make good decisions at every step of the design process, you are likely to come up with a technology that will change the world.
Be like the BEAM. Be useful.
If you want to learn more about BEAM languages and how they can help create scalable solutions for highvolume systems, follow us on Twitter.
]]>No doubt, by now you have read many posts talking about how to deal with the coronavirus: do’s and don’ts, the importance of social distancing, backseat epidemiology, etc.
But many of you have probably been thrown in a situation where you have to work remotely or manage remote work.
Serokell has been fully remote since it was founded over five years ago. Therefore, while we will briefly mention all the critical aspects that have already been talked about elsewhere, we will also try to give a unique perspective. Since we’ve been doing this for some time, we have had the luxury to think a little bit more about some of the less obvious things.
The article is split into two parts: personal recommendations to winter this period and recommendations to businesses that will inescapably have to move to remote for some time.
So, Europe is on lockdown, same goes for the States. There are pretty much universal suggestions for living around the world. Some governments push it arguably too far with tracking citizens (Israel, Iran, China), but overall, the message is clear – the situation is serious, the virus is dangerous, we should practice social distancing whether we like the governments’ intrusion in our lives or not.
We suggest you get the necessary health information from trusted sources – the World Health Organization and your government public information sources.
Let’s talk about the situation from a personal standpoint. Thing is, that if you or your employees are in distress or suffering, no business can be conducted. In these tough times you have to put mental and physical health of yourself and your employees before optimizing your budgets, running tallies, etc. Even if your business is on the ruthless, misanthropic side, you will lose it if you don’t consider your employees.
First off, social distancing is not a choice, but a moral duty that is mostly imposed by the government to make sure that bad actors don’t behave antisocially.
But social distancing is not the same as isolation. Isolation causes depression and, as it seems, physiological symptoms. If your city does wetcleaning and you can follow social distancing guidelines, we strongly suggest going out for a walk once in a while. Parks or squares are great if there is a way for you to get into one without breaking the social distance. If you’re living with someone and neither of you had contact with objects/people outside, it is reasonably safe to go out together as long as you’re confident that you can maintain distance from others. Remember to always check what doctors in your locality suggest before plotting your route. Due to your climate, population density, or both, it might be illadvised to leave the house.
Having physical exercise is important, so stuff like power walking outdoors and yoga can not only help you maintain an adequate lifestyle, but also could help you combat the mental health toll of increased isolation. Isolation makes us sadder, exercise and trees make us happier. Let’s hope these things balance out!
From an economic perspective: no matter how prepared you and your employer were for these interesting times, manymany others weren’t, and many more couldn’t. There will be issues with more expensive imports across the board (including raw materials), more halts of factories, etc. That’s why we suggest you take really good care of your items, especially electronics. Make sure to not drink close to computers.
For the professions that typically require presence, it makes sense to work with the employer to figure out remote options. If you have recently moved to working remotely, consult our blogpost on surviving remote work. There we list tips and tricks for picking up remote work and getting to be productive.
Consider using your skills to freelance via websites such as Fiverr, especially if you feel like your employer might have problems adjusting.
Also, despite market fluctuations, money is not going anywhere, so probably increasing the rate at which you do savings is a good idea, especially since cooking at home is suggested these days anyway. Don’t forget to balance out your diet, though! There are many materials online, but here’s an OK article that lists good suggestions.
Finally, if you’re alone, do some videomessaging with your relatives and friends. And don’t forget about the elderly and those with compromised immune systems. Now that you can’t visit them anymore, calling them is very important for them to suffer less.
Now let’s talk about our suggestions for business to cope with the situation.
Most of the business, with some adjustment of processes, can operate remotely. Supply chains are possible to manage while complying with the norms of social distancing, and manufacture—albeit with severe losses in production—will be able to adjust.
You’ll have to learn how to organize your work remotely. But first things first – make sure that people who work for you are safe. You will lose less in the long run by ensuring the safety of your employees. React swiftly, if you haven’t yet, figure out the processes later. Speaking of which…
As a business going remote, your first shock will be that people go missing in action. There are a lot of different reasons for that:
In Serokell, we have finelytuned processes that we have been improving for years. Every task is accounted for, and no effort goes unnoticed. We are using the YouTrack issue tracker to do that, and have processes for describing the tasks, planning, and tracking the effort. A free alternative to that would be to create perproject Trello boards.
For brief discussions, coordination. and watercooler talk, we’re using Slack, which is paid for. For a free alternative, you can have a look at Discord. Both allow for the creation of chat channels, voice comms, and screen sharing. Slack does a significantly better job with managing textual data, making it searchable, while Discord makes a better job with screen sharing and voice comms. Discord also has persistent voice channels, while Slack only allows for calls. In addition, some kind of meeting application is beneficial. At our company, we use Google Meet, and it is amazingly stable, allowing us to hold big meetings with Google Calendar integration. It works perfectly in Chrome and as a mobile app.
You most likely won’t be able to come up with effective automated processes overnight. It’s okay, we didn’t get there right away either. You can achieve a lot with a little.
Instill some sort of manual reporting routine and encourage your employees to follow it. This is necessary for management to understand what is going on. Please, be positive with your employees, talk to every single one of them about why reporting is so important if it is needed.
Your line of business might not be too friendly for remote work. Maybe you have a complex supply chain, or perhaps you do manufacturing. The good news is that retail can go remote thanks to ecommerce solutions, storage management can be done in a reasonable way if you sanitize storages and make sure that employees are transporting themselves to the warehouses and there is nobody in the warehouse that is not essential.
As you go remote, you have to look at what’s getting accomplished in your company and see if you can improve it somehow. Remote work lends itself to flatter management hierarchies, which makes it so that your managers should be able to focus on more creative tasks than oversight.
Remember that to ensure a smooth transition to remote work, you will inevitably have to tweak and evolve your business model.
Summing up, here’s a checklist for you:
Every business has its nuances. Suggestions mentioned in this post are necessary to implement, but what is sufficient might vary. In the end, even when the lockdowns are over, digitizing your business and pushing as many processes as you can to full remote will end up cutting costs and diversifying the hiring pool. If you need help and continued support in going remote, digitalization, and business analysis, reach out to us, or to me personally. We’ll try our best to provide pro bono consultations, within reason.
Remember, we’re humans, the most adaptable species barring, perhaps, only tardigrades. Somehow we’ll manage. We always do.
]]>In this interview, we talk with Ivan Gromakovskii – a legend of the Serokell technical team. In Serokell, Ivan is famous for his punctuality and productivity. He didn’t mention it, but during his work here, he has participated in ZuriHac, gotten a nickname “the Machine”, and tried his hand at writing articles – we posted his comparison of GitHub and GitLab on our blog. Ivan is a team lead of several Serokell projects, and he certainly has something to say, enjoy!
When I just joined Serokell, I was working on a small project on my own, and the main goal for me was to learn Haskell because I had no “production” experience with this language before that, I only studied it at university. However, I had experience with imperative languages, so I treated it as “just another language”, even though a quite different one.
Apart from learning Haskell, I was improving my general programming skills: how to structure your code, make it reusable and understandable, how to work in a team and communicate with your colleagues.
Each of my colleagues knows/does something better than me, during our work we constantly learn from each other.
When I make a pull request, code reviewers may suggest an approach that never came to my mind. It works the opposite way as well: when I review someone’s code, I may learn about a new library, compiler extension, or how some complex thing works. When I can’t find a good solution to some problem or understand how something works, I can always ask in Slack, and my colleagues will help me figure things out.
There were numerous cases when I learned something during code review or Slack discussions. Sometimes people discover and share new tools or posts useful for my work. And, of course, by working in a team I can improve my communication skills and skills of being a “team player”.
Yes, of course, knowledge sharing is useful. Most often, it happens when someone gets a “research” task which requires delving into something complex. Later on, this person will usually write something about his research and everyone will be able to learn this “complex” topic and ask questions if they appear.
Sometimes we cover basic topics as well. For example, Kirill Elagin wrote some pages about copyright and licenses for internal use. I knew almost nothing about it, so these pages were very helpful for me to learn these basics. Also, we have good internal sources to learn from: practical materials and academic papers.
First of all, in order to be a good team lead, you need great technical skills and knowledge. No matter what technical problem arises, you should be able to solve it. You should be really good with all the tools and technologies used in the project.
Apart from that, you should be able to take responsibility and make decisions. Team lead is the one that makes final decisions on behalf of the team. When a problem appears, you should take action to solve it. Ignoring it until someone explicitly asks you to handle it is not an option. You are the one who should explicitly ask someone.
Communication skills are no less important. You are a bridge between your team and upper management/your customers. You should clearly explain what needs to be done to the former and provide updates about your work to the latter.
Of course, I had to improve all the “main skills for team leads” that I mentioned above. To be more specific, one of the things I had to learn was how to split big tasks coming from your customer/outer world into finegrained tasks that can be independently assigned to multiple developers.
When you lead a team of multiple people, you must make sure that each of them has something to work on and there are no two people concurrently working on the same thing or two conflicting things.
Another thing is how to always be aware of what’s going on in your project, follow how the code evolves. I try to review as many pull requests as I can to always have uptodate knowledge of our code. So whenever someone asks me to help with a new task, I don’t have to go and see how certain things are implemented in our code, I can start thinking about this task immediately.
In Serokell, team leads pick appropriate tasks for juniors which shouldn’t be too hard (as too hard tasks may look scary and take too much time). At the same time, we help juniors grow and the complexity of tasks gradually increases over time.
The next step is to make sure that the person properly understands what needs to be done. Team leads explain what needs to be done, and juniors figure out how it should be done (asking your help only if necessary).
Code review is where the main action happens. I think that team leads should be calm and patient: sometimes juniors might misunderstand the task or come up with a wrong solution. Team leads explain what should be modified and provide guidance. I also review juniors’ code thoroughly and point out what can be improved — it is invaluable for their personal growth.
In our company, team leads provide feedback outside of code review as well, tell the person if they do something wrong or great. We ask them about their satisfaction with their work, maybe they dislike something (e. g. assigned tasks) but are too shy to say.
I think higher education is useful to learn basic things that every developer should know. It also helps engineers learn new things faster. After some point, the usefulness of higher education decreases, at least for me.
I think the exact point depends on preferred tasks and domains: if you work on something scientific and like doing research, this point will likely be much further for you than for a person who develops simple web pages, for example.
It is important for a developer to constantly learn something new, but for me, it just naturally happens as part of my work when I need to use a new technology or when I just read a cool article.
As for other materials, I suggest you read «The Manager’s Path: A Guide for Tech Leaders Navigating Growth and Change». It’s a good source of practical tips for team leaders and everyone who wants to be a successful manager in the IT industry.
Thanks, Ivan! We wish you lots of luck with your future projects, stay cool and be happy!
Hey folks! We are trying our best to make the most relevant content about functional programming and management in the IT industry. If you have any suggestions on what topics to cover – mention us on Twitter, and we’ll discuss the topic with our engineers.
]]>hGetContents: invalid argument (invalid byte sequence)
hPutChar: invalid argument (invalid character)
commitBuffer: invalid argument (invalid character)
Oh no!
Bad news: something is wrong. Good news: it is not necessarily an issue with your code, it can be one of the libraries or build tools that you depend on.
Yes, really. Haskell tools you are using every day have a problem that can cause this confusing error to show up. Here is a (definitely incomplete) list of projects still affected:
And these are the projects where it was, hopefully, fixed:
This is to announce that we have published a new Haskell library
called withutf8
and we hope that it will help solve this kind
of problems once and for all.
You can find everything you need to start using it in your projects
in its documentation on Hackage, while this post offers a slightly
more detailed explanation of what is going on and the reasoning
behind some of the design decisions we made.
What we all can do is raise awareness, and from now on try our best to write Haskell programs (and especially Haskell tooling) that will work correctly on the first try. For this, we only need to:
People speak many different languages and languages use different alphabets. To be able to work with characters from all these alphabets, people invented Unicode. Unicode is basically a huge collection (repertoire) of all characters present in all alphabets that ever existed and then some more.
Most modern programming languages have their Char
(or equivalent) type support
the full range of Unicode, and Haskell is not an exception.
This is a great thing, as it means that your program that greets the user will
be equally happy to greet a John, an Иван, and a たろう:
main :: IO ()
main = putStrLn "Name: " *> getLine >>= putStrLn . ("Hello, " <>)
$ ./greet
Name:
Кирилл
Hello, Кирилл
As a software developer, you know that everything in the computer is made of zeroes and ones, therefore there needs to be a way to represent this enormous repertoire of characters as sequences of bytes. This is what a character encoding (aka “character set”, aka “code page”) does.
Back in the day people used encodings such as latin1, cp1256, and koi8r. The weird thing about these is that each of them supports only a subset of whole Unicode, and, for some reason, for a long time everyone was OK with that (well, to be fair, Unicode did not yet exist back then, and the Internet was not very popular). There is no place for anything like that in the 21st century.
Nowadays we have UTF8, which can encode all of Unicode, and we also have UTF16 and UTF32… Wait, what? Excuse me, why are there three (actually, five) of them? The short answer is: UTF32 (being a fixedlength encoding) can be useful in rare algorithms where you need constanttime access to individual characters, and UTF16 was a mistake.
While you are in the world of your highlevel programming language,
you’ve got this nice abstraction where strings are just sequences of chars,
and chars are, you know, just characters, things you can compare to each other,
call toUpper
on, stuff like that.
However, the usefulness of your programs comes from the fact that
they communicate with the external world: they get data from stdin
,
files, network sockets, do some processing and then write the answer back.
And it is exactly at this border with the real world where
truly scary things start to happen.
For historical reasons, operating systems do not provide an abstraction for humanreadable text, and thus they always give your program raw bytes and expect raw bytes in response: bytes come from the user’s terminal, bytes are stored in the file system, bytes travel over the network. This is perfectly fine most of the time as your programs work with binary formats and protocols anyway.
But sometimes you might encounter just text. The two most prolific examples are:
In order to turn bytes into text and vice versa, one needs to know
which character encoding to use. You probably don’t think about this too
often, do you? You just call putStrLn
or hGetContents
and let your language handle this.
But, hold on a second… really, which encoding will it use?
As you have probably guessed already, the answer is not “always UTF8” –
otherwise I wouldn’t be writing this and you wouldn’t be reading it.
In Haskell, every file handle has an encoding associated with it;
you can query it using hGetEncoding
, and change using hSetEncoding
from System.IO
. If you don’t explicitly set it though,
it gets initialised with an encoding derived from the current
locale configured in the operating system.
On Linux, the default locale is determined by the LC_*
environment libraries,
the ones that the locale
command outputs. You can see this with
the help of a tiny Haskell program:
import GHC.IO.Encoding (getLocaleEncoding)
main :: IO ()
main = getLocaleEncoding >>= print
Save it as encoding.hs
, compile, and run:
$ ./encoding
UTF8
$ LANG=C ./encoding
ASCII
$ LANG=ru_RU.koi8r ./encoding
KOI8R
This behaviour certainly seems to make sense. However, I can think of another behaviour that would make sense too: just always use UTF8, right? In order to decide which one is better, we’ll have to dive into specific usecases.
As I said, most protocols and file formats do a good job specifying how exactly the bytes should be interpreted, however there is one pretty widespread file format that does not: plaintext.
When you see a plaintext file, there is no way for you to know what encoding it uses. In fact, if you come from a nonEnglishspeaking country, chances are, you are old enough to remember trying a bunch of different encodings in your text editor when opening a file until one of them happens to work.
Indeed, as a person using a text editor to write down some thoughts, you don’t really care what exact encoding your text editor uses as long as it uses it consistently (that is, you can close the file, open it later, and it will be readable). And you probably want to be able to open the file with a different editor. As long as all your editors are using the same encoding, for example, the one specified in your system locale, everything will be good.
Oh, wait. It’s 2020. Maybe you also want others to be able to read your files, even if they live in countries different from yours. Now it is starting to look like always using UTF8, even if you or your friend have some weird locale configured, might be a better choice. Luckily, nowadays UTF8 is the de facto standard for text files, so if you see a text file that has been created in the past 10 years or so, it is almost certainly encoded in UTF8.
Everything above applies to source code as well. Except that specifications of some languages actually restrict the set of characters valid in source code to ASCII, which have the unique property that they are encoded by the same sequences of bytes in almost all existing encodings (including UTF8), so you can decode such a file using almost any encoding and the result will be the same. Some other specifications explicitly say that source code must be encoded in UTF8. But we are talking Haskell here, and, surprisingly, the Language Report only says that the syntax uses Unicode, but does not mention any encodings at all, leaving it all to the compilers.
Well, our favourite Haskell compiler – GHC – assumes source files are encoded in UTF8. Well, strictly speaking, it truly ignores comments, which are allowed to contain sequences invalid in UTF8, but thinking about someone using this “feature” gives me chills.
Bottom line: in year 2020, all text files should be decoded as UTF8, especially those that contain source code, especially those that contain Haskell source code. And the user’s locale is not relevant at all.
Working with the console is similar to working with a file: your program can write some text to a special handle and the operating system will relay it to the user’s terminal. Whatever the user types in their terminal you can read from another special handle.
The important difference with text files is that the text shown on a terminal is meant to communicate some information and then disappear forever. For this reason, it is not very important what encoding it uses.
However, there is a complication: there is another program involved in the process of using the console – it is the user’s terminal emulator, the graphical application that actually renders the console on the screen. In order to pick the right symbols from the font, it needs to know how to decode the bytes coming from your program through the OS into humanreadable text. For this, it needs to know which encoding to use.
This encoding is configured somewhere in the settings of your terminal emulator. You probably knew about this configuration option, but you have long forgotten about its existence, and rightfully so, as most you will never need to use, as it is already set to the most universal value of all: UTF8.
The problem here is that if you blindly output UTF8 to the user’s terminal and it was configured to use a different encoding for some reason, you might end up getting some Р¶РѕРїР° or screwing the terminal entirely. The safest assumption to make is that the terminal emulator uses the same encoding as the locale of the operating system (well, otherwise it would be hard for the user to even read localised messages from standard system utilities).
What to do, though, if you really want to output a character that is not encodable in the locale’s encoding and, thus, impossible to render on the screen? Unfortunately, your only choice is to replace it with something else.
This restriction is especially hard on software development tools, since, even if they correctly set the encoding on files they read and write, they still need to worry about the characters that they show on the screen. In fact, GHC itself was bitten exactly by this: it did a good job reading source files as UTF8 regardless of the locale, but if there was a typechecking error in a definition whose name was unrepresentable in the locale’s encoding, it would crash trying to display the error on the screen.
Oh, by the way, exactly this problem is still present in Haddock and you now know enough to make it crash on Linux. Save this file:
module Hahahaddock where
domaĝo :: String
domaĝo = "pity"
and run haddock
on it with LANG=C
. It will crash trying to say the name
of the definition that is missing documentation.
Why would your program ever be used on a system with a locale that uses an encoding that is not UTF8?
Well, the simplest thing you can do is to give your program to a Windows user.
You’ll be surprised how hard Microsoft is trying not to do the right thing
and simply use UTF8 everywhere (well, if you followed the link to
UTF8 Everywhere above, then you would’t be surprised at
this point). There are still a lot of Windows installations that use
various esoteric character encodings (or “code pages” as they call them).
That is the reason why a lot of the encodingrelated issues are reported
by Windows users, and that’s actually great, because it helps solve the problem.
Unfortunately, sometimes the solutions are not wellthoughtout, such as
this fix to a real problem affecting everyone,
bashfully hidden behind #if defined(mingw32_HOST_OS)
for no good reason
at all.
Another reason to unset the locale to C
is reproducible builds. That is
why another source of encodingrelated bug reports is Nix users. Current
locale settings can affect the build process in subtle ways, such as
change the format of dates or the sorting order for strings.
It’s just weird to think that a build tool may output different results
depending on the locale of the system it is used on, so people
preparing builds for others prefer not to give it even a chance and change
locale to the most universal and simple one – C
. Sadly, this has a
consequence of changing the locale encoding to ASCII. Even more sadly,
there does not exist a standardised locale which would behave like C
but
have UTF8
as its default encoding.
Debian’s C.UTF8
is one attempt at standardising this,
but it is not yet as widespread as it should be.
I hope you can agree now that the defaults chosen by GHC are not optimal, as evidenced by the plague of encodingrelated issues in the Haskell world. Here is a more reliable strategy.
Whenever your program opens a text file, in 99.9% of all cases
you really want it to be treated as UTF8. Calling hSetEncoding
on every file after opening is not very convenient. Luckily,
there is an easier way.
As you remember, GHC creates new file handles with the encoding taken
from getLocaleEncoding
. All we have to do is call setLocaleEncoding utf8
!
Now all files opened in text mode will automatically use UTF8, and you
can still change it to something else on individual files if you really need
to. Of course, this is only a good idea in your own executable; if you
are creating a library, changing programglobal defaults is a nono.
If your library function accepts a handle as an argument and then writes to it, it is very likely that you expect this handle to be a file handle rather than a terminal device (unless, of course, you are developing a library that is directly related to showing information to the user on their screen, such as a CLI toolkit).
In this case, you want to make sure the encoding is UTF8, however, if
for some reason it happens so that the handle is connected to a
terminal, you are in trouble because you never want to change the
encoding of text shown on the screen by the terminal emulator.
Luckily, you can use hIsTerminalDevice
to detect this situation.
What do you do if it is a terminal device? Well, you have to replace
all unencodable characters with something else. I have good news again:
you will not need to change your function at all – the iconv
library that
GHC uses under the hood has you covered and it can approximate
(aka transliterate) those bad characters for you. All you have to do
is change the encoding of the handle to a newly created one, which you
make by taking the name of the old encoding and appending //TRANSLIT
to it.
The three standard handles – stdin
, stdout
, and stderr
– are
normally used for interacting with the user. When they are attached
to a terminal, there is no question, you have to transliterate them.
But what if, say, stdout
is not attached to a terminal?
This situation is tricky, and you will have to guess what the user
wants to do with the output of your program.
If the output of your program is informational and
only useful for a short period of time,
then you probably don’t want to try to encode it in UTF8.
The reason is simple: if the user pipes the output through something
like grep
, your stdout
will not be connected to a terminal, but
the output will end up on the terminal anyway.`
On the other hand, if your program is meant to process text files and supports printing the resulting new file to standard output, then you do want to encode it in UTF8, as long as the standard output is not connected to a terminal but rather redirected to a file. In this case, you can just treat the standard handle as any other unknown handle passed to your code from somewhere else.
withutf8
All of the above is implemented in withutf8
.
I invite you to take a look at its Hackage documentation, but to quickly sum it up:
withUtf8
function is a wrapper around your main
that will
call setLocaleEncoding
and reconfigure standard descriptors
so that they don’t cause a runtime error no matter what you
write in them.Utf8.withFile
(and Utf8.openFile
) that will
set the encoding on the file it opens to UTF8.Utf8.withHandle
to temporarily switch it to UTF8 if it is safe to do so,
or enable approximation otherwise.Utf8.withTerminalHandle
to only enable approximation,
regardless of whether it points to a terminal or not.Utf8.readFile
and Utf8.writeFile
, just
in case you need them.Please, consider using UTF8 and withutf8
in your next project,
update your old projects, reexport it from your custom prelude,
send a link to UTF8 Everywhere and
this blog post to your colleagues and friends, report and fix
encodingrelated bugs in Haskell tools that you are using.
Together we can defeat invalid argument (invalid byte sequence)!
A few years ago, Arseniy Seroka and Jonn Mostovoy started on a quest to answer that question for themselves.
Now we have Serokell, a company of over 50 people that work remotely, sharing the same values and creating spectacular projects.
How did they realize their dream? To find out the answer, watch this interview with our CEO Arseniy Seroka (made by the lovely people at HuntIT). While the interview is in Russian, we have added English subtitles for our international audience.
In addition, we have also featured some of the more interesting questions and answers down below!
The thing is that we do not have an office. Even though we don’t have that many people, more than 50, less than 60, we are an international company, we work from the whole world remotely and we try to hold on to the principle that we do not have any official place of gathering or office.
We have employees from all parts of the world, some from America, some from Europe (and Russia), and Asia, and we coordinate all that with modern tools like Slack, YouTrack, GitHub and other means of communication but the main thing is that we try to cultivate, develop, and maintain certain processes within the company that help us communicate better.
For example, even if somebody has talked with somebody else offline, we still try to bring the information back to Slack.
Mostly, the employees at Serokell are young specialists, 2030 years old, if we are talking about developers. We do not have only developers at our company, there’s also marketing and HR, for example. But the main backbone of the company is these people aged 2030 with similar interests, love of Haskell and functional programming.
And, you know, it is a very good uniting force because there is not a lot of work in Haskell out there (even less in real projects) and when the developers find out that, woah, I can do that not only for money but actually have an interesting project – it means a lot.
Yeah, of course, a lot of our people are from St. Petersburg, some of them are from Moscow, several from Amsterdam. Naturally, those who live in one city frequently meet without us organizing any events but we try to do trips together, try to visit different conferences, participate in different events. In those cases, we try to gather together different people from different parts of the world, to go to conferences and then the person can meet others, communicate.
To be honest, you could count on one hand the number of employees that have not met somebody else from the company, have not talked to them.
We understand that remote is extremely hard, it is hard on you psychologically, especially if you have never done it before. That’s why we are trying our best to create an atmosphere with a feeling that we all are one team, make our colleagues and comrades feel that they are not alone, that even if you work in the Canary Islands (we do have employees that work there), you’re still in the loop.
Officially, we are located in Estonia. Most of our clients are from the USA, there is a client from Europe, and there is a client from Korea – those are the ones I can talk about publicly.
In addition, we have a lot of our own initiatives, first – in research, and second, we very slowly and accurately are trying to make our own small products about which, unfortunately, I can’t talk about right now.
They are connected with problems that we run into in our daily work, instruments, tools for automation, HR, project management, everything that is modern and popular. We can’t find it all in one instrument on the market right now.
We are just trying to solve this task for ourselves and we have the feeling that a lot of projects, a lot of companies of our scale, basically, middlesized businesses, have a need, for which we will try to release a product on the market.
We use Haskell, we use Nix and NixOS for our servers and deploying systems. We are doing a little bit of frontend, and for that, we generally use TypeScript, PureScript, but the main specialization is backend. In addition, we use Erlang, Elixir, and just a tiny bit of Rust.
Obviously, everything depends on the business task. You can’t say that Haskell is perfect for everything or that C++ is the panacea. The main thing is to know your instrument, to know other instruments, and be thoughtful about what you are doing.
Yes, our philosophy is to do as much as we can as open source. We often try to explain to our customers: If you have a large system that people need to trust, do open source. You will not lose money if you have a correct business model.
The modern world as whole goes toward releasing all source code because people are starting to think about how the computers work, what are we using, and if they do not spy on us while we walk past our webcams.
We are trying to release our own libraries and the projects that we do, both commercial and nonprofit. In addition, we have several people working on compiler development for Haskell. That is, we pay them to develop the compiler that the whole world uses.
Yeah, it happened that way. A very large factor in the emergence of Serokell was my lecturer of functional programming, Jan Malahovsky, a wonderful human being who inspired me to study all these technologies.
And, it so happened that about 4 years ago he moved to France to do a PhD and there was no one left to lecture on functional programming because it is not a popular area of study at our faculty. (I studied and finished IT at ITMO.)
I understood that I am the only one at the faculty that knows the subject, loves the subject, and I understood that I want to give my knowledge to students just to somehow popularize those approaches, instruments, and technologies.
It’s a difficult question. We have quite an informal atmosphere and any employee can write to anybody else if they wish and in different situations if people need help at anything.
For example, something has happened to the person in Petersburg and they have just moved in, they can write in the chat and practically everyone will answer them and give some kind of help.
Often, we try to keep in touch with each other. We don’t have it like: here is the supervisor, here is the subordinate, you cannot talk with those and those. No, we have quite a friendly atmosphere and everyone understands approximately what is happening with others, the people are open, the people have some kind of life experience, and try to have each other’s backs.
We do not rank people as juniors, middle, or seniors. We know and understand what everybody knows, is capable of, what background they have. For the most part, we try to hire people that know how to learn quickly. Technologies change as quickly as tides, and it is not really important for us whether you know any specific framework or technology.
Good question. Our company position on this matter is such: for example, a person has a task and the agreement is that it should be done on Friday. The person says, yeah I will do it on Friday, no worries, or says that to do it on Friday is unreal – I will do it on Monday. But, if the person has named some deadline, then we trust him.
The main thing that we try to create in the company is trust in each other, respect for each other’s word. If a person says on Wednesday that, strictly speaking, I said I will do it on Friday, but I can’t really make it, (programming is not really an exact science) then obviously, talking, communication is the key to everything as always. And, if you give in time the information that you can’t do that task before the deadline, it helps prevent further problems.
The main indicator for people we are searching for is, for the most part, those that love to learn and who want to learn because we try to be wellrounded.
When a person comes to you from an area where they had to build ships and they become your head of HR or when a mathematician who does logic comes to you and becomes a developer, every one of them brings a part of their experience and knowledge to our common piggy bank.
That allows us to grow not only in one direction, but to develop in all directions and be kind to the world.
We love science very much, we want to develop computer science and science in general. We love such things as formal verification which is not that well developed.
You prove a theorem of the fact that the program works. For example, dependent types. It is quite a deep technical process, but we are trying, we are doing it right now, we are trying to popularize things that allow you to do more qualitative work.
Yes, it is also harder work but it is of better quality, and as a result, the client suffers less and the people suffer less.
That’s it! If you want to read more interviews, both with our employees and with other Haskell developers, go to our interview section. If the questions interested you, be sure to also check out the full interview on the HuntIT YouTube channel.
]]>In order to learn about various ways one could end up doing functional programming at a company like Serokell, I asked three of our engineers how they started with FP. The answers were lowkey amazing and surprisingly different from each other. While Pasquale was struck by the expressivity of the language, someone like Anton has found in Haskell a great vessel for applying his knowledge of category theory.
But I won’t give you any more spoilers – read on!
Pasquale Pinto
I first encountered functional programming with, oddly enough, Python. At the time, it was the language I was using almost exclusively and had done so for quite some time. As such, I knew most common practices, tooling and even pointless curiosities about it, the only real exception was its (relatively uncommon) functional side.
However, after a few times I had seen/used it, it struck me as very expressive and so, out of curiosity, I decided to take a deeper look. Wandering around forums and such, eventually (perhaps inevitably) someone suggested a “real” functional language: Haskell. As soon as I opened haskell.org, I had a flashback: I had been there before, multiple times, and even completed the small interactive tutorial, but somehow it didn’t catch my attention beforehand.
This time, I decided, it was going to be different, so I picked the book that seemed the most beginnerfriendly – Learn You a Haskell for Great Good and I imposed myself to finish it, hoping not to regret the time it would take. I went through the first half of it only out of stubbornness: I didn’t see what was the point and making even simple things seemed comparatively very cumbersome. Definitely not impressed.
Then all of a sudden it hit me: the first eureka moment arrived when I learned about partial application and that every function is curried. It took me half an hour just to stop considering the endless possible applications of these two combined and how composable, readable and versatile (aka elegant, I later learned) the language really was, not to mention this was just impossible in any programming language I had ever seen before.
Even though the complexity of the arguments quickly grew, and took me quite some effort to keep up, I devoured the rest of the book and kept learning from everything I could find from that moment forward.
For me, it was quite hard to implement nontoysized code at first, but eventually I managed to write a couple of small projects. At that point, I knew I wanted to do this fulltime and lucked out when I got a position at Serokell.
I have since learned tenfold about Haskell, quite a bit about Elixir, and functional programming in general; to this day I still get surprised at the possibilities and cleverness of the community and my colleagues. So, in the end, I cannot say it has been a walk in the park, but this all makes it worth it. No ragrets.
Aleksandr Pakulev
My first acquaintance with functional programming began in the first year of the university. I accidentally found out about Haskell and decided to attend a training course on stepik.org. But due to the lack of the necessary knowledge and programming experience in general, I could not fully understand what was going on and what was this all about.
Afterwards, I had courses of math logic and type theory, and after that, Ilya Peresadin and George Agapov (from Serokell) taught us functional programming in Haskell. Ilya’s maximum openness in communication and teaching greatly influenced my attitude about Haskell and functional programming in general. Together with an understanding of all the advantages of functional programming, this has led me to begin getting real pleasure using Haskell and to want to continue using it further.
Anton Myasnikov
Ever since elementary school, I was obsessed with the idea that our life is highly deterministic, like a game, and there surely must be some kind of strict order of actions that need to be performed in order to eventually achieve the desired result.
Like when playing games, I was interested not in specific game rules that need to be followed directly, but more in their influence on the sequence of keys that need to be pressed in order to achieve the desired result. I even had a diary in which I wrote down such sequences, in fact formalizing the process of playing.
In elementary school, where we were given the basics of math, I was surprised that when solving some simple math problems, you use abstract formulas that define the general behavior of things and then you just twist the variables, working only with their behavior. Like you don’t formulate any object and its properties directly, but only work with some specific behavior, no matter what the object is. This has had a strong influence on my attitude in contemplating many things. I was good with math at school, was sent to all possible Olympiads that I can participate in, eventually took first place in my country in physics)
In high school, I got acquainted with Pascal. I used to solve some simple math problems and then was introduced with the concept of lists and dictionaries. This fascinated me a fair amount and I played with them a lot. When I wasn’t solving needed tasks, I was writing down everything that came to my mind with these dictionaries and trying to formulate all kinds of interconnections between them.
Then I realized that this is how programming works in general, and then I wondered how it works in other languages. At the time, my vision of programming was limited to Python, C, C++, Java, and JS. And in the end, I started learning everything. One day, one of my internetfriends advised me to read a little about category theory because that’s what it’s all about, formalization of relations. Of course, in order to understand it, it took me a little while (despite the fact that everything is much simpler than it seemed to me at first), and eventually, I began to build my worldview in terms of category theory (all kinds of social relationships in the form of colimits, logical systems, even such abstract concepts as importance, usefulness, relevance). I came to Haskell almost by accident, but I immediately was inspired because of many concepts from category theory such as functors and monads that were applied to realworld production.
Overall, it has helped me to understand a lot of things that I have been working on lately from a different angle, inspiring me to work more deeply on this topic, and in general to sort out my thoughts.
I would like to thank all three engineers for talking with me, and I hope you find their answers as fascinating as I did. If you want to read more about people that work at Serokell, you can find more interviews in our interview section.
If, on the other hand, this article inspired you to start learning Haskell, we also have you covered! This article by Yulia will point you to the best resources for becoming a great Haskeller, and, perhaps, after some time, you can work with us! :)
]]>In simple terms, a runtime witness is a value that in some way holds some typelevel information associated with a polymorphic value and makes it available to the type checking process.
But this is confusing because type checking happens at compile time and values are often available only at runtime. So how can values provide type information at compile time?
It is possible because even though values are only available at runtime, if there is a branch in the code (ifthenelse, case statements) that branches on a value, we can make assumptions about that value inside each of the branches.
So, for example, if there is a branch in code like:
if (i == 1) then {  block 1  } else {  block 2  }
we can safely assume that if we find ourselves in block 1, then i
will be 1
inside that block and if we find ourselves in block 2, then i
was not 1.
Thus, at compile time, we will have some information about a value in conditional branches of the code that branches on the said value. The core idea of the type witness technique is to use this information to make the compiler infer the attributes of a polymorphic type, such as what the inferred type is, how it is constrained, etc.
For this, we first need a way to link a value to some typelevel
detail. In Haskell, we have the GADTs
extension that enables us to define a
data type of the form
data MyData a where
MyValue1 :: MyData Int
MyValue2 :: MyData String
MyValue3 :: MyData Char
The powerful thing about this kind of data definitions is that it enables us to explicitly mark the constructors to be of a certain concrete type.
And thus, we have declared values MyValue1
, MyValue2
, and MyValue3
to have
types MyData Int
, MyData String
, and MyData Char
respectively.
Therefore, these values can now point to what the type a
in MyData a
is.
Now, let us see how this type can act as a “witness”.
Consider the following function. You can see that by branching on the
MyData a
value, we are able to figure out what a
is.
func1 :: MyData a > a
func1 myData =
case myData of
MyValue1 > 10  `a` is Int here
MyValue2 > "I am a string"  `a` is String here
MyValue3 > 'c'  `a` is Char here
But how is the name “witness” justified? What is it witnessing?
Imagine that this function is part of an expression, say
(10 :: Int) + (func1 MyValue1)
The MyValue1
constructor, by being a part of the call site, is witnessing a piece of information that is only available there, that is, the type required at the call site is actually Int
.
Hence the name “type witness”. We can have witnesses that witness other
things, like a
is the same type as b
, or a
has been constrained in a certain
way, etc.
We saw that the values of MyData a
can point to what a
is. In addition,
since each polymorphic variant of MyData a
contains one and only one value,
the concrete types of MyData a
can also point to value, because there is only
one possible value for any given variant. Such types where there is a onetoone
correspondence between values and types are called Singletons.
Static type systems can feel very restrictive at the beginning, but, if they are sufficiently advanced, you will find that you can get some of that flexibility of dynamically typed languages back while retaining the safety of static typing.
Let’s see an example where this is manifested, which also involves the use of a type witness.
Imagine you are building an application that has got users with different privileges. We represent the possible privilege using a type,
data UserPrivilege = Member  Admin  Guest
and the users can now be represented by something like,
data User = User { userId :: Integer, userName :: Text, userPrivilege :: UserPrivilege }
Since we are interested in type safety, we want to make the userPrivilege
attribute to be
at the type level, so that if we pass a user of privilege Member
to a function that
requires a user with privilege Admin
, the compiler will catch it at compile time.
To do this, we add a type argument to the User
type. We also enable the DataKinds
extension so that the constructors of UserPrivilege
will be available at the type level
to tag the User
type with. So, we end up with something like,
data User (ut :: UserPrivilege) = User { userId :: Integer, userName :: Text }
Now, we have the user privilege at the type level, and this will prevent us from passing
User 'Member
to a function that requires User 'Admin
.
But we find it is now impossible to write a function that reads a user from the database without explicitly specifying which privilege the user has. So for example, we try to implement this function with the following type.
fetchUserById :: Int > IO (User a)
But this is not possible because if you read the user from the database and
find the user to be of type ‘member’, you won’t be able to return the concrete
type User 'Member
from the function, because the signature says that it
should be able to return User a
for all a
.
The idea of a polymorphic value User a
is that it should be able to concretize into
any type, as required by the expression where the polymorphic value is used.
So here, in the fetchUserById
function, if we find the user read from the database to
have a privilege of Admin
, we can return the concrete value only after checking that
the caller of this function is indeed asking for User 'Admin
. We have seen how it can
be done in the func1
function we saw earlier. But here, we won’t be able to use something
like that, simply because we wouldn’t know the privilege of the user when we make
the fetchUserById
call.
One solution to this problem is to wrap the user
type in another type, which will have
multiple constructors, each wrapping a different type of user, thus hiding the typelevel
privilege behind them.
data UserWrapper
= MemberUser (User 'Member)
 AdminUser (User 'Admin)
 GuestUser (User 'Guest)
A problem with this approach is that you will have to match on all these constructors
every time you read a user
from db to do anything with it, even when you don’t care about the
privilege of the user.
Another way to hide the typelevel privilege is by using a GADT
wrapper type that hides the typelevel
privilege behind a GADT constructor.
data SomeUser where
SomeUser :: forall a. User a > SomeUser
Since the SomeUser
type constructor does not have the type parameter, we can
wrap it around a User a
of any privilege and return from our database read
function.
But now, we will find that the User a
that is unwrapped from the SomeUser
type can only be
used with functions that accept a polymorphic user, that is, User a
, and cannot be used with
a function that requires concrete types, such as User 'Admin
.
This is exactly what we wanted in the first place. We are prevented from
passing a user of unknown privilege to a function that requires an administrator
privilege. But it seems that now we cannot make that call at all. How can we
convince the type checker that the User a
unwrapped from SomeUser
is in fact
User 'Admin
?
We can do that by using a type witness. We add the following type to act as a witness.
data WitnessPrivilege up where
WitnessMember :: WitnessPrivilege Member
WitnessGuest :: WitnessPrivilege Guest
WitnessAdmin :: WitnessPrivilege Admin
Then we change the User
type to include this witness as one of its fields.
data User (up :: UserPrivilege) = User
{ userId :: Integer
, userName :: String
, userPrivilege :: WitnessPrivilege up
}
And that is it. When you want to convert a User a
unwrapped from SomeUser
to a concrete type, like User 'Admin
, you only have to pattern match on
the userPrivilege
field. As soon as you get a match on the WitnessAdmin
branch, GHC will have inferred the User a
to be an User 'Admin
, and allow you to
call functions that require User 'Admin
.
Thanks to the included type witness, we get the best of both worlds; a typelevel user privilege which gets out of the way when you don’t need it, but can pop up anytime you need it.
{# Language GADTs #}
{# Language DataKinds #}
{# Language KindSignatures #}
{# Language ExistentialQuantification #}
{# Language ScopedTypeVariables #}
module Main where
import Data.List
 User privileges for our users
data UserPrivilege = Member  Admin  Guest
 Our type witness
data WitnessPrivilege up where
WitnessMember :: WitnessPrivilege Member
WitnessGuest :: WitnessPrivilege Guest
WitnessAdmin :: WitnessPrivilege Admin
 Our user type
data User (up :: UserPrivilege) = User
{ userId :: Integer
, userName :: String
, userPrivilege :: WitnessPrivilege up
}
 The type that we use to hide the privilege type variable
data SomeUser where
SomeUser :: User a > SomeUser
 A function that accept a user id (Integer), and reads
 the corresponding user from the database. Note that the return
 type level privilege is hidden in the return value `SomeUser`.
readUser :: Integer > IO SomeUser
readUser userId = pure $ case find ((== userId) . (\(a, _, _) > a)) dbRows of
Just (id_, name_, type_) > let
in case type_ of
"member" > SomeUser (User id_ name_ WitnessMember)
"guest" > SomeUser (User id_ name_ WitnessGuest)
"admin" > SomeUser (User id_ name_ WitnessAdmin)
Nothing > error "User not found"
 This is a function that does not care
 about user privilege
getUserName :: User up > String
getUserName = userName
 This is a function only allows user
 with Admin privilege.
deleteStuffAsAdmin :: User 'Admin > IO ()
deleteStuffAsAdmin _ = pure ()
main :: IO ()
main = do
(SomeUser user) < readUser 12
putStrLn $ getUserName user  We don't care about user privilege here
case userPrivilege user of  But here we do.
 So we bring the typelevel user privilege in scope by matching
 on `userPrivilege` field and then GHC knows that `user`
 is actually `User 'Admin`, and so we can call `deleteStuffAsAdmin`
 with `user`.
WitnessAdmin >
deleteStuffAsAdmin user
_ > error "Need admin user"
dbRows :: [(Integer, String, String)]
dbRows =
[ (10, "John", "member")
, (11, "alice", "guest")
, (12, "bob", "admin")
]
Riskbook is a marketplace that connects reinsurance brokers and underwriters, and Haskell has been working out quite well for them. In the interview, we talk about how Haskell can be a blessing when introducing changes to the codebase, what libraries and extensions they use in their project, and what are the downsides of choosing Haskell which you should consider.
My name is Jezen Thomas, and I’m the CTO and one of the cofounders of Riskbook. We’re building a marketplace product for the reinsurance industry. This essentially involves enabling reinsurance brokers to create risk programmes and then market them to underwriters, all within our online platform. A risk programme has a fairly complex life cycle, and we model and digitalise all of the steps that professionals in the industry would traditionally have done on paper, which is of course far less efficient and far more expensive.
I’m proud to say that we have attracted a distributed team of truly brilliant people, we’re backed by some top angel investors and venture capital firms, and we have early traction with some of the biggest businesses in the industry.
I think the biggest challenge we face is not unique to us — we want to quickly build robust software on a modest startup budget. I can say we have overcome this challenge to some degree; we’ve managed to hit several key milestones and our investors have so far been super impressed with how much we managed to do with so little, but carefully balancing where you invest and where you take on technical debt is something that persists through the life of a business. We’re not solving any wildly complicated problems at this stage of the business — at least relative to what many startups in AI/ML/Cryptoland are doing. We’re just trying to delight our users and build the kind of company we all would love to continue working at.
Haskell and GHC give us an enormous amount of leverage even though we stick to writing fairly boring Haskell code. The typechecker enables us to quickly make broad, sweeping changes to the codebase with a relatively high level of confidence. Business is all about adapting to change — especially when you’re a startup — so having the facility to rapidly shuffle things around is crucial.
Furthermore, we are lucky to have such a rich package ecosystem. While we have contributed some features and patches to opensource libraries, we are overall very happy with the libraries we make heavy use of, e.g., Yesod, Conduit, and Persistent/Esqueleto.
The people who founded Basecamp (formerly 37 Signals) aren’t crazy. We run Riskbook quite similarly to the advice offered in their book Rework, and I strongly recommend anyone starting a business read that first. You can build an excellent business by hiring a small team of smart people, taking care of them, and listening to them. Hire remote, and don’t inundate people with synchronous meetings.
Haskell is absolutely production ready. Yesod is an excellent web framework (though there are others too), there is good documentation freely available online, and there are plenty of friendly people around the world who can help you get started.
Simple traditional web applications still work great. For most business applications, you don’t need a distributed microservice system that serves up a JSON API for consumption by a complex JavaScript application that runs in the user’s browser. This may be contrary to what many people in the usual online programmer haunts might have you believe.
Good question. We don’t consider our team to be outsourced, though we are geographically distributed with people in the United Kingdom, Russia, Canada, Netherlands, and Ukraine. We don’t believe that having everyone colocated in an office is necessary for social cohesion, and it is most certainly to our benefit that we are able to hire from just about anywhere in the world. That said, we do appreciate that a good social bond is important for a team of professionals, and in November of 2019 we flew everyone on the team to Belgrade, Serbia, for a week of learning more about the reinsurance industry, and generally spending time together. We plan to have the team meet up in exotic locations more frequently as our company matures.
Riskbook is a partially eventsourced system written with the Yesod web framework. We lean on GHC as much as we can, while constantly trying to balance robustness with development and maintenance cost. We’re using the yesodtest framework for fairly highlevel integrated tests, and Hspec otherwise. We would like to invest more in propertybased testing.
Most interactivity is implemented with serverside templating in an orthodox RESTful style (where that makes sense), though more stateful user interface components of the system are implemented in Elm.
Our data persistence strategy depends on our tolerance for volatility, and data ends up in one or more of PostgreSQL, Redis, and Haskell’s native softwaretransactional memory.
Our infrastructure is managed with the Nix ecosystem of tools, meaning our network of machines on AWS run NixOS with immutable file systems, which are built to a declarative specification and deployed atomically with NixOps. New machines can be provisioned and deployed with a single command.
As far as language extensions, I ran the following command to measure our usage:
grep rh '^{# LANGUAGE' .  cut d ' ' f3  sort  uniq c  sort nr
This returned the following results.
102 TemplateHaskell
85 OverloadedStrings
65 TypeFamilies
52 DeriveGeneric
51 GADTs
49 MultiParamTypeClasses
47 RecordWildCards
46 GeneralizedNewtypeDeriving
38 UndecidableInstances
33 FlexibleContexts
28 FlexibleInstances
25 QuasiQuotes
18 DataKinds
16 LambdaCase
16 DeriveAnyClass
15 TupleSections
14 RankNTypes
13 ScopedTypeVariables
5 ConstraintKinds
4 ViewPatterns
3 TypeFamilyDependencies
3 Rank2Types
2 DefaultSignatures
1 TypeOperators
1 TypeApplications
1 StandaloneDeriving
1 PackageImports
1 KindSignatures
1 InstanceSigs
1 DerivingStrategies
1 CPP
If I hadn’t measured, I probably would have forgotten that Yesod does indeed make quite heavy use of Template Haskell to generate code. The core of our application is still only a humble ~17,000 lines across 182 files — not counting some external libraries we maintain — but if you were to dump the splices generated by Template Haskell we would be looking at approximately 160,000 LOC.
It’s worth mentioning also, however, that the numbers above are not truly representative. For example, we use the Persistent library, and we split up all our models into separate files.
We use generics and instance derivation where we can to make the codebase more ergonomic to work with, and some noncontroversial syntax sugar too like RecordWildCards
and LambdaCase
. Nothing too crazy happening here.
We only have one language extension enabled for all modules (and declared in our Cabal file), which is NoImplicitPrelude
. We generally opt for ClassyPrelude
instead.
As for libraries, I think a large chunk of our work leans heavily on lens, aeson, conduit, persistent/esqueleto, safemoney, shakespeare, and hedis.
For this and the following question, I had a chat with my colleagues to try and produce a more representative answer. We feel that Haskell is an excellent match for medium to large sized projects where prototyping speed and robustness are highly prioritised. Our workflow is highly ergonomic, and the refactoring story is best in class meaning it is relatively cheap for us to quickly adapt the software to changing requirements.
Where our technology arguably falls short is that with the combination of libraries and language extensions we use (and I doubt we are a special case here), some parts of the codebase may feel more imperative and less like classic plain Haskell. Of course, any business (and by extension, commercial software project) is going to be full of tradeoffs.
It’s both. Overall we are thrilled with Haskellthetechnology, and also Haskellthecommunity. Almost everyone else in the community has been exceptionally warm, helpful, and generous with their time. I think Haskellthetechnology is also far more pragmatic than it’s typically given credit for. Indeed we originally chose to use it not out of theoretical selfindulgence, but simply because we think it’s cheaper at any time scale.
It’s not all unicorns and roses — we still face some runtime errors (as anyone inevitably will), and the lessthanideal compiler performance sometimes necessitates inconvenient project restructuring. But on the whole, I think we’ve made the right choice and we’re excited to continue in this direction.
We’d like to thank Jezen Thomas for finding the time to talk with us.
If you would like to speak about your industry project that uses Haskell (or any other functional programming language), we’d be pleased to hear about it! Our email is always open: hi@serokell.io.
]]>This is the first post in a series about Lorentz — Haskell eDSL for Michelson smart contracts language. In it, we look at how uncommon Haskell features can be used in real life. The post also might be interesting for people working with smart contracts on Tezos or elsewhere to see how contract language supported by builtin features of a generalpurpose language may look like.
One of the previous posts describes how we have reimplemented Michelson core into Haskell, which gave us an opportunity to parse textual contracts, interpret them and eventually cover them with tests. However, when it came to the need to write our own contracts, it became clear that writing production Michelson code manually is in many senses inconvenient, so we along with camlCase initiated work on Lorentz under Tocqueville Group’s management.
In this post, we are going to describe the very basic features of Lorentz. Bringing a generalpurpose language such as Haskell into Michelson contract development provides extremely broad opportunities that cannot be covered just within a single post, so features like objects, optimizations, contract documentation, typesafe upgradeable types and migrations will be covered in sequels.
Haskell is a language with a pretty powerful type system, it fits quite well for writing eDSLs.
On the one hand, languages with custom syntax and feature set that have their own translator cannot be easily extended by a user when necessary; new features usually appear slowly and may require community approval. In this sense, using one of the existing generalpurpose languages allows users to write arbitrary extensions without modifying the base engine; it enables simpler prototyping because one doesn’t need to write a parser and (ideally) type system drives the user through the process.
On the other hand, generalpurpose dynamically typed languages (like Python), as well as most strongly typed languages (like Java) lack expressive power to provide a user with a fully safe interface. For instance, imagine a macro which takes an object and returns one of its fields by name, will the language ensure that type of returned field is a valid one, or that this field even exists? At runtime — for sure, at compile time — hardly. One can say that we may provide a sort of static analyzer for that, but then we run into all the same problems of special languages.
The previously mentioned post introduces primitives used for reimplementing the Michelson engine in Haskell. Generally, we could write contract code in Haskell using those primitives as they are, but that would be a thankless job. In this section, we are going to describe a way to hide some implementation details from a contract developer thus providing a more natural Haskell API for writing Michelson code.
Lorentz starts from the idea that contract writers should operate with Haskell values, and underneath they will be turned into their Michelson counterparts.
Previously, we introduced datatypes representing Michelson types and values, let us recall how they looked like (with minor changes applied):
data T =
TInt
 TNat
 TOption T
 TList T
 TPair T T
 There is more in actual Michelson
data Val t where
VInt :: Integer > Val 'TInt
VNat :: Natural > Val 'TNat
VOption :: Maybe a > Val ('TOption a)
VList :: [Val t] > Val ('TList t)
VPair :: Val p > Val q > Val ('TPair p q)
Apparently, we don’t want to pass VInt 5
or VPair a b
around, rather it should be possible to use bare 5
and (a, b)
.
So we begin with defining an isomorphism between Haskell and Michelson values for each possible type.
class IsoValue a where
type ToT a :: T
toVal :: a > Val (ToT a)
fromVal :: Val (ToT a) > a
instance IsoValue Integer where
type ToT Integer = 'TInt
toVal = VInt
fromVal (VInt x) = x
instance (IsoValue a, IsoValue b) => IsoValue (a, b) where
type ToT (a, b) = 'TPair (ToT a) (ToT b)
toVal (a, b) = VPair (toVal a) (toVal b)
fromVal (VPair a b) = (fromVal a, fromVal b)
...
Now one can write:
λ> toVal (1 :: Integer, 2 :: Natural)
VPair (VInt 1) (VNat 2)
Actually, there are two different approaches we could take here.
The set of Michelson types is closed, meaning that the user cannot add his own types to it.
We could make it the same in Haskell, then the compiler would always be able to deduce the Haskell type by the Michelson type derived from it (thanks GHC for TypeFamilyDependencies
extension), this would have its benefits.
In contrast, we went about it differently, allowing multiple Haskell types to have the same Michelson representation so that the user could define custom types built from basic primitives. This does not increase expressive power but provides substantial assistance of type system similar to how it happens in a general Haskell code when one defines his custom newtype wrappers and datatypes, as well as opens a venue for other programmers to write plugins to the base engine.
Let us remind that in Michelson one writes code as a sequence of instructions which operate on a typed stack, and the set of allowed instructions is defined as follows:
data Instr (inp :: [T]) (out :: [T]) where
Seq :: Instr a b > Instr b c > Instr a c
Nop :: Instr s s
PUSH :: Val t > Instr s (t : s)
PAIR :: Instr (a : b : s) ('TPair a b : s)
...
Taking into account our IsoValue
typeclass, now we can write code like
withZero :: Instr (ToT Integer : s) (ToT (Natural, Integer) : s)
withZero = PUSH (toVal (0 :: Natural)) # PAIR
 Helper operator
(#) :: Instr a b > Instr b c > Instr a c
(#) = Seq
Constantly calling all these ToT
s and toVal
s is apparently not what we want, and it would be nice to hide this logic within appropriate aliases.
But before starting, let’s make a single observation.
Previously, we assumed that multiple Haskell types should be able to turn into the same Michelson type, so ToT
is not an injective type family.
This means that when one writes ToT Integer
, information about the original Integer
type gets immediately forgotten and type checker only knows about the resulting TInt
.
Thus, for instance, if we remove :: Natural
part in the code snippet above, GHC won’t be able to guess from the function signature that 0
is of type Natural
— it only sees that type of toVal 0
is ToT Natural
and that is too ambiguous.
How do we fix the original types? We declare a datatype!
Let’s illustrate this for a simplified case. Say we have a datatype declared as follows:
newtype MyType a = MyType Int
It is a wellknown fact that MyType a
and MyType b
are treated as different types despite having the same representation (and in such case MyType
is said to have a phantom type parameter):
convert :: MyType () > MyType Int
 This does not work.
convert a = a
 We have to recreate our type from scratch.
convert (MyType a) = MyType a
So datatypes (and newtypes) fix their type arguments up to the moment when they are deconstructed.
The same technique can be used for fixing Haskell types lying on stack.
We declare a newtype wrapper over Instr
type and then mirror all Michelson instructions in the new representation.
infix 1 :>
newtype (inp :: [Type]) :> (out :: [Type]) =
I { unI :: Instr (ToTs inp) (ToTs out) }
type family ToTs (ts :: [Type]) :: [T] where
ToTs '[] = '[]
ToTs (x ': xs) = ToT x ': ToTs xs
 * Mirrored instructions
(#) :: (a :> b) > (b :> c) > (a :> c)
I l # I r = I (Seq l r)
push :: IsoValue t => t > (s :> t : s)
push = I $ PUSH (toVal a)
pair :: a : b : s :> (a, b) : s
pair = I PAIR
Now we can smoothly write contract code as if Michelson was naturally designed in Haskell:
withZero :: Integer : s :> (Natural, Integer) : s
withZero = push 0 # pair
What we effectively got after all this work:
Our initial assumption about the open set of types allows defining custom newtypes and datatypes on top of the fixed set of Michelson types. We will come back to this later someday.
It is not possible to apply an instruction accepting a stack of type [x]
to a stack of type [y]
even if x
and y
have the same Michelson representation. I.e. our typesystem does not allow implicit casts, which seems good and quite Haskellish.
Error messages prompted by the compiler on type mismatches will now also mention Haskell types, not the Michelson ones — the latter would indeed be quite confusing for contract developers.
For the rare cases when developer knows what they are doing, we should supply a function for explicit casts.
type MichelsonCoercible a b = ToT a ~ ToT b
coerce_ :: MichelsonCoercible a b => (a : s) :> (b : s)
coerce_ =
 under 'I' constructor Haskell types are not fixed, so this works
I Nop
That’s it.
Note that these Nop
s are eliminated upon translating code to Michelson.
As our practice shows, such eDSL should have at least a primitive optimizer for various cases anyway, otherwise, the user constantly would have to choose between writing clear code with macros, reusable functions e.t.c and micro optimizations.
Actually, our coerce_
method semantically takes the middleground between Haskell’s coerce
and unsafeCoerce
, so this naming is a bit unfortunate.
It is not exactly coerce
, because Coercible
constraint in Haskell tends to preserve type invariants.
A known example is Set a
— one cannot simply call coerce :: Set a > Set (MyNewtype a)
because that may break inner Set
structure in case MyNewtype
modifies Ord
instance.
Also, if some newtype has invariants which may break on its type argument change, and coercions are used in code extensively, it seems like a good practice to assign type roles to type arguments (in order to prohibit undesired arguments changes via coercion) and to hide newtype constructor (to avoid wrapunwrap coercions) when necessary.
This way we can sleep well knowing that coerce
s scattered across the code never cause a bug.
So unlike Haskell’s coerce
, our coerce_
is less safe because it can violate type invariants.
On the other hand, it is not as unsafe as unsafeCoerce
because it never segfaults nor produces invalid contracts.
Eventually, we stopped at forcedCoerce_
name for that function.
A proper way to implement safe coerce_
is yet under discussion.
An obvious solution would be to additionally require Haskell’s Coercible
in it, but (taking the specificity of Lorentz use cases into account) this solution has its own substantial drawbacks.
We will return to this topic in one of our next posts.
Haskell syntax is very flexible and concise, it fits pretty nice for writing eDSLs. Further, we consider how its features allow reaching the same quality of syntax as in many nonembedded DSLs (like Michelson itself) and even better.
Michelson supposes the use of a simple typechecker where type information goes strictly from the beginning of code to its end.
This means, that while some instructions like CAR :: (a, b) : s > a : s
can deduce type of result by themselves, other instructions like PUSH t (v :: t) :: s > t : s
or LEFT b :: a : s > or a b : s
always require specifying type explicitly.
Not so in Haskell. GHC typechecker performs generic unification thus being able to deduce type information whenever it can be “guessed”. For instance:
code1 :: s :> Integer : s
code1 = push 5
 ^ Type of pushed constant can be derived from expected result type
code2 :: IsoValue b => a : s :> Either a b : s
code2 = left
 ^ And same here, we only require `IsoValue b` as a proof of that
 `b` turns into an actual Michelson type
A common case when we still have to prompt types to typechecker via the dedicated TypeApplications
extension is pushing numbers: Michelson has separate int
and nat
types, and in code like push 1 # push 2 # sub
it’s not clear which types are meant because both numeric literals and sub
instruction are polymorphic.
In such cases, Haskell allows us to pass optional annotations which clarify types, for instance, push
instruction allows specifying a type of value to be put:
push @Integer 1 #
push @Natural 2 #
sub
Use cases for type clarifications are not limited to pushing numbers, they can be used to ensure readability. For instance,
drop @()
specifies developer’s intentions way clearer than simple
drop
and this may be a boon when delving into large blocks of code.
Also, we can declare an instruction which fixes the current stack:
stackType :: s :> s
stackType = nop  that was trivial
code =
...
stackType @(Integer : Integer : _) #
add #
applyCoefficients #
stackType @(Natural : _) #
...
Aside from serving as a hint for a code reader (profit of that for realworld contracts cannot be overestimated), this significantly helps in locating typecheck errors when code is being modified.
Previously we introduced #
operator as a glue between instructions, but using it constantly is annoying.
Fortunately, most of Haskell’s sugar can be overloaded to work with user functions via RebindableSyntax
extension.
In our case, we would like to exploit do
blocks syntax.
Apparently, our instruction type :>
cannot be an instance of Monad
, so by default, it is not possible to make instruction a direct part of the do
block.
To deal with this, we define our custom >>
operator.
(>>) :: (a :> b) > (b :> c) > (a :> c)
(>>) = (#)
Now one can write
{# LANGUAGE RebindableSyntax #}
code = do
push @Integer 5; dup
dip $ do
swap
sub
...
...
What about return
and >>=
from the Monad
typeclass?
The former is not necessary for do
blocks to work; the latter is used for arrow syntax (a < instr
), and in the given stack machine language we didn’t manage to find a practical use case for it.
A bit of polishing at the last:
XApplicativeDo
extension is enabled, do
blocks tend to use fmap
, pure
and <*>
functions whenever it is possible not to fall back to >>=
and >>
. We definitely cannot implement those sanely for our instruction type, so this extension has to be disabled.do
block, “returns” something but its “result” as of “action” is not used. Thus Wunuseddobind
warning should be disabled as well.There might be an unexpected profit from Wunuseddobinds
though: it’s a common wish to be able to know the current stack at a given instruction, and given extension allows viewing those as a set of warnings. Works especially good with IDE showing errors/warnings at the cursor position.
In Michelson, IF
like instructions accept clauses as operands and pick predicate from the stack.
For instance:
PUSH bool True;
IF { /* Do one thing */ } { /* Do another thing */ };
or
PUSH (or int nat) (Left 5);
IF_LEFT { /* Action for Left case */ } { /* Action for Right case */ };
This differs from most highlevel languages where if
accepts condition as expression.
In Haskell, it’s possible to redefine the semantics of if ... then ... else ...
construction via declaring alternative ifThenElse
function, though it still should accept 3 operands.
To fit in our case, we implement if
as something intermediate between common if
and conditional jumps from assembly language.
As the first operand, we will accept the type of condition — it may be checking a boolean value, comparing two values on stack or patternmatching on or
value.
Since the type of stack accepted by if
clauses will depend on the condition type, we define this condition type using GADTs.
data Condition arg argl argr where
Holds :: Condition (Bool : s) s s
IsNone :: Condition (Maybe a : s) s (a : s)
IsLeft :: Condition (Either l r : s) (l : s) (r : s)
IsZero :: ArithOpApplies Eq' a => Condition (a : s) s s
IsEq :: ArithOpApplies Eq' a => Condition (a : a : s) s s
...
ifThenElse
:: Condition arg argl argr
> (argl :> o) > (argr :> o) > (arg :> o)
ifThenElse = \case
Holds > if_
IsNone > ifNone
IsLeft > ifLeft
IsZero > \l r > eq # if_ l r
IsEq > \l r > compare # eq # if_ l r
...
It would also be possible to define an open set of conditions via typeclass instead of GADT if necessary.
Summing up, at this moment we can write the following code:
push @Integer 5; push @Integer 3
if IsEq
then do
 Action
else do
 Another action
When working with a flat stack of unnamed variables, it sometimes gets difficult to understand what currently lies on it and in which order.
We resolve this problem by (ab)using the named
package.
In particular, we make use of its primitives for attaching names to types, it looks like "var" :! Integer
.
The code which provides support for them on Lorentz field is limited to:
import Data.Vinyl.Derived (Label)
instance IsoValue (name :! a) where
type ToT (name :! a) = ToT a
...
  Attach a label to the variable at the stack top.
toNamed :: Label name > (a : s :> (name :! a) : s)
toNamed _ = coerce_
  Remove a label from the variable at the stack top,
 expecting it to have the given name.
fromNamed :: Label name > ((name :! a) : s :> a : s)
fromNamed _ = coerce_
And now one can write:
withdraw = do
...  get balance
toNamed #balance
...  get withdrawn amount
toNamed #withdrawal
swap
substractTokens
...
substractTokens :: ["balance" :! Natural, "withdrawal" :! Natural] :> [Natural]
Here we used #
syntax coming from the OverloadedLabels
extension which allows fixing a typelevel string with a termlevel literal (though its use cases are broader than that).
We don’t provide any automatic ordering of stack variables when calling a function because Lorentz is a lowlevel language.
Rather these names are a safety measure and developer is still responsible for proper stack management like calling swap
in the example above (or contrary, he may want to swap the whole computations for the sake of more optimal code).
The next observation here is that we have several arithmetic and comparison instructions that accept two entities of the same type and for which order matters. Visually ensuring that each such instruction is used properly is tiresome, and doing that again on each refactor is tiresome doubly. It would be nice to check via the type system that upon calling those instructions we have the expected variables on the stack.
checkHasEnoughTokens :: [Natural, Storage] :> [Storage]
checkHasEnoughTokens = do
dip getCurrentAmount
stackType @[Natural, Natural, Storage]
if ???  IsGe or IsLe?
then nop
else fail
We can make sure that upon calling if
block variables are named, but that is still inconvenient when if
itself accepts unnamed variables.
Such unpleasantness can be fixed neatly by adding appropriate condition types:
data Condition arg argl argr where
...
IsLeNamed
:: Comparable a
=> Label n1 > Label n2
> Condition ((n1 :! a) ': (n2 :! a) ': s) s s
IsGeNamed
:: ...
 * Aliases
(<=.) = IsLeNamed
(>=.) = IsGeNamed
So now the example above can look like this:
checkHasEnoughTokens :: [Natural, Storage] :> [Storage]
checkHasEnoughTokens = do
toNamed #withdrawn
dip $ do getCurrentAmount; toNamed #balance
stackType @[_ :! Natural, _ :! Natural, Storage]
if #withdrawn <=. #balance
then nop
else fail
Now misordering stack operands will lead to a type error.
To get a clue on how this can help at production scale, you could imagine a dozen instructions inserted between toNamed
calls and if
, along with one more Natural
present on the stack from the beginning.
It sounds like a good thing to similarly update all other instructions like sub
and div
.
On the other hand, this solution can be considered as only partial because the resulting boilerplate has its costs.
We are nearing the end of this post, and in the last section let’s consider the fact that some of the Michelson types have an expectedly limited definition domain.
Values of types nat
or mutez
(the underlying currency) cannot be negative, string
s allow only some ASCII characters.
Once we are used to writing typesafe code, it’s frustrating to find out that we cannot safely construct values.
 something went wrong here
push @Natural (1)
 such value is also not valid
push @Text "A text\0"
Let’s deal with each of the mentioned types in turn.
For Natural
case, GHC prompts a warning saying that 1
literal is not valid for this type, so everything is fine here.
But at the moment of writing this post, such checks are hardcoded in GHC and apply only to some primitive types, so replicating this behaviour on Mutez
does not seem feasible.
We workarounded it as follows: Word
has a strictly smaller definition domain than Mutez
, and at the same time it is one of those types for which GHC can prompt the mentioned warning.
So we added a function toMutez :: Word > Mutez
, and now one can safely write
push (toMutez <num literal>)
which in most cases is enough.
Approaching string literals requires a different solution.
IsString
typeclass allows performing only runtime checks, so it does not suit our purposes.
Typelevel analysis of Symbol
also seems complicated at the current stage of GHC existence.
Eventually, we went with QuasiQuotes
, they exactly allow performing arbitrary parsing at compile time.
Definiing a custom quasi quoter is as simple as
import qualified Language.Haskell.TH.Quote as TH
 @mt@ for "Michelson text"
mt :: TH.QuasiQuoter
mt = TH.QuasiQuoter
{ TH.quoteExp = \s >
case parseQuasiQuoterText s of
Left err > fail $ toString err
Right txt > [e toText @String txt ]
, TH.quotePat = \_ > fail "Cannot use at pattern position"
, TH.quoteType = \_ > fail "Cannot use at type position"
, TH.quoteDec = \_ > fail "Cannot use at declaration position"
}
The unpleasant thing with quoters is that some find them cumbersome to type and, sometimes, read:
push [mtSome text here] # push [mt... and some here]
Though, indeed, this depends on the amount of support (syntax highlighting and snippets) a particular IDE provides to a developer.
One more problem is that a quite widespread but outdated haskellsrcexts
library fails to read the entire module if this module has uses of quasi quotes, thus they can interfere with existing code management workflows. Quoting the reaction of Ivan Gromakovskii:
Apparently, this
mt
thing exists only to circumvent checks performed by hlint.
So in cases when correctness is likely and can be easily verified by tests, it might be worth thinking carefully before introducing compiletime checks this way.
In this post, we have described the foundation of Lorentz, including common syntax, primitives and funny Haskell constructions involved. With the addition of some other features like objects, it has been successfully used for developing several production contracts and the accompanying tooling. Some public contracts written on Lorentz can be found in the Morley repository.
As one can guess, writing realworld contracts on a stackmachine language is a pleasure below average, and now we are working on fleshing out once prototyped highlevel language over Lorentz called Indigo. In the next posts, we are going to describe the essence of Indigo and touch advanced features used both in Lorentz and Indigo.
Stay with us!
ﾍ(=^･ω･^= )ﾉ
]]>And a year later, I’m happy to report that I’ve made some good progress in this direction, with help and guidance from Simon Peyton Jones and Richard Eisenberg. But for me this is more than just work, it’s a personal journey. I always found compiler engineering interesting, and it’s extremely fulfilling to work on something I like.
That’s why I’m eager to share it with everyone who would listen, or, even better, get involved. For example, I put together https://ghc.dev/, a cheatsheet to help anyone get started with contributing to GHC. So if you ever feel like adding a GHC feature, especially one that is related to dependent types, and don’t know where to get started, one option is to start by dropping me a message on Twitter.
But this line of work is not trivial. Like other areas of software engineering, working on a compiler is challenging and requires thinking deeply about hard problems. That’s why it helps immensely to have the opportunity to immerse yourself into the problem space, to work on this full time at Serokell. What follows is a small dive into the thoughts and opinions of a randomly sampled GHC contributor, i.e. me :) Read on if you’re interested in what sort of person ends up working on a Haskell compiler and to see some of the latest results of improving GHC.
My GHC activities fall into two categories: social and technical. The social activities are about discussing language design, and going through the GHC proposal process to get my preferred design decisions accepted. In that regard, I (co)wrote several proposals in 2019:
In general, I’m trying to nudge Haskell in a direction that brings it closer Dependent Haskell. I have outlined my vision in a metaproposal, #236 Grand Class Unification. I also routinely participate in the discussion of proposals by other people, as long as they are relevant to my work. The technical activities involve implementing the designs that were accepted by the community and also internal improvements. That’s the part I prefer because I’m first and foremost a software engineer, not a community manager. Here are the things I managed to get upstream in 2019:
%shift
pragma.forall
is now always a keyword in types.(.)
is now a valid type operator.reifyType
.(!), (~), ($), ($$),
and (@)
.{# SCC ... #}
annotations.*
kind syntax in the prettyprinter.Also, I made a few minor improvements to the test suite, removed dead code here and there, and did other small refactorings of the “while I’m in town” nature. You can scroll through my commits to see the exact changes: https://github.com/ghc/ghc/commits?author=intindex
As to the plans for 2020, I was happy to learn that Artem Kuztensov would join my team on a permanent basis. My most optimistic estimates are that in the third quarter of the year we will be done with the term/type unification in the parser and the renamer, and then we can focus on the type checker.
I’m an idealist, and I believe that a perfect programming language with perfect tooling (or, at least, Paretooptimal language and tooling) can exist. This is in contrast to people who just see languages as tools to get the job done. For them, a language is probably as good as the libraries available for their tasks. For me, a programming language is an instrument of thought, and I’d like this instrument to be as versatile as possible. But, of course, I dare not claim to know what a perfect language would look like.
At this stage of my life, I’m just trying to figure out what makes a language good. It’s quite clear that static types are important to guarantee correctness. And it’s also clear that the type system must be very powerful (including dependent types) to express any interesting properties. However, it is less clear how to design a type system that not only has all the cool features, but also an efficient type checker. There are also many other concerns besides correctness, such as runtime performance, distributed computation, modularity, and so on.
That’s why I enjoy working on GHC so much. Not only I’m contributing to an important open source project, but I’m also constantly learning about the tradeoffs of language design and production grade compiler engineering techniques. That’s also why my other project is a structurebased code editor: the goal is to find out if a new generation of programming languages can provide a better user experience by abandoning some core assumptions (e.g. that code is text), and what exactly this user experience should be.
My hope is that in a few decades this will culminate in a clear design document for the programming environment of the future. So, to summarize, my dream project is to revolutionize programming, to enable ordersofmagnitude higher developer productivity by means of superior language design and superior tooling.
GHC finds itself in a remarkably difficult position. Haskell is used by people with very different background and goals:
Teaching. Haskell is the natural starting point to teach functional programming to students. Algebraic data types, pattern matching, pure functions, higher order functions, lazy evaluation, and parametric polymorphism, are all very important concepts, and Haskell has them all. And you know the one thing that teachers dislike? It’s when their teaching materials go out of date and they can’t point students to a rich body of literature because all of it is subtly incorrect due to recent breaking changes.
Industry. Haskell is memory safe, compiles to native code, offers a good threaded runtime, and has a powerful type system. It encourages immutable data and pure functions. It makes it easy to express complicated control flow using lazy evaluation and higher order functions. Michael Snoyman goes into more detail in his article “What Makes Haskell Unique”. All in all, the features of Haskell make it a great choice for writing applications. And you know the one thing that industrial users dislike? It’s when their 100k LOC codebase takes ages to compile.
Research. Haskell attracts the sort of people who are openminded about new features, even if there are sometimes loud opposing voices. Today’s dialect of Haskell implemented by GHC is light years ahead of standardized Haskell, featuring GADTs, data type promotion, type families, template metaprogramming, various deriving mechanisms, unboxed data types, multiparameter type classes with functional dependencies, higher rank universal quantification, and more. Many of those features have imperfect design and imperfect implementation, but such is the nature of research. And mark my words, sooner or later Haskell will also get dependent types, linear types, row polymorphism, refinement types, and whatnot. And you know the one thing that researches dislike? It’s backwards compatibility, which makes it extremely hard to design and implement new features.
How can GHC developers balance these concerns? How do we add new features without breaking the teaching materials too much and without making the compiler too slow? For that, we need to be inventive. We must find solutions that are good for the community as a whole. And that’s why the GHC proposal process exists. Sure enough, it helps when more people are coming up with new ideas and find flaws in the existing ones. I have learned a lot just by reading the discussions there.
On the other hand, it puts proposal authors under a lot of stress. It is hard to present a feature in a compelling way. Sometimes good ideas get abandoned because their authors are not good at selling those ideas.
Good question. As I said, GHC’s dialect of Haskell is much more advanced than the language standard. Why don’t we incorporate all of these features into the standard? As I also said, these features have imperfect design and imperfect implementation. It would be a shame to standardize something flawed. And I don’t think it would serve any purpose either. If there were competing Haskell implementations, then the standard could be useful to describe the portable subset of the various dialects. But there are no competing Haskell implementations, and there’s no market for them. The effort is better spent on improving GHC. Therefore, I don’t believe there’s a need for a new standard.
There are definitely languages that are better than Haskell in some particular aspects. For example, PureScript has row polymorphism, which Haskell does not have. Agda has dependent types, and Haskell has not caught up yet. Mercury has linear types. However, I’m not aware of a language that would be better than Haskell in many ways rather than a few specific ones. So I’d say that overall, Haskell is the best general purpose language out there at the moment, but it must evolve continually to stay in this spot, or else it will be outcompeted.
––––––
Vladislav Zavialov is our Haskell developer and GHC contributor. If you are interested in other Vlad’s materials, you can read his article about Dependent Haskell and follow him on Twitter. Thank you, Vlad, for such a great interview and for working with us!
]]>In the first installment, we got the opportunity to interview Ashesh Ambasta from CentralApp, a tool that enables you to manage your company’s visibility on the Internet: website, reviews, social media, and search engines.
Tell us about your company, your team and your own role there.
My name is Ashesh, and I’m a cofounder and the CTO of CentralApp. I work in the backend development team, and my role entails:
CentralApp is a SaaS platform aimed at SME’s. Our goal is to ensure people’s online presence is in sync: that is, their information on the social platforms is constantly up to date and their website is up to date (both in terms of their information and in terms of the latest trends in the web space). We also have a third parallel of the product in which we let the customers communicate directly with their customers: either through reviews on social platforms or via their website.
What were the biggest challenges you’ve overcome while working at this project?
CentralApp is a large system: the product in itself is large and complex. It requires multiple subsystems working together to achieve the product’s functionality as a whole. Besides, having almost 1500 unique websites currently hosted on our platform means we constantly have to deal with a high throughput system where performance is paramount.
In general, the hardest problem we’ve had to solve is to keep our code base clean, maintainable and functional. We’re also a small development team, so we need to ensure we’re effective in keeping the business requirements going and sustaining the product.
How did Haskell help you to solve them?
Haskell has obvious advantages. It is a statically typed, functional and pure programming language that usually results in performant code. Haskell’s advanced type system lets us model complex business requirements in types: thereby removing a large area of friction for developers. The type system works with you.
Besides that, being pure, Haskell lets us isolate effectful code and reduce the chance of transient failures across our code base. That also allows for easy refactoring.
Could you describe your technology stack?
What made you switch from Scala to Haskell?
That was more a matter of preference: I was the lone developer back then and I personally liked the syntax of Haskell more. From a more rational perspective: I liked the fact that Haskell was much less verbose than Scala and type inference in Haskell is ages ahead of that of Scala. And moreover, Haskell seems to be at the cutting edge of typesystem research which I find appealing: we use libraries like Servant for all of our API’s.
Which Haskell language extensions / libraries do you tend to use most often?
Libraries:


Extensions:


Where is Haskell great to use and where does it fall short in your stack?
Great:
Falls short:
Are you satisfied with the result or do you still have some difficulties?
We’re quite satisfied with what we have so far.
Any key takeaways?
We would like to thank Ashesh for participating in this interview. If you are interested in the project, you can follow him on Twitter: @AsheshAmbasta or follow CentralApp on LinkedIn.
If you would like to talk about your own app, service or any other project that uses Haskell, we’d be pleased to hear about it! You are welcome to write to us: hi@serokell.io.
]]>In the first part, we introduced the reader to basic modal logic. We discussed Kripke semantics, some of the popular systems of modal logic. We also observed how to prove the decidability of modal logics using such tools as minimal filtration and its transitive closure. Here we observe use cases and take a look at connections of modal logic with topology, foundations of mathematics, and computer science. Finally, we recommend the literature for further reading and study.
Topology is the branch of mathematics that studies geometric objects such as planes, surfaces, and their continuous deformations. The notion of a continuous deformation might be illustrated with an example. Suppose you have a cubeshaped piece of plasticine. You deform that piece into the sphere continuously, i. e., keeping your hands on that piece all the time:
Topology study geometric structures up to homeomorphism. A homeomorphism is a onetoone continuous transformation as in the example above.
The abstract definition of a topological space is settheoretical. A topological space is defined as a family of subsets. More formally, a topological space is a pair $\mathcal{X} = \langle X, \tau \rangle$, where $X$ is a nonempty set and $\tau$ is a family of subsets of $X$ with the following data:
The most common practice is defining a topological structure via a base of topology. A base of topology is a family of opens such that every open set in this space has the form of union of some elements from the base. We consider instances of a base within examples of topological spaces.
Examples of topological spaces are:
The topologies above are trivial ones. Let us consider a slightly more curious example.
Let $\langle X, \rho \rangle$ be a metric space. By metric space, we mean a set equipped with a distance function $\rho : X \times X \to \mathbb{R}_{\geq 0}$ that every pair of points maps to some nonnegative real number, a distance between them. A distance function has the following conditions:
One may topologise any metric space uniformly. Let $\varepsilon$ be a positive real number and $x \in X$. An open ball of radius $\varepsilon$ and centre $x$ is the set $U_{\varepsilon}^x = \{ y \in X \:  \: \rho(x, y) < \varepsilon \}$.
To visualise the notion of an open ball, just imagine you take a cricket ball. An open ball derived from a given cricket ball is just the content, if we assume that its content is restricted by its leather cover. The centre of a cricket ball is a centre in the usual Euclidean sense. The radius of such an open ball approximately equals to 3.5 centimetres according to International Cricket Council data.
The set of all open balls in this metric space forms the base of topology. Thus, any metric space is a topological one. Moreover, topological spaces historically arose as the generalisation of metric spaces at the beginning of the previous century.
The example of a metric space is real numbers with the distance function defined with the absolute value of subtraction. The second example is a normed vector space, a vector space equipped with a norm function, a generalisation of a vector length. One may produce a metric space from a normed vector one by defining the distance between two vectors as the norm of their subtraction. Such a metric is often called the Minkowski distance. We don’t consider the examples above in detail since those topics are closer to functional analysis and pointset topology rather than modal logic.
Let us motivate interior and closure as follows. Suppose we have a snake on the Euclidian plane $\mathbb{R}^2$. Unfortunately, this snake ate an elephant and lost its fullcontour line after that:
A closure operator on the Euclidean plane helps it to restore the lost part of a boundary:
We would like to identify the other regions of this snake. An interior of the snake is its container. The last sentence is not meant to be taken literally in the anatomical sense:
The boundary of a snake is its full contourline without the interior. Here we are not interested in the rich inner world of our snake, only in its external borders. Just imagine that you chalked the snake:
The exterior of a snake is the whole plane without this snake itself. An exterior operator allows the snake to define strictly what an external world is:
Here, the snake is the subset of plane $\mathbb{R}^2$, namely, $S$. Let us denote its closure as $\operatorname{Cl}S$. $\operatorname{I}S$ is its interior. The boundary of a snake is the closure of the snake minus its interior, $\operatorname{Cl} S \cap  \operatorname{I} S$. The exterior of this snake is an interior of its complement. Here the basic notions are interior and closure. Let us move on to stricter definitions:
Let $\mathcal{X} = \langle X, \tau \rangle$ be a topological space and $A \subseteq X$ a subset of $\mathcal{X}$. A closure of $X$ is the smallest closed set that contains $X$. More precisely:
$\operatorname{Cl}(X) = \bigcap \{ A \in \operatorname{Closed}(\mathcal{X}) \:  \: X \subseteq A \}$
A closure operator satisfies the following properties that also knows as Kuratowski’s axioms:
$\operatorname{Cl}(\emptyset) = \emptyset$, empty set is closed. $A \subseteq \operatorname{Cl}A$, any set is a subset of its closure. $\operatorname{Cl}A = \operatorname{Cl}\operatorname{Cl}A$, you don’t need to apply closure operator twice, it’s idempotent. $\operatorname{Cl}(A \cup B) = \operatorname{Cl}A \cup \operatorname{Cl}B$, a closure operator distributres over finite union.
Very and very roughly speaking, a closure operator transforms any subset to a closed one. A dual operator is an interior. An interior of a subset $X$ is the biggest open subset of $X$: $\operatorname{I}(X) = \bigcup \{ A \in \tau \:  \: A \subseteq X \}$.
It is not so hard to show that $\operatorname{I}(X) =  (\operatorname{Cl}( X))$ since an open set is a complement of a closed one and vice versa. An interior operator satisfies the following conditions: $\operatorname{I}X = X$ $\operatorname{I}A \subseteq A$ $\operatorname{I}A = \operatorname{I}\operatorname{I}A$ $\operatorname{I}(A \cap B) = \operatorname{I}A \cap \operatorname{I}B$
To observe the analogy between Kuratowski’s closure axioms and the logic ${\bf S}4$, let us take a look at the equivalent formulation of ${\bf S}4$. We recall that ${\bf S}4$ is a normal modal logic that extends ${\bf K}$ with reflexivity and transitivity axioms:
First of all, we define a normal modal logic as the set of formulas that consists of all Boolean tautologies, the following two formulas:
The reader might check that this definition of normal modal logic is equivalent to the definition we introduced in the first part as an exercise.
The minimal normal modal logic is the least set of formulas that satisfy the previous conditions. Such a logic is deductively equivalent to ${\bf K}$ defined in the previous post. Then ${\bf S4}$ is an extension of the minimal normal modal logic with reflexivity and transitivity axioms, more explicitly:
As the reader could see, the ${\bf S4}$ axioms are the same as the Kuratowski’s closure axioms if we read $\operatorname{Cl}$ as $\Diamond$. Dually, we represent ${\bf S}4$ logic with boxes and observe the analogy with the Kuratowski’s interior axioms, if we read $\operatorname{I}$ as $\Box$:
We may study such an analogy considering topological models (or topomodels) in which we define the truth condition for modalities via open and closed sets in a given topological space.
We discussed Kripkestyle semantics based on binary relations before. The idea of topological models is the same, but we replace Kripke frames with topological spaces and modal formulas are understood in terms of closed and open sets.
Let $\mathcal{X} = \langle X, \tau \rangle$ be a topological space and $\vartheta : \operatorname{PV} \to 2^{X}$ be a valuation. A topological model is a triple $\mathcal{M} = \langle \mathcal{X}, \vartheta \rangle$ with the following thurh conditions. Here we assume that $\neg$, $\lor$, and $\Box$ are primitive connectives:
Let us define that valuation of an abritrary formula that follows from the semantics above:
As in Kripke models, a valuation function maps each variable to some subset of an underlying set. From a naive geometric intuition, a valuation function determines some region on a space. If we return to our example with the snake on a plane, then we may, for example, require that $\vartheta ( p ) = S$. The items 23 are the same as in Kripke models. The truth condition for $\Box$ is completely different. Logically, this condition declares that $\Box \varphi$ is true at the point $x$ if $\varphi$ is locally true. It means that the proposition $\varphi$ is true at each point in some region of an observed space.
As we remember, $\Diamond = \neg \Box \neg$. Thus, the possibility modality has the following truth condition in a topological model:
$\mathcal{M}, x \models \Diamond \varphi \Leftrightarrow$ for each open neighbourhood $U$ such that $x \in U$ there exists $y \in U$ such that $\mathcal{M}, y \models \varphi$. In other words, there exists a point in every open neighbourhood $U$ of $x$ at which $\varphi$ is true. This definition induces the closure of $\varphi$, the set of all points at which $\varphi$ is true: $\Diamond \varphi = \neg \Box \neg   (\operatorname{I} ( \varphi)) = \operatorname{Cl}(\varphi)$
Also, we will use the same notation as in Kripke semantics: $\mathcal{M} \models \varphi$ iff $\varphi$ is true at every point. $\varphi$ is valid in a topological space iff it’s true in every model on this space. The logic of a topological space $\mathcal{X}$ is the set of valid formulas in this space. The logic of class of topological space is an intersection of logics.
In Kripke semantics, the logic ${\bf K}$ was the underlying one, ${\bf K}$ is the logic of all Kripke frames. The same question for topological spaces is quite natural. McKinseyTarski theorem claims that ${\bf S}4$, the logic of all preorders from a Kripkean perspective, is the logic of all topological spaces. Let us discuss the soundness theorem first. Moreover, any extension of ${\bf S}4$ is complete with respect to some class of topological spaces.
Theorem Let $\mathbb{X}$ be a class of all topological spaces, then ${\bf S}4 = \operatorname{Log}(\mathbb{X})$
Proof Let us prove that ${\bf A}4 = \Box p \to \Box \Box p$ is valid. Let $\mathcal{X}$ be a topological space and $\vartheta$ a valuation. Let $\mathcal{M}, x \models \Box p$. Then there exists an open neighbourhood $U_x$ such that for each $y \in U_x$ $\mathcal{M}, y \models p$. $y \in U_x$, then $U_x$ is also open neighbourhood of $y$. Thus, $\mathcal{M}, y \models \Box p$ and $\mathcal{M}, x \models \Box \Box p$.
To prove the converse inclusion, one needs to perform the same thing as in the case of Kripke semantics. The set of all maximal ${\bf S}4$consistent sets forms a topological space. So, one can build a canonical topological model, a topomodel on the set of all maximal ${\bf S}4$consistent sets with canonical valuation. We refer the reader to the paper called Reasoning About Space: The Modal Way written by Marco Aiello, Johan van Benthem, and Guram Bezhanishvili, where the topological completeness of ${\bf S}4$ is shown quite accurately. Thus, ${\bf S}4$ is the logic of all topological spaces.
Some might say that this is result is too abstract. We would like to take a look at a more precise classification of topological spaces with modal logic. Modal logic is able to provide an invariant for topological spaces, but, frankly speaking, it’s quite weak. Let us observe, however, what had already been done in topological semantics of modal logic. In other words, we discuss how we can particularise the class of topological spaces preserving McKinseyTarski theorem.
Let $\mathcal{X}$ be a discrete space, then $\operatorname{Log}(\mathcal{X}) = {\bf K} \oplus \Box p \leftrightarrow p$. This axiom claims that we may box and unbox everywhere. From a topological point of view, it denotes that the interior operator is the trivial one. That is, any subset is open. All modalities fade away since the interior operator is merely the identity function on subsets.
Let $\mathcal{X} = \langle X, \tau \rangle$ be an infinite antidiscrete space, that is, only $X$ and $\emptyset$ are open, then ${\bf S}5 = \operatorname{Log}(\mathcal{X})$. As we told in the first part, ${\bf S}5 = {\bf S}4 \oplus p \to \Diamond \Box p$. The last axiom expresses symmetry of a relation. The symmetry formula also has the equivalent form $\Diamond \Box p \to p$.
The truth condition for $\Box$ in models on antidiscrete spaces transforms as follows. $\Box \varphi$ is true at the point $x$, if there exists an open neighbourhood $U_x$ such that $\mathcal{M}, y \models \varphi$. In an antidiscrete space, there is only the one open neighbourhood for every point, the space itself. That is, in an antidiscrete space, $\mathcal{M}, x \models \Box \varphi$ iff $\mathcal{M}, y \models \varphi$ for each $y \in X$. It’s not hard to check that ${\bf AB}$axiom is valid on every infinite antidiscrete space. Let $x \in X$ and $\mathcal{M}, x \models \Diamond \Box \varphi$. Then for every open neighbourhood $U_x$ there exists the point $y$ such that $\mathcal{M}, y \models \Box \varphi$. But there is only one open neighbourhood of $x$, the space itself. Thus, $\mathcal{M}, x \models \varphi$.
Let $\mathcal{F} = \langle W, R \rangle$ be a preorder, that is, ${\bf S}4$frame. One may induce a topological structure on a preorder if we take also upper sets as opens. A set $A \subseteq W$ is called upper if $x \in A$ and $x R y$ implies $y \in A$. In this topology, any intersection of opens is open, or equivalently, the interior operator distributes over an arbitrary intersection of subsets. Such a space is the instance of Alexandrov space. This connection claims that topological semantics for modal logic generalises Kripkean semantics for ${\bf S}4$ and its extensions.
${\bf S}4$ is also the logic of the class of all metric spaces as it was proved by McKinsey and Tarski. Rasiowa and Sikorski showed that ${\bf S}4$ is the logic of all denseinitself metric spaces, that is, metric spaces that contain no isolated points. Here we may claim that modal logic provides invariants for topological and metric spaces, but such invariants are too rough if we have closure and interior modalities. The roughness of these invariants follows from the fact that all topological spaces, all metric spaces and all denseinitself metric spaces have exactly the same logic and those spaces are indistinguishable from a logical point of view.
We refer the reader to the paper by Guram Bezhanishvili, David Gabelaia, and Joel LuceroBryan to become familiar with more precise classification of modal logics of metric spaces.
Note that interior and closure are not the only topological modalities. There are also a socalled graduated, tangled, difference, and other modal operators that are of interest from a topological perspective. Such extensions of modal language allow one to study topological and metric spaces from a logical perspective much more faithfully. You may read the recent paper by Ian Hodkinson and Robert Goldblatt called Strong completeness of modal logics over $0$dimensional metric spaces, where those alternative modalities are overviewed quite clearly. Also, you may read the paper Some Results on Modal Axiomatisation and Definability for Topological Spaces by Guram Bezhanishvili, Leo Esakia, and David Gabelaia to understand the expressive power of socalled derivation modality that generalises closure in some sense. Let us observe briefly a universal modality as an example of such an additional modality.
The universal modality is denoted as $[\forall]$ and has the following semantics: $\mathcal{M}, x \models [\forall] \varphi \Leftrightarrow$ $\mathcal{M}, y \models \varphi$ for each $y \in \mathcal{X}$, where $\mathcal{X}$ is an underlying topological space.
The modal logic that we seek is the system ${\bf S}4{\bf U}$ which is defined in the bimodal language. We have $\Box$ (an interior modality) that satisfies ${\bf S}4$ axioms and we have $[\forall]$ with ${\bf S}5$ axioms. We also add the additional postulate that has the form $[\forall] p \to \Box p$. That is, if the statement is true everywhere, then it’s true in every open neighbourhood. The very first example of a universal modality is an interior operator in an infinite antidiscrete space, as we observed above. In other words, universal modality is stronger than the interior. Logically, $[\forall] \varphi$ denotes that $\varphi$ is true at every point. A formula $\Box \varphi$ tells us that $\varphi$ is locally true, i. e. at some open neighbourhood that shouldn’t cover the whole space. Moreover, there is the result according to which ${\bf S}4{\bf U}$ is the logic of arbitrary zerodimensional spaces, such as Cantor space, or rational numbers as a metric space with the usual distance.
Also we observe the following postulate:
$[\forall](\Box p \land \Box \neg p) \to [\forall] p \vee [\forall] \neg p$
This formula denotes the fact that an observed space is connected one, i.e. we cannot split it into two disjoint open subsets. Informally, formula claims that if one can split whole space into two disjoint subsets, then one of them is empty. More formally. Let $\mathcal{X}$ be a topological space, then $\mathcal{X} \models [\forall](\Box p \land \Box \neg p) \to [\forall] p \vee [\forall] \neg p$ if and only is $\mathcal{X}$ is connected. Moreover, ${\bf S}4{\bf U} \oplus [\forall](\Box p \land \Box \neg p) \to [\forall] p \vee [\forall] \neg p$ is the logic of all connected separable denseinitself metric spaces.
As we noticed previously, one may study provability and consistency in arithmetic via modal logic. The problem of provability and consistency in Peano arithmetic (briefly, ${\bf PA}$) is connected with famous Gödel’s incompleteness theorems. To understand the connection between modal logic and these aspects of ${\bf PA}$, we show how to prove the incompleteness theorems. Before that, let us explain the historical context of the Gödel’s theorems’ appearance.
At the beginning of the 20th century, one of the commonly discussed topics was the question about the foundations of mathematics and its consistency. Such mathematicians as Richard Dedekind and Georg Cantor were interested in axiomatisation of real analysis in order to describe the principles of real numbers as the set of primitive properties of the real line in terms of order and arithmetical operations.
The notion of a number was defined intuitively before, but one may define a number formally with the set theory proposed by Cantor. Unfortunately, the initial version of the set theory was inconsistent. Inconsistency of Cantor’s set theory was shown by Bertrand Russell who invited the famous paradox which is named after him. The paradoxfree version of the set theory is socalled ZermeloFraenkel theory which is assumed sometimes as the “default” foundations of mathematics.
Later, in the 1920s, David Hilbert, a German mathematician who famous for his works in the foundations of geometry, algebra, and mathematical physics announced the programme of establishing mathematics consistency.
According to Hilbert’s programme, any mathematical theory such as differential equations should be embedded in some formal axiomatic theory. Moreover, any usual mathematical proof must correspond to some formal proof. By formal proof, we mean a finite text written according to a set of strictly defined rules such as inference rules in firstorder logic. The finite methods themselves should be formalised in Peano arithmetic, a formal axiomatic theory of natural numbers. Although, there was the open question: how can we prove that formal arithmetic is consistent within itself? In other words, we would like to formalise formal methods in Peano arithmetics and we want to have a finite proof of Peano arithmetic at the same time. Since this hypothetical proof should be finite, then it also should be formalised in Peano arithmetic.
Kurt Gödel solved this problem negatively in 1931.
More precisely, he showed that there is a sentence in Peano arithmetic that is unprovable and true at the same time. In other words, such a statement has no formal proof but it is true as a fact about natural numbers. Let us describe the idea informally. Moreover, the finite proof of ${\bf PA}$ consistency is impossible since the formula that expresses desired consistency isn’t provable.
I believe, everybody heard about the Liar paradox. Let me remind it. Suppose we have a character that always lies, for instance, Tartuffe from the sametitled comedy by Molière. Let us assume that Tartuffe claims with courage, clarity, and frankness that he’s lying.
Here we are in an embarrassing situation. If Tartuffe tells that he’s lying, hence, he lies that he’s lying. Thus, he’s telling the truth. On the other hand, he always lies by definition. Such confusion is often called the Liar paradox.
Gödel used approximately the same idea and we describe how exactly in the further proof sketch. Before that, we define Peano arithmetic as a formal theory.
Peano arithmetic is an axiomatic theory of natural numbers with addition and multiplication. Philosophically, Peano arithmetic describe the basic properties of natural numbers with primitive operations. Formally, we put the arithmetical language, the set of primitive symbols:
x++
It is not so difficult to see that all those signs are read in accordance with our everyday intuition. To define the grammar of arithmetical formulas, we should restrict the definition of the firstorder formula to the observed arithmetical language.
As in firstorder logic, we have a countably infinite set of individual variables. Let us define a term first:
Informally, terms are finite strings that we read as arithmetical objects. The definition of a formula is the same as in the firstorder case with the additional condition: if $t_1, t_2$ are terms, then $t_1 = t_2$ is a formula.
Now we are able to define Peano arithmetic. Here we introduce two group of axioms. Let us overview the equality axioms which completely agree with our intution:
Equality respects unary and binary operations: 5. $\forall x_1 \: \forall x_2 \: \forall y_1 \: \forall y_2 \: ((x_1 = y_1 \land x_2 = y_2) \rightarrow x_1 + y_1 = x_2 + y_2)$ 6. $\forall x_1 \: \forall x_2 \: \forall y_1 \: \forall y_2 \: ((x_1 = y_1 \land x_2 = y_2) \rightarrow x_1 \cdot y_1 = x_2 \cdot y_2)$ 7. $\forall x \: \forall y \: (x = y \Rightarrow \operatorname{S} ( x ) = \operatorname{S} ( y ))$. Informally, if $x$ and $y$ are equal, then $x + 1$ and $y + 1$ are also equal.
Equality also respects other predicates: 8. $\forall x \: \forall y \: ((x = y \land P ( x )) \rightarrow P ( y ))$ This axiom claims that if objects $x$ and $y$ are equal and the property $P$ holds for $x$, then it also holds for $y$.
We also split the arithmetical group of axioms into the following subgroups. The first group form the definition of successor function $\operatorname{S}$:
Recursive definitions of addition and multiplication:
Induction schema:
As usual, a proof of a formula $A$ in Peano arithmetic is a sequence of formulas, each of which is either:
The last element of such a sequence is the formula $A$ itself.
Here’s an example. Let us show that the associativity of addition is provable in ${\bf PA}$. That is, we show that ${\bf PA} \vdash \forall x \: \forall y \: \forall z \: ((x + y) + z = x + (y + z))$. We provide semiformal proof for humanistic reasons.
Induction base: Let $z = 0$. $(x + y) + 0 = x + y$ by the first addition axiom. On the other hand, $x + (y + 0) = x + y$, since $y + 0 = y$ be the same addition axiom.
Induction step Let $z = \operatorname{S}(z_1)$. Suppose we have already proved $(x + y) + z_1 = x + (y + z_1)$. Let us show that $(x + y) + \operatorname{S}(z_1) = x + (y + \operatorname{S}(z_1))$. Then:
$\begin{array}{lll} & (x + y) + \operatorname{S}(z_1) = & \\ & \:\:\:\: \text{The second addition axiom} & \\ & \operatorname{S}((x + y) + z_1) = & \\ & \:\:\:\: \text{Induction hypothesis} & \\ & \operatorname{S}(x + (y + z_1)) = & \\ & \:\:\:\: \text{The second addition axiom (twice)} & \\ & x + \operatorname{S}(y + z_1) = x + (y + \operatorname{S}(z_1)) & \end{array}$
We also note this way of inductive reasoning is implemented in such proofassistants as Agda, Coq, Isabelle, etc. To get started with formal inductive reasoning in Agda, read the second part of my blog post on nonconstructive proofs.
A Gödel numbering is an instrument that encodes the syntax of Peano arithmetic within itself. Informally, we have a function $\gamma$ that maps each symbol of our signature maps to some natural number. More precisely, let $\operatorname{Term}$ be a set of all arithmetical terms and $\operatorname{Form}$ a set of all arithmetical formulas. A Gödel numbering is a function $\gamma : \operatorname{Term} \cup \operatorname{Form} \to \mathbb{N}$, i.e., $\gamma$ maps every arithmetical term or formula to some natural number. We denote $\gamma(t)$ and $\gamma(A)$ as $\lceil t \rceil$ and $\lceil A \rceil$ correspondingly.
The next goal is to have a method of proof formalisation within Peano arithmetic. As we told, proof of an arithmetical formula $A$ is a sequence of formulas with several conditions that we described above. The keyword here is a sequence. First of all, we note that one needs to have an ordered pair since any sequence of an arbitrary length has a representation as a tuple. First of all, we note that one needs to have an ordered pair since any sequence of an arbitrary length has a representation as a tuple. In other words, one has a sequence $\langle a_1, \dots, a_{n1}, a_n \rangle$ that might be represented as a tuple $\langle \langle a_1, \dots, a_{n1} \rangle, a_n \rangle$. Here, we have Cantor’s encoding $c : \mathbb{N} \times \mathbb{N} \to \mathbb{N}$ that establish bijection between natural numbers and $\mathbb{N} \times \mathbb{N}$. In order to avoid a technical oversaturation, just believe me that there is a way to encode predicates that define whether a $\lceil A \rceil$ either is logical axiom or arithmetical one, or it’s obtained from previous formulas via inference rules.
Ultimately, we have a proof predicate that has the form $\operatorname{Prf}(x, y)$. This formula should be read as $x$ is a Gödel number of a sequence that proves $y$ in Peano arithemtic. Moreover, if $A1, \dots, An, A$ is a proof of $A$, then ${\bf PA}$ knows about this fact. In other words, ${\bf PA} \vdash \operatorname{Prf} (\lceil A1, \dots, An, A \rceil, \lceil A \rceil)$. Otherwise, ${\bf PA} \not\vdash \operatorname{Prf} (\lceil A1, \dots, An, A \rceil, \lceil A \rceil)$.
We need the proof predicate to express provability formalised in Peano arithmetic. A provability predicate is a formula $\operatorname{Pr} ( y )$ which claims that there exists a sequence of formulas that proves $y$ (in terms of Gödel encoding, indeed) in ${\bf PA}$. It is not so difficult to realise that a provability predicate has the form $\exists x \:\: \operatorname{Prf} (x, y)$. It is also clear that if ${\bf PA} \vdash \operatorname{Prf} (x, y)$, then ${\bf PA} \vdash \operatorname{Pr} ( y )$.
Provability predicate in Peano arithmetic has crucially important conditions that were established by Hilbert, Bernays, and Löb.
HilbertBernaysLöb conditions
The reader may observe that these conditions remind the logic ${\bf K}4$, the logic of all transitive frames. Here, the first condition corresponds to the necessitation rule, the second one to Kripke axiom, and the third one to the transitivity axiom $\Box p \to \Box \Box p$. It’s no coincidence as we’ll see further.
Now we are ready to observe the first incompleteness theorem.
The fixedpoint lemma allows one to build uniformly selfreferential statements, i. e. such statements that tell something about themselves. For instance, the root of the Liar paradox is a selfreference, where Tartuffe tell us that he’s lying at the moment being a chronic liar.
Fixedpoint lemma Let $B ( x )$ be an arithmetical formula with one free variable, then there exists a closed formula $A$ such that ${\bf PA} \vdash A \leftrightarrow B(\lceil A \rceil)$
The first Gödel’s incompleteness theorem is a consequence of the fixedpoint lemma and the HilbertBernaysLöb conditions.
The first Gödel’s incompleteness theorem Let $\varphi$ be an arithmetical formula such that ${\bf PA} \vdash \varphi \leftrightarrow \neg \operatorname{Pr} (\lceil \varphi \rceil)$. Then ${\bf PA} \not\vdash \varphi$
Suppose ${\bf PA} \vdash \varphi$. Then ${\bf PA} \vdash \operatorname{Pr} (\lceil \varphi \rceil)$ by the first HilbertBernaysLöb condition. On the other hand, ${\bf PA} \vdash \neg \operatorname{Pr} (\lceil \varphi \rceil)$ by the given fixedpoint. Contradiction.
The fixedpoint from the formulation above claims its own unprovability but it’s true in the standard model of Peano arithmetics (the model of arithmetic on usual natural numbers) at the same time! Thus, the elementary theory of natural numbers with addition, multiplication and equality, the set of all true arithmetical statements, contains the proposition that doesn’t belong to the set of all consequences of Peano arithmetic. This fact tells us that Peano arithmetic is not complete with respect to its standard model. That is, there exists a statement that is true and unprovable in ${\bf PA}$ at the same time.
The second incompleteness theorem argues that it is impossible to prove the consistency of ${\bf PA}$ within ${\bf PA}$. First of all, we define a formula that expresses consistency. Let us put $\operatorname{Con} $ as $\neg \operatorname{Pr} (\lceil \bot \rceil)$. Here $\lceil \bot \rceil$ is a Gödel number of the false statement, e.g. $0 = 1$. Here we formulate only the theorem without proof. Note that this theorem might be proved via the same fixedpoint as in the first Gödel’s theorem:
The second incompleteness theorem
${\bf PA} \not\vdash \operatorname{Con}$
Löb’s theorem arose as a response to the question about statements in Peano arithmetic that are equivalent to its provability. Martin Löb showed that such statements are exactly all provable in ${\bf PA}$ formulas. Note that Löb’s theorem itself has a formalisation in Peano arithmetics as follows. Here we formulate Löb’s theorem in two items, where the second one is a formalised version.
Löb’s theorem
Note that the second incompleteness theorem follows from Löb’s theorem as follows:
$\begin{array}{lll}
& {\bf PA} \vdash \bot \Leftrightarrow {\bf PA} \vdash \operatorname{Pr} (\lceil \bot \rceil) \rightarrow \bot \Leftrightarrow & \
& {\bf PA} \vdash \bot \Leftrightarrow {\bf PA} \vdash \neg \operatorname{Pr} (\lceil \bot \rceil) \Leftrightarrow & \
& {\bf PA} \vdash \bot \Leftrightarrow {\bf PA} \vdash \operatorname{Con} &
\end{array}$
We discussed the incompleteness of formal arithmetic and its influence on the foundations of mathematics. Let us discuss now how we can study provability and consistency using modal logic.
GödelLöb logic (${\bf GL}$) is an extension of the logic ${\bf K}$ with Löb formula $\Box (\Box p \to p) \to \Box p$. One may rewrite this formula with diamonds as $\Diamond p \to \Diamond (p \land \neg \Diamond p)$. In terms of possibility, Löb formula has an explanation à la if something is possible then it’s possible for the last time. In other words, every possibility appears only ones, dies after that, and never repeats. It sounds quite pessimistic and very realistic at the same time, isn’t it?
From a Kripkean perspective, ${\bf GL}$ is the logic of transitive and Noetherian frames. We have already discussed what transitivity is. Let us discuss Noetherianness. A Noetherian relation (or wellfounded) is a binary relation $R$ such that there are no increasing chains $x_0 R x_1 R \dots$. Equivalently (modulo axiom of choice), any nonempty subset has a $R$maximal element. An element $x$ is called $R$maximal in some nonempty subset $W’$, if for each $y \in W’$ one has $y R x$ and there is no $z$ such that $x R z$. Here we claim that ${\bf GL} = \operatorname{Log}(\mathbb{F})$, where $\mathbb{F}$ is the class of all transitive and Noetherian frames. This relation is named after Emmy Noether, a famous German mathematician, whose results have influence in commutative algebra and ring theory.
Note that the GödelLöb formula is not the Salqvist one and it is not canonical. Moreover, wellfoundedness is not a firstorder definable property of Kripke frames since we need quantifiers over subsets, not only over elements. Thus, we cannot prove that ${\bf GL}$ is Kripke complete via its canonical model. One may show the Kripke completeness using more sophisticated tool called selective filtration provided by Dov Gabbay in the 1970s. We drop the selective filtration in our post, the reader might study this tool herself. Here we recommend the sametitled paper by Gabbay.
By the way, Löb’s formula also has a computational interpretation and connections with functional programming. Read this blog post by Neel Krishnaswami to become acquainted with this aspect of GödelLöb logic. A Haskell engineer might have met something similar to Löb’s formula. Here we mean the function called cfix
from the module Control.Comonad
, a comonadic fixedpoint operator.
We need GödelLöb logic to charaterise provability and consistency in Peano arithmetic. Let us define an arithmetic realisation. Let $\operatorname{Cl}{\Omega}$ be the set of all closed arithmetical formulas. Suppose also one has a valuation of propositional variables $r : \operatorname{PV} \to \operatorname{Cl}{\Omega}$ such that each variables maps to some arithmetic sentence. An arithmetic realisation is a map $\rho$ such that:
In other words, an arithmetic realisation is an extension of a given valuation to all modal formulas. An arithmetic realisation is an interpretion of modal formulas in terms of arithmetic sentences and their provability. As you could see, we read $\Box \varphi$ as $\varphi$ is provable in Peano arithmetic. Solovay’s theorem claims the following fascinating fact:
Theorem A modal formula $\varphi$ is provable in ${\bf GL}$ if and only if it’s interpretation in ${\bf PA}$ is provable for every arithmetic realisation.
The “only if part” is proved quite simply. This part claims that an interpretation of provable in ${\bf GL}$ formula is provable in ${\bf PA}$ is provable for every arithmetic realisation. The fact that such an interpretation respects the necessitation rule follows from the first HilbertBernaysLöb condition. Kripke axiom also preserves because of the second HBL condition. The provability of Löb axiom interpretation follows from the formalisation of Löb’s theorem in Peano arithmetic. The converse implication is much harder than the previous one and we drop it. The reader might familiarise with the arithmetical completeness proof in the book The Logic of Provability by George Boolos.
This theorem is often called the arithmetical completeness of ${\bf GL}$ that gives us a purely modal characterisation of provability in Peano arithmetic. In other words, all statements we can formally prove about provability and consistency are covered in the system of modal logic.
For instance, as we told earlier, ${\bf PA}$ doesn’t prove its consistency, a formula of the form $\neg {\bf Pr}{\bf PA}(\lceil \bot \rceil)$. The modal counterpart of $\operatorname{Con}{\bf PA}$ is the formula $\Diamond \top$ which claims that relation in a Kripke frame is serial, i.e., every point has a successor. It is not so hard to check that $\Diamond \top$ isn’t provable in ${\bf GL}$ since a frame cannot be Noetherian and serial at the same time: if any increasing chain has a maximal element, then there exists a deadlock that has no successors. Thus, Noetherianess contradicts to seriality. In other words, ${\bf GL} \oplus \Diamond \top$ is inconsistent:
$$\begin{array}{lll} (1) & \Diamond \top \to \Diamond (\top \land \neg \Diamond \top)& \\ & \:\:\:\: \text{The instance of Löb formula}& \\ (2) & \Diamond \top& \\ & \:\:\:\: \text{Seriality}& \\ (3) & \Diamond (\top \land \neg \Diamond \top)& \\ & \:\:\:\: \text{(1), (2), Modus Ponens}& \\ (4) & \Diamond \neg \Diamond \top& \\ & \:\:\:\: \text{Top is a neutral for conjunction}& \\ (5) & \neg \Box \neg \neg \Diamond \top& \\ & \:\:\:\: \text{Unfolding diamond with negation and box}& \\ (6) & \neg \Box \Diamond \top& \\ & \:\:\:\: \text{Double negation elimination}& \\ (7) & \Box \Diamond \top& \\ & \:\:\:\: \text{(2), Necessitation}& \\ (8) & \neg \Box \Diamond \top \land \Box \Diamond \top& \\ & \:\:\:\: \text{(6), (7), Conjuction introduction}& \\ (9) & \bot& \\ & \:\:\:\: \text{(8), Boolean tautology}& \\ \end{array}$$
Under the similar circumstances, ${\bf GL} \oplus \Box p \to p$ is inconsistent. That is, a Noetherian frame cannot be a reflexive one:
$$\begin{array}{lll} (1) & \Box p \to p & \\ & \:\:\:\: \text{Reflexivity axiom}& \\ (2) & \Box (\Box p \to p )& \\ & \:\:\:\: \text{(1), Necessitation}& \\ (3) & \Box (\Box p \to p) \to \Box p& \\ & \:\:\:\: \text{Löb formula}& \\ (4) & \Box p & \\ & \:\:\:\: \text{(2), (3), Modus Ponens}& \\ (5) & p & \\ & \:\:\:\: \text{(1), (4), Modus Ponens}& \\ (6) & \bot & \\ & \:\:\:\: \text{(5), Substitution}& \end{array}$$
Gödel incompleteness theorems admit a variety of generalisations. Here we refer the reader to the book Aspects of Incompleteness by Per Lindström.
We discussed before how modal logic is connected with such fundamental disciplines as metamathematics and topology. Now we overview briefly more applied aspects of modal logic. As we told, modalities in logic have a philosophical interpretation in terms of necessity and possibility. We saw above modal operators also have more mathematical reading such as operators on subsets of topological space and provability in formal arithmetic. First of all, we overview temporal logic.
Historically, temporal logic arose as a branch of philosophical logic like modal logic itself. Arthur Prior, a New Zealander philosopher and logician, was the first who described logical systems with temporal modalities in the 19501960s. Prior extended modal language with operators $\Box^{}$ and $\Diamond^{}$. Here, $\Box \varphi$ and $\Diamond \varphi$ denotes $\varphi$ will always be true in the future and $\varphi$ will occur at some moment correspondingly. Modalities $\Box^{}$ and $\Diamond^{}$ have the same interpretation, but in terms of future. If we will consider Kripke models, then the semantics for $\Box$ and $\Diamond$ are the same as we used to consider. $\Box^{}$ and $\Diamond^{}$ have similar truth conditions in terms of converse relation:
We are not going to consider such systems of temporal logic more closely. We only note that such modalities allow one to characterise such structures as the real line much more precisely. Instead of unary temporal modalities, we consider binary ones. We reformulate our modal language to stay accurate:
As usual, we have a countably infinite set of propositional variables $\operatorname{PV} = { p_0, p_1, p_2, \dots }$. Temporal language with binary modalities is defined as follows:
The items above define usual language of classical logic. The following item is completely different: 4. If $\varphi$, $\psi$ are formulas, then $(\varphi \mathcal{U} \psi)$ and $(\varphi \mathcal{S} \psi)$ are formulas.
The question we need to ask is how should we read $\varphi \mathcal{U} \psi$ and $\varphi \mathcal{S} \psi$?
$\varphi \mathcal{U} \psi$ and $\varphi \mathcal{S} \psi$ are read as “$\varphi$ until $\psi$” and “$\varphi$ since $\psi$”. More strictly, these modalities have the semantics:
Here we note that unary modalities $\Diamond \varphi$ and $\Diamond^{} \varphi$ are expressed as $\top \mathcal{U} \varphi$ and $\top \mathcal{S} \varphi$. Thus, such binary modalities generalise unary modalities proposed by Prior. Initially, such modalities were introduced by Kamp for continuous ordering description on the real line. We will take a look at the system of temporal logic that represents the behaviour of concurrent programs axiomatically.
Here we observe the temporal logic of concurrency briefly. Read the book by Goldblatt called Logics of Time and Computation, Chapter 9 for more details. The core idea is we describe a program behaviour on the set of states with reachability relation as on a Kripke frame. Here, modalities describe alternatives in further actions depending on the execution at the current state, e.g., for process deadlocks and showing correctness.
We will use only $\mathcal{U}$ modality with unary modality denoted as $\bigcirc$. Here we work with a state sequence, a pair $\langle S, \sigma \rangle$, where $S$ is a nonempty set and $\sigma$ is a surjective enumeration function that map every state to some natural number. We require surjectivity to restore a state by its number. The desired Kripke structure is a state sequence with relation $R$ such that $i R j$ iff $j = i + 1$. A Kripke model is a state sequence equipped with a valuation function, as usual. We extended the language with $\bigcirc$ that has the following semantics:
$\mathcal{M}, i \models \bigcirc \varphi$ if and only iff $\mathcal{M}, i+1 \models \varphi$.
The logic we are interested in is an extension of classical logic with following modal axioms:
The first two principles just tell us that both unary modalities are normal ones. The third axiom claims that $\bigcirc$ is a functional relation, that is, if $i = j$ implies $i + 1 = j + 1$. The fourth postulate describes the connection between unary modalities: if $p$ is always true in the future, then it’s true at the current moment and it’s always true at the next moment. The fifth axiom is a sort of induction principle: if it is always true that $p$ implies itself at the next state, then it’s true for every state. The formal logic that we described above is exactly the logic of all state systems considered as Kripke frames
Temporal logic of concurrency is just an example of modal logic use in computation and program verification. For instance, the reader may take a look at the project called Verified smart contracts based on reachability logic. Reachability logic is a formal system that combines core ideas of Hoare’s logic, separation and temporal logics. This project provides a framework that produces a formal specification for a given smart contract passed as an input. Such a specification is formulated in terms of reachability logic language. After that, one needs to show that a lowlevel code (EVM bytecode, for instance) behaviour satisfies the infered specification. The fact of this satisfiability should be provable in reachability logic and such proofs are formalised in this formal system via Kframework. The examples of verified smart contracts are OpenZeppelin ERC 20, Ethereum Casper FFG, and Uniswap.
Intuitionistic modal logic is modal logic where the underlying logic is intuitionistic one. That is, we reject the law of excluded middle. Such a rejection is needed to consider formally constructive reasoning in which proof is a method that solves a given task. One may condiser intuitionistic modal logic from two perspectives. The first perspective is the philosophical one: we try to answer the question about necessity and possibility from a constructive point of view. The second perspective is closer to constructive mathematics and functional programming. As it is wellknown that one may map any constructive proof to typed lambda calculus, where proofs correspond to lambdaterms and formulas to types. In such an approach, modality is a computational environment.
Here we discuss monadic computation within intuitionistic modal logic to consider logical foundations of basic constructions used in such languages as Haskell. One of the most discussed topics in functional programming in Haskell is monad, a type class that represents an abstract data type of computation:
class Functor m => Monad m where
return :: a > m a
(>>=) :: m a > (a > m b) > m b
Here, the definition of the Monad
type class is quite old fashioned, but we accept this definition just for simplicity. Monad instances are Maybe
, []
, Either a
, and IO
. As the reader might know, Monad
represents how to perform a sequence of linearly connected actions within some computational environment such as inputoutput, for instance. In such a sequence, the result of the current computation depends on the previous ones. Such a computation was considered typetheoretically by Eugenio Moggi who introduced the socalled monadic metalanguage. Without loss of generality, we can claim that monadic metalanguage is an extension of typed lambdacalculus with the additional typing rules that informally correspond to return
and (>>=)
methods. In fact, monadic metalanguage is a typetheoretic representation of Haskellstyle monadic computation.
It is quite convenient to consider such a kind of computation in terms of monads, a categorical construction that arose as a generalisation of a composition of adjoint functors. In fact, Monad
in Haskell is closer to Kleisli triple, an equivalent formulation of monad. Here are the typing rules of monadic metalanguage:
Monadic metalanguage is also studied logically, see the paper Computational Types from a Logical Perspective by P. N. Benton, G. M. Bierman, and V. de Paiva.
It is quite curious, one may consider monadic metalanguage in terms of CurryHoward correspondence. From this perspective, this modal lambdacalculus is isomorphic to lax logic that was introduced by Robert Goldblatt in the paper called Grothendieck Topology as Geometric Modality, p.p. 131172, where he studies modal aspects of a Grothendieck Topology, a categorical generalisation of a topological space. This logic is defined as follows:
Note that one may equivalently replace the fourth axiom with $(p \to \nabla q) \to (\nabla p \to \nabla q)$, a type of monadic bind in eyes of Haskeller.
This logic also describes syntactically socalled nucleus operator that plays a huge role in pointfree topology, a version of general topology developed within constructive mathematics. If you apparently want to get more familiar with the related concepts, you may read the book Stone Spaces by Peter Johnstone or the third volume of Handbook of Categorical Algebra (the subtitle is Sheaf Theory) by Francis Borceux. Additionally, here we also recommend the brief lecture on Topos Theory course by André Joyal, here’s the link on YouTube where the same notions are covered.
Let us overview some of the monographs, textbooks, and papers that we recommend to you for further study if you are interested in it.
Basic Modal Logic by Patrick Blackburn, Maarten de Rijke and Yde Venema is one of the best introductory texts books in modal logic. This book covers such topics as relational semantics, algebraic semantics, firstorder frame definability, general frames, computational aspects of modal logic and its complexity. Also, there are a lot of miscellaneous exercises that allow you to improve your mastery of the material.
Modal Logic by Alexander Chagrov and Michael Zakharyaschev can serve the reader as a handbook in basic concepts of modal logic. This book might be considered as a more detailed version of the previous book.
Modal Logic for Open Minds by Johan van Benthem is another introduction to modal logic. In contrast to the previous two books, Modal Logic for Open Minds is more concentrated on the variety of modal logic applications. In this book, the reader may study and build an understanding of basic spatial logic, epistemic logic, logic of time, logic in information dynamics, provability in formal arithmetic, etc. We also note that Modal Logic for Open Minds is written in accessible language and the textbook is full of humour.
An Essay in Classical Modal Logic is a PhD thesis by written Krister Segerberg at the beginning of the 1970s. As we told in the first part, Segerberg is one of the founders of the contemporary modal logic. One may use this thesis as a laconic introduction to modal logic, especially to such topics as Kripke semantics, neighbourhood semantics, filtrations, and completeness theorems for basic logics.
Logics of Time and Computation by Robert Goldblatt is an introduction to modal logic but specified to tense logic and related systems that describes several dynamics and computation. The first part of the textbook is the standard introduction to basic modal logic. Further, Goldblatt studies discrete, dense, and continuous time; describes concurrent and regular computation via temporal logic with binary modalities and propositional dynamic logic. The third part covers modeltheoretic aspects of predicate extensions of propositional dynamic logic.
Mathematics of Modality is the collection of papers by Robert Goldblatt, the author of the previous book. As an alternative introduction to modal logic, I would recommend the paper called Metamathematics of Modal Logic, the very first paper from this collection. In this paper, Kripke semantics is more discussed from a modeltheoretic perspective. In addition to Kripke semantics, the reader may use the paper as an introduction to categorical and algebraic aspects of modal logic. By the way, Goldblatt is one of the pioneers in Categorical Logic and he is also famous as the author of Topoi: The Categorical Analysis of Logic textbook.
Handbook of Modal Logic, edition by Patrick Blackburn, Johan van Benthem and Frank Wolter. The title is selfexplanatory. This handbook is a collection of introductory papers on diverse branches of modal logic written by wellknown experts in corresponding areas. The handbook covers quite comprehensively more advanced topics and applications of modal logic in philosophy, game theory, linguistics, and program verification.
Handbook of Spatial Logics. The title is selfexplanatory to the same extent as the previous one. This handbook is a collection of papers on applications of logic in spatial structures. Here we recommend especially the paper called Modal Logics of Space written by Johan van Benthem and Guram Bezhanishvili.
Provability Logic by Sergei Artemov and Lev Beklemishev is a comprehensive overview on the current state of affairs in provability logic, the area of modal logic that study provability of formal axiomatic theories as arithmetic and set theory. This paper has two parts. The first part is an observation of provability in arithmetical theory and its algebraic and prooftheoretical aspects. The second part is an introduction to logic of proofs that was proposed by Sergei Artemov as an extension of classical logic with proofterms.
Quantification in Nonclassical Logics by Dov Gabbay, Valentin Shehtman and Dmitry Skvortsov is the fundamental monograph on the semantical problems of modal firstorder logic. The problem is the variety of modal predicate logics are Kripke incomplete and one has to generalise Kripke semantics of firstorder modal logic to make the class of incomplete logic more narrow. Here we recommend bringing to the attention of simplicial semantics, the most general version of predicate Kripke frame based on simplicial sets.
As we discussed, one may study weaker logics than normal ones. For that, we need to have a more general semantical framework. Such a framework is called neighbourhood semantics. Read the textbook called Neighbourhood semantics for Modal Logic by Eric Paquit to familiarise yourself with this generalisation of Kripke semantics.
If you’re interested in temporal logic, we may recommend the textbook Temporal Logic. Mathematical Foundations and Computational Aspects by Dov M. Gabbay, Ian Hodkinson, and Mark Reynolds.
I believe the reader did a great job trying to become engaged with all this abstract material. Let us overview what we observe. We started from a philosophical motivation of modal logic and its historical roots. We got acquainted with Kripke frames, the canonical frame and model, and filtrations in the first part. In the second part, we studied some use cases. Pure modal logic is quite abstract and therefore admits miscellaneous interpretations in a variety of subject matters. Our use case observation isn’t comprehensive, it’s merely an invitation. We provided the list above for further reading if the reader considers to study modal logic herself more deeply.
I thank Gints Dreimanis and Jonn Mostovoy for editing and remarkable comments. I want to say thank you to my mate and colleague Rajab Agamov from the Department of Mathematics at Higher School of Economics (Moscow) for helpful conversations and the fact that he is a good lad. I’m grateful to Alexandra Voicehovska for the idea of a snake in topological illustrations. This blog post series is mostly based on the lecture course that I gave at the Computer Science Club based in the SaintPetersburg Department of Steklov Mathematical Institute last autumn. I’m also grateful to Ilya B. Shapirovsky and Valentin B. Shehtman of the Institute for Information Transmission Problems of the Russian Academy of Sciences for discussions on the course programme.
]]>In recent times, functional programming has invaded codebases all around the world.
Look at the main programming languages. We wouldn’t be able to live without higherorder functions, those anonymous functions that new developers tend to extremely overuse, and other cutesy stuff.
This is not going to be a functional programming tutorial that will show how to use these features in JavaScript. You can find that on freeCodeCamp.
In what can only be described as “a crazy move that will decimate the view count of this article”, I’d rather talk about the paradigm from which these features have been captured from. Ready?
In short, functional programming is a catchall term for a way of writing code that is focused on composing pure functions, actually using the innovations in type systems made in the last few decades, and overall being awesome.
So what’s the point? All of these things help to better understand what actually happens in our code.
And, once we do that, we gain:
You’re a functional programmer, Harry.
As it is, functional programming is ideal for developing code for distributed systems and complex backends, but that isn’t all it can do. At Serokell, we use it for most of our industry projects. Whether you need frontend or backend, it doesn’t matter, there is an FP language for everything nowadays.
Now that you are stoked about learning more about functional programming and have already ordered your copies of Programming Haskell on Amazon, let’s delve deeper into the details.
At the heart of functional programming is lambda calculus.
Introduced by the mathematician Alonzo Church in the 1930s, lambda calculus is just a way of expressing how we compute something. If you understand this one, you will gain a lot of intuition on how functional programming looks in practice.
There are only three elements in lambda calculus: variables, functions, and applying functions to variables. Here we have to think about function as a pure/mathematical function: a way of mapping members of a set of inputs to members of a set of outputs.
Even though it is a very simple tool, we can actually compose different functions / boxes and, in that way, encode any computation possible with a regular computer. (It would get unwieldy fast for anything nontrivial though, and that’s why we don’t program in it.)
To further illustrate the concept, I refer you to this video of an absolute madman implementing lambda calculus in Python.
In 195060s, people began to encode this notion into programming languages. A good example is LISP, a kind of functional language designed by John McCarthy that keeps the overall incomprehensibility of lambda calculus while actually enabling you to do some things.
Example implementation of A* search algorithm in Racket (a dialect of LISP):
#lang racket
(require racket/set)
(define (pqinserts elems pq)
(sort (append elems pq) (lambda (a b)
((first a) . < . (first b)))))
(define (astar estimate neighbors dist eps start)
(define (go visited pq)
(match pq
[(list* (list estimation distance path) restpq)
(let ((point (first path)))
(if ((estimate point) . <= . eps)
path
(let*
( (near
(filter
(compose not (curry setmember? visited))
(neighbors point)))
(paths
(for/list ([pt near])
(let ((distance1 (+ distance (dist point pt))))
(list
(+ distance1 (estimate pt))
distance1
(list* pt path))))))
(go
(setadd visited point)
(pqinserts paths restpq)))) )]
[else
'()]))
(define initial
(list (list 0 0 (list start))))
(reverse
(go
(set)
initial)))
But that was only the beginning. One thing led to another, and, as we introduced such languages as ML and Miranda, the numerous permutations explored adding readability and a great type system. As a result, the 1980s saw the arrival of something beautiful – Haskell, a programming language so great that it was destined to evade mainstream for the next 30 years.
The same A* algorithm in Haskell:
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import qualified Data.Set as Set
import qualified Data.List.NonEmpty as NE
import Data.List.NonEmpty (NonEmpty (..), (<))
{ 
I use `Map.Map` as a priority queue, by popping element
with a minimal key using `Map.minView`.
}
aStar
:: (Num d, Ord d, Ord a)
=> (a > d)  distance estimation
> (a > [a])  neighbours retrieval
> (a > a > d)  distance between 2 points
> d  distance from end we should reach
> a  starting point
> Maybe (NE.NonEmpty a)
aStar estimate neighbors dist epsilon start = do
NE.reverse <$> go Set.empty initialQueue
where
initialQueue = Map.singleton (cost start 0) (start : [], 0)
cost point traversed = estimate point + traversed
go visited queue = do
((path @ (point : _), distance), queue') < Map.minView queue
if estimate point <= epsilon
then do
return path
else do
let near = filter (`Set.notMember` visited) (neighbors point)
batch = flip map near $ \\point' >
let distance' = distance + dist point' point
in (cost point' distance', (point' < path, distance'))
go (Set.insert point visited) (Map.fromList batch <> queue')
We’ll return to Haskell later.
Ok, I hope I gave the intuition about how pure functions and chaining pure functions would look. What else is there?
Immutability. This follows from pure functions. If the function has an input and gives an output, and doesn’t maintain any state, there can be no mutable data structures. Forget i++. This is for the better. Mutable data structures are a sword that looms over the developer’s head, waiting to fall at any moment.
Immutability also helps when the underlying code needs to be threadsafe and therefore is a huge boon in writing concurrent/parallel code.
All kinds of ways to handle functions. Anonymous functions, partially applied functions, and higherorder functions – these you can get in all modern programming languages. The main benefit is when we go higher up the abstraction ladder. We can introduce various kinds of design patterns such as functors, monads, and whateverkindsofmorphisms that we port right from category theory, one of the most powerful tools of mathematics, because… get it? Our code is a composition of mathematical functions.
There is a chance you stopped at immutability and thought: how can we accomplish anything without maintaining a global state? Isn’t it extremely awkward? Nope. We just pass the relevant state through the functions.
While it may seem unwieldy at first (and that is more because it is a new style of programming, not because of inherent complexity), functional programming abstractions help us to do it easily, For example, we can use special constructions such as state monad to pass state from function to function.
As you can see, functional programming concepts synergize well with each other. In the end, we have a selfconsistent paradigm that is wonderful for anything where you would want to include an element of it.
I’ve been holding back on the greatest thing, though.
Did you know that a lot of smart people are doing Haskell & Co nowadays? Functional programming is a great way to gather/meet unappreciated talent that hasn’t yet been devoured by the corporate clutches of FAANG.
We know this from experience. Our engineers are badass, and not only on our team page.
So if there is a project you want to kick off, and you want to kick it off with a team that will rock your socks off, I will list a few functional programming languages with which to attract nextlevel developers.
Haskell was developed back in times far, far away when the FP community faced the situation of there being too many goddamn functional programming languages with similar properties. Turns out when you bring a lot of smart people together, something can happen. But more about that in our Haskell history post.
Since then, Haskell has established itself in certain fields, such as:
Many large companies have projects of various sizes that use Haskell.
Haskell is a combination of various ideas that, brought together, have created a being of utter (expressive) power:
It’s one of our favourite languages, and for a reason. Haskell, when used correctly, delivers. And what it delivers is precise and effective code that is easy to maintain.
Want to go functional, but would love to spoil it with a couple of classes here and there?
Scala is the right choice for that. For some reason favoured by people that wrote Apache Spark, it can be useful for big data processing, services, and other places where functional programming is amazing.
An additional bonus of Scala is that it compiles to JVM. If that is something you need as a manager to introduce functional programming to a Java codebase, go you!
Once you start writing purely functional Scala that does not interact with JVM, there are not a lot of reasons to just switch to Haskell though as the support is much better.
If Haskell is a bit niche, OCaml is super niche with one of the main things holding it above water being local developer support in France.
But perhaps not anymore. For example, similarly to other programming languages listed, it has seen use in blockchain, particularly, Tezos. And they have their reasons.
OCaml is one of those languages that blurs the boundary between functional programming and objectoriented languages. Therefore, using OCaml over Haskell might be more intuitive for a newly functional programmer. OCaml is less obsessed with purity, and the people who write in it are a bit more practical: you might survive the attack of your fellow developers if you just try to wing it in OCaml.
Did you know that the world’s best web framework is written in a functional programming language? Productive. Reliable. Fast. Yeah.
Elixir is a functional, generalpurpose programming language that runs on BEAM, the Erlang VM. It is known for its role in creating lowlatency and faulttolerant distributed systems. Furthermore, it is great at creating stuff that scales according to the needs of the network. Elixir is extremely well used at companies like WhatsApp and Netflix that handle a lot of data and need to do it fast. You can’t miss this one if you are doing something similar.
You know, I cannot end without a pitch. Functional programming is excellent for extensive systems and structures. However, not every business can devote enough resources to execute such complicated work. Serokell understands the struggle and aims to deliver the best service possible to ensure smooth and reliable programming projects for your company.
Our developer team provides development services in different languages. We not only write code but also carry out projects from their starting ideas to their last stages. This means that we can also do research, design, and other connected services for you. Although we offer a versatile coding language scope, I have to warn that we mainly use Haskell.
Visit our page and learn more about how you can make your projects at least ten times more amazing by using Serokell’s services. Thank you for reading.
]]>Could you unite a career in physics with writing and teaching Haskell, one of the (allegedly) most complex programming languages out there? Today, we have an interview with a person that can. Meet Rinat Stryungis.
While he started out wanting to be a historian, Rinat has since become a physicist and a teacher of a Haskell course at Moscow State University, and he does all that while working with Haskell at Serokell. Rinat is truly a person that can wear many hats.
In the interview, we talk about the difference between people studying exact sciences and humanities, Haskell and its use in physics, and Rinat’s course at Moscow State University. Let’s go!
Hi! Do you divide people into techies and humanities? If so, how do you think the technician’s thinking differs from the humanities?
The mindsets of technicians and humanities are very different from each other. Sometimes, I even feel the difference between physicists and programmers.
Our profession greatly affects us. Once I wanted to be a historian, then as a student of the Faculty of Physics, I went to courses at the Faculty of History of Moscow State University. Also, my previous girlfriend instilled in me an interest in philology. So, I know the difference between techies and humanities well.
A person in the humanities profession must constantly do three things:
They must study people. They must study them objectively and subjectively. It’s hard to learn a culture that you don’t feel at least a little. Because of this, the reasoning of the humanities at first seemed wildly inaccurate to me and it annoyed me. Then I understood the methodology of history and why it became so.
They must study and process VERY much lowconcentrated and unstructured information. To be a good historian, you need to thoroughly study the primary sources, constantly keep abreast of new works of other historians, work together with philologists and archaeologists. This information is simpler than in physics or mathematics, but it is VERY abundant. Therefore, it is a little bit more difficult for them to give rigorous reasoning. But then, it’s a little easier for them to quickly understand difficult situations in life since they can immediately recognize them from many angles.
They should write a lot and very well. They should express their thoughts very well and competently. Suppose I am a historian who has studied the culture of Assyria well. I spent many years on this, understood the culture of the Assyrians: read books about them, read their inscriptions, studied life, myths, ideas about the world, etc. Suppose I feel their culture. Now, so that my sensation does not die with me, I need to be able to express my sensation, my many years of generalized experience and accumulated knowledge in one or several books. Express complex and interrelated facts. Express so that the reader has a similar sensation from a culture that he has never known before.
This is a very difficult task, which is irreplaceable for the humanities. Therefore, usually, they are better able to express themselves with words.
Cool! And now, it’s time to talk about functional programming. How did you get into Haskell? And programming as well?
It’s a long story. As a child, I wanted to be a historian. I even found my specialization – “The Late Roman Empire and Byzantium”. Then, in the 9th grade, I decided to be a physicist: I read a lot of science fiction books and decided that it is the only way I can move humanity forward into space.
Physics at the university didn’t go very well, unlike programming, but because of this childhood dream, I thought that any activity besides physics is something meaningless and without any value. I liked programming but for me it also seemed like something nonfundamental and fleeting.
In general, I liked IT, I worked as a system administrator, but perceived it as something temporary, which has to be done purely for the sake of money. In my previous job, I wrote a lot of different utilities in C ++. Although initially I was employed as a system administrator, at some point, I decided to learn something new. Python language in my case.
Suddenly, it was rather hard for me, although I had heard a lot about its simplicity. Then, I thought that probably I hadn’t studied anything new for too long and my brain had “stagnated”. To fix this, I decided to study something that would be as difficult and new as I can make it to be. I chose functional programming and the most “functional” of all the languages – Haskell. It seriously changed my life.
Pretty quickly I became interested in this language. I liked that certain problems are solved with the support of related math. Then, I admitted to myself that I didn’t quite want to be a physicist. It was difficult, but for a long time there was not a single moment when I regretted it. I studied functional programming excitedly, without even considering that I could ever work and use it. At least not for money.
Now I’m a little bit sad that I entered this area so late. I constantly need to catch up with something, but better late than never!
Does knowing Haskell help you in science over colleagues?
Definitely, yes. As it turned out, the language is quite suitable for writing code quickly, which, in fact, is really necessary in physics.
Maybe you can tell us more about the Haskell course you’re teaching to other physicists?
Sure. My course is based on the Serokell ITMO course, with minor changes. The main goal of the course is to give enough knowledge and experience to put the language into practice. In particular, at the end I will go through popular and often used libraries. Therefore, the course turned out to be quite extensive and difficult. I give a lot of homework, plus, at the end of the year or in the summer there will be individual projects.
I warned people in advance that the course would require a decent amount of time from them, but it would also give them a lot in return, not just being an overview course or an introduction to the language. Nevertheless, a significant part of people did not pull the combination of this load with the rest of their studies on the eve of the session. The course runs 2 days a week for 1.5 hours.
How is it different than teaching programmers and helping people here, at Serokell?
Usually, people come to me with zero knowledge in programming or at a very basic level, like the introductory semester of 12 courses of the physics department. This makes learning a language even more difficult due to the lack of a “background”. They do not have the intuition that helps programmers learn new areas of IT.
In addition, I explain some basic algorithms and data structures. This is an interesting experience; however, it requires a lot of time, both from me and from my students.
How did you get around to doing that?
I like and enjoy teaching. Previously, I taught a course on the history of the late Roman Empire for 5 years. Teaching helps to systematize one’s knowledge. When you are looking for a clear way to explain a concept, you understand the concept by yourself better than before, even if it seemed obvious. I decided to teach Haskell with several goals:
I would like this language (or at least its ideas) to become mainstream in the industry, and so I at least slightly increase the likelihood of this.
I would like my achievements made during the writing of the diploma to not sink into oblivion if I decide to leave the faculty.
The more people know the principles of FP, even if they don’t use it, the greater the likelihood that it will be used somewhere, because current students will someday become specialists, and then leaders. And I would like for some of my current students to become my colleagues in the future:)
What materials do you use when you prepare your courses?
For the introduction, I use Denis Moskvin’s lectures on lambda calculi and Pierce’s type theory. I’m actively using Graham Hutton’s textbook “Programming in Haskell”, “Learn You a Haskell for Great Good!” and “Thinking with Types”.
For homework, in addition to the above, I use tasks from the course of Denis Moskvin on Haskell on stepik.ru.
As a teacher, can you say that Haskell seems to be more popular with years among the students?
It’s not easy to say, as it is my first fulltime course. But I think this will not happen.
Now I have the impression that Haskell is good as a tool for largescale development of complex applications. But studentsnaturalists (physicists, chemists, etc.) often need just an auxiliary tool. Such as, for example, Python, in which you can write a simple script quickly and without going into the details of the programming language.
My course is purely voluntary and some of the students have stopped attending it. There are only those who, apparently, have already decided to connect their lives with IT. They are ready to spend a lot of their time on selfdevelopment in this area (2 lessons per week, often 3 + homework).
Perhaps, there is a way to interest in Haskell those who don’t plan to be programmers, but this requires a quick overview minicourse. This might be a good idea, but my current course is in a different format.
We wish Rinat the best of luck in achieving his goals with the Haskell course! Eager to learn more? Check out his work on processing laser beams and investigating machine learning with Haskell.
]]>2019 was a great year for us, and for Haskell overall.
While I am contractually obliged to praise a bit biased towards Haskell, it did actually seem that more companies were using Haskell in production and searching for Haskell employees this year, which, hopefully, is a tendency that will persist.
To close out the year, I’ve asked my coworkers about notable highlights from this year and separated the answers into 4 groups: releases, books, talks, and blog posts.
Disclaimer: this is not meant to be an extensive list, think about it more like a “nice conversation starter”.
GHC is the standard compiler for Haskell, and November saw the release of the alpha version for GHC 8.10.
It brought new extensions:
Even if you don’t write complex kinded code, you can use standalone kind signatures to have some practice in thinking with kinds: writing kind signatures explicitly. Just like you could write f x = x + x
and let the compiler infer types, and you could write
f :: Num a => a > a
f x = x + x
to check if you understand what you wrote correctly, you can now write
type MyEither :: Type > Type > Type
data MyEither a b = Left a  Right b
to make sure that the types you write are shaped the way you intended.
GHC 8.10 also features several improvements in code generation, and other upgrades beyond that.
Want to contribute to GHC? Check out this contributor’s cheatsheet by Vlad: https://ghc.dev/
Cabal is a package system for Haskell software that helps us to easily distribute programs and use software created by others.
Version 3.0 of Cabal was released in August, and it finally has moved on to Nixstyle (reproducible) local builds that combine the best of nonsandboxed and sandboxed Cabal.
You could previously access them through cabal v2build
, but now they have become mature enough to be the main type of build for Cabal. With Cabal switching hell to heaven, stackage becomes more and more of a social phenomenon rather than a technological advance, which is—undoubtedly—a good thing.
polysemy
is a library that implements “effects” as seen in languages like PureScript. In its capabilities (no pun intended), it’s similar to mtl
and freersimple
, but it is supposed to be advantageous to other libraries for several reasons.
If you want a beginnerfriendly tutorial on the library, you can head here.
Haskell is a great tool for writing compilers for other languages, and one of those is Agda, a dependently typed programming language / interactive theorem prover.
In 2019, we got the release of Agda 2.6. The key idea of the release is that it goes deeper down the rabid hole of type theory. The reader might be aware that there’s a lot of hype around homotopy type theory lately, however we were yet to obtain a practical computational framework for it. Agda 2.6 bridges that gap. It introduces something called Cubical mode, which adds the pillars of HoTT into the computational system of Agda.
To get to know Agda better, you can read this introductory post by Danya Rogozin.
A book by Sandy Maguire, the author of polysemy
library mentioned above, Thinking with Types is an extremely detailed guide to typelevel programming in Haskell.
While it has managed to dodge getting mentioned in our Haskell beginner guides (here and here), it is a good option for a levelup if you are already a competent Haskell developer.
Meant to be a comprehensive guide to optics in functional programming, Optics by Example by Chris Penner features both introduction to stuff like Lenses, Folds, Traversals, and yes, Prisms, and a deeper delve into the details.
To give an introduction of what you might get there, you can check out the Adventure of Optics blog posts by the same author on his blog.
Monadic party is a 4day long summer school that features lengthy talks and workshops that will make you into a better Haskell programmer.
All the videos from this year’s Monadic Party are published here, and I recommend you to check them out as they are quite practical and informative even for beginners.
In this one, Paweł Szulc shows the magic behind some of the frequently used Haskell libraries (including the aforementioned polysemy
). It’s very clearly explained, informative and the presence of alpacas also makes it quite fun.
Recursive types, dependent types, propositions as types, identity types, types, types, types.
A great introduction into type theory here, and more.
Simon Peyton Jones showing how the type inference engine of GHC actually works behind the scenes, coupled with his trademark ComicSanspowered presentation style.
Unfortunately, the recording cuts out at the end, but you can view all the slides here.
Several of my colleagues said it’s worth watching.
And now for an extra round:
While not a highlight in the sense of the popularity of the talk, it is a highlight for the progress made that it actually describes: how dependent types are used in Haskell to write correct, fast, extensible and maintainable programs that achieve things.
At Serokell, we believe that Dependent Haskell is the future and act on it; it’s nice to know that we are not the only ones.
There were a ton of great “Why Haskell” posts this year (we promise we will make one as well :) ), but if I had to choose one, I’d choose this one.
In the article, Haskell code is compared sidebyside with Python code to show how some features of Haskell like pattern matching and algebraic data types make code much cleaner.
It’s short, practical, and something I wouldn’t be embarrassed to show to a friend that uses Python.
Talking about the visibility of production Haskell, we compiled places where Haskell is used (and it’s used even in large companies like Facebook and NASA!). A larger list is coming next year; in the meantime, enjoy our list of length 6.
https://tech.channable.com/posts/20190313howwemadehaskellsearchstringsasfastasrust.html
Clean, clear, practical, and with images!
In this post from Channable, they describe how they optimized their Haskell implementation Aho–Corasick string searching algorithm to a speed faster than a Rust implementation of the same algorithm.
https://lexilambda.github.io/blog/2019/11/05/parsedontvalidate/
“You, dear reader, ought to be parsing!”
Learn how to make types work for you in this extremely wellwritten blog post about typedriven design.
https://captjakk.com/posts/20190512practicalintroeff.html
Freer monads is one of the ways of structuring effects in Haskell programs, and this is a clear and (as it says) practical introduction to them.
We see great trends in Haskell this year. Its adoption has increased, there are more and more success stories of people using it in production. Efforts of grand unification (which are so close to our hearts) are yielding fruit, and people fight boilerplate in creative ways, shutting down the whole “too much typing with strong typing” argument. Here’s to productive 2019, and let’s make the 2020 even better!
Is there anything else you would like to add to the list of highlights? Let me know on social media like Facebook and Twitter.
]]>In this post, we would like to provide our developers’ view on the best projects written in Haskell that they use regularly, in some cases — daily.
This is a classic, a Haskell project with significant impact and wide usage.
Pandoc is a free Haskell library that converts text from one format to another. Here are just some of the formats that this library supports: commonmark, docx, epub, fb2, gfm and so on.
It also features a powerful and simple way to do everything a text processor can do by just writing plain text. It’s called Pandocflavored Markdown and it’s amazing.
Now you know what all those “convert PDF into DOCX online without registration and SMS” websites use under the hood!
XMonad is a tiling window manager. What does it mean?
To ensure optimal UX, in 1981 Xerox came up with the notion of tiling application windows. This means that the window manager arranges windows automatically in such a way that they don’t overlap! Sadly, like many powerful arts, this art was forgotten by mainstream users (up until recently).
XMonad is one of the best tiling window managers out there. While it has very good defaults, it can also be customized to your heart’s content. Furthermore, it’s minimalistic, easy to use, and “haskell + smart programming practices guarantee a crashfree experience”.
There are a bunch of launchers on Linux, but the fastest, leanest and meanest is dmenurun. Unfortunately, it is also not that wise, it just searches for an application based on the string you provided. Therefore, we use this application that wraps dmenu and enables fuzzy search.
To give an example: Let’s say you are an ebook fan. If you type ca
, it predicts that you want to read a book using calibre
. When you just invoke the launcher, it knows that you likely want to do things like turn on the browser or movie player, or tweak the sound settings. You don’t need to think, it reads your mind. And yes, it’s written in Haskell.
If you’re into pretty stuff, you might want to have a more graphical launcher. Well, as a matter of fact, we (yeah, literally “we”) got you covered. For the price of 5MiB of RAM and an additional 20MiB operational footprint, you can get a launcher with configurable hotkeys called “Lambdalauncher”.
It’s made by one of our employees and it’s very extendible and configurable. Inspired by Albert launcher, it’s probably the best compromise between the speed of operation and the speed of use.
If you are a fan of the quantified self movement like some of our employees, tracking time for various tasks done at the computer can be an annoying process. How to make it easier? With Haskell of course.
Straight out of my software toplist, arbtt is an amazing crossplatform time tracker that automatically records data about your activities and writes it into an encrypted file. Later on, you can query and analyze your data. Since it’s crossplatform, you can cover most of your laptop and desktop computers, no matter whether they are running Linux, macOS, or even Windows.
Taffybar is a Haskellwritten window manager bar for desktops. Its coolest feature is integration of advanced set of widgets that a programmer can use to quickly and easily visualize data. If you use the config file (~.configtaffybar/taffybar.hs), you can tailor the default widget bar to your own exquisite taste. We must say that the program is pretty similar to xmobar but we put it before anything else because of a better widget set to which you can as well contribute.
Taffybar creates widgets using GTK and Cairo. This means that you don’t have to get extra work done if you want nicely looking and quick rendering charts. Mind that taffybar is a library that you have to install alongside with a Haskell compiler (GHC) if you want to create tailormade executable files.
If you think that Haskell is an obscure language, think twice. Haskell is probably more intuitively clear for children who know only the basics of maths than for Python or, God forbid, Javaexperienced developers.
The language that really is obscure to the point it becomes unsafe and that is extremely overused is standard Shell scripting language. Three people in the world who know how to write idiomatic code in shell got together and wrote a brilliant tool that checks if your scripts are idiomatic. They even have a gallery of bad code.
If you don’t want your Shell scripts to end up there, use ShellCheck for every shell script you intend to run more than once!
With gitannex you can work even with such large volumes of data, that do not open in GitHub. How does it happen? With gitannex, you track filenames and metadata instead of content, leaving content in more convenient places: drives, cloud, etc.
Gitannex lets you support the resources, manage the intentional and unintentional copies of files and keep control of the versions. It saves the files you need while you’re working in offline mode on the local device. During the next connection to the remote server, all the data will synchronize, saving space on your gadget and keeping the routes to all the files.
For an extensive introduction and description of use cases, check out this video.
Here is an example of a gitannex repo you might find interesting: ocharles repository of CS papers.
Why did they use Haskell for writing gitannex? From what gitannex is not: “gitannex is not some flaky script that was quickly thrown together. We wrote it in Haskell because we wanted it to be solid and to compile down to a binary.”
Tired of writing ugly stateful UIs? Use Gigtkdeclarative library to make other stuff. It was used to make Lambdalauncher, for instance.
Declarative markups save your time and nerves when you develop the user interfaces. UI created in Haskell with Gigtkdeclarative package are easily validated by the HTML standards, well recognized by “virtual DOM” and enabled by technologies like Electron.
Gigtkdeclarative package is a bit raw now for the commercial usage, but it’s functionality is headspinning, so we fix our attention on this project’s future steps in extending the haskellgi family.
At Serokell, we write quite a bit of Nix code. But here we won’t be convincing you that you need to use Nix for all your building needs, even though it is true.
If you already know about and use Nix, though, we have created an opinionated formatter for Nix code. Feel free to try it out or contribute.
If you want to learn more about the formatter and our design choices, you can watch Lars’ talk from NixCon.
The terms “blockchain” and “distributed ledger technology (DLT)” are very often used as synonyms. Guess what: they are not! So, if you don’t want to look like a weirdo in front of your colleagues, read on.
Distributed ledger technology (DLT) is a way of storing information. Let’s analyze it word by word.
So, first, there is “ledger”. A long time ago, when people had no idea about the Internet, electronic cash registers, and other wibblywobbly hitech, they would put information about their transactions in a regular book called a ledger. Imagine: you go to a bank to ask for a credit, and the clerk makes a record on paper about how much money you took and when you need to restore it.
Is there any problem with storing information like this? A bunch of them, of course.
Theft. Anyone can steal a ledger, delete or change the information: your creditor, other bank employees or even you.
Human factor. It’s easy to write $100,000 instead of 10,000 intentionally or by mistake, which will be an unpleasant surprise for you as a borrower.
Force Majeure. All the recordings can get destroyed by natural reasons like a flood or a fire.
The thing is that keeping records on a regular server or cloud database today is not much different in terms of security than just storing it on paper. Someone can hack it, or the server can crash by itself (the problem of Single Point of Failure).
So, keeping all the eggs in one basket is not a good solution. What shall we do?
Make copies.
This is where the word “distributed” steps into the game.
Distributed means that the information from the book is kept, administered and used by all members. It’s still a book, or to be more precise, a database, but it’s spread across all the participants of the DLT network. These are also called nodes.
How do you ensure that the same data is seen across the network without any central authority in power?
In 1991, the researchers Stuart Haber and W. Scott Stornetta asked themselves the same question. They proposed practical methods for timestamping digital data.
Follow their logic:
These principles basically gave birth to DLT.
In 2002, David Mazières and Dennis Shasha continued to develop the concept, studying how to store data in blocks. They were working on a protocol of a multiuser network file system called SUNDR (Secure Untrusted Data Repository). The fruits of their work laid the ground for the blockchain of today. After the appearance and spread of blockchain, the history of DLT became the history of blockchain.
In a distributed ledger system, all nodes have their copy of the ledger and update information independently.
To make a change, they need to go through a mechanism of consensus where the nodes collectively agree for the change to be introduced. This is how we make sure that the copy of the ledger is the same in all the nodes.
There is a multitude of ways to do this, and the choice of the consensus mechanism depends on how large tolerance for faulty actors do you wish your system to have and several other constraints. While consensus can technically be achieved with just a vector clock, it is much more popular to use protocols like Paxos and pBFT.
So, all in all, the definition of distributed ledger goes as follows:
A distributed ledger technology is a decentralized database distributed across different nodes of the network. Every node views all the records in question and processes every transaction. The nodes collectively vote on every item’s veracity guaranteeing trust and transparency under certain conditions.
DLT has gained a wide popularity thanks to its multiple benefits over centralized data storage systems.
Transparency and immutability. Unlike in a centralized system, all nodes enjoy equal rights over the data. All the decisions are made collectively. DLT provides an immutable and verifiable audit trail of all operations.
Attack resistance. DLT is a more cyberattack resilient system than traditional centralized databases because it’s distributed. There is no single point of attack, which makes the attempts to hack such systems too expensive and useless.
Now let us get back to the blockchain. Why does everybody mix these two terms?
The answer is that blockchain is indeed a distributed ledger system. Blockchain users also have decentralized control over data, and many nodes participate in the distribution, administration, and change of data.
What matters is that blockchain is a specific type of DLT. It looks like a sequence of blocks of information. Each of them depends on the previous block and the following, which does imitate the construction of a chain.
Here are the differences between blockchain and DLT:
Available operations. In a traditional database technology, four operations are available: Create, Retrieve, Update and Delete (CRUD). In a blockchain, you can only use Create and Retrieve operations.
Block structure. Blockchain represents data as a chain of blocks, which is not mandatory for other types of DLT.
Sequence. Distributed ledger technology doesn’t have to follow the block after block structure of the blockchain.
Tokens. Blockchain is generally a token economy, but DLT doesn’t require their usage.
Blockchain is the most popular type of DLT. However, it’s not the only one.
The most popular types of DLT that are used in industry today can be divided into three groups:
Public. This is a decentralized system where any two parties regardless of their location can transact. Public DLT relies on the consensus of all the nodes.
Private. Often used by enterprises as a corporate database. It’s a permissioned network meaning that different ledgers are still synchronized across the nodes. However, there is an owner who has the power to decide who will get access to the network.
Consortium. Consortium DLT is used by an association of companies that share equal rights over the network. The system lets multiple businesses use the DLT as a decentralized system. These are also called federated DLTs.
Serokell is one of the teams of independent researchers and software engineers who developed the Cardano project. It is an opensource decentralized public blockchain. The purpose of Cardano was to provide the users with a smart contract platform that overcame common security flaws, had decreased transaction costs and improved network speed.
We developed the Cardano Settlement Layer cryptocurrency and a wallet for CSL.
Cardano uses a ProofofStake consensus algorithm. This choice allowed to introduce some fresh features to CSL. Let’s talk about them more in detail.
This feature allows a node to be offline but still have an impact on the system. Delegation isn’t compatible with PoW where everybody should be present in order to vote. There are two types of this feature that the users can benefit from using Cardano.
How does it work? Imagine being in a board of directors of an enterprise. All the members have shares, attend meetings and vote on decisions regarding the company.
Another option is to give the proxy only to your representative who will show it to the others on demand whenever needed to vote on your behalf. If you come to the meeting, the others will just ignore the proxy.
This feature allows all users to vote for proposed updates. Any user can suggest an update to the system. The others will have a look at it, make sure it’s safe and won’t allow anybody to abuse the system. If it’s okay, they cast their shares for the update. So, the system is selfregulated: a user proposes updates, other users vote. If there are enough votes, the system will be updated.
You can write code, send it to all nodes in the system, and the code will run on them. Every node will check whether the code is correct, for example, that it doesn’t waste money it’s not allowed to spend.
This concept provides an ability to write applications over CSL. For example, you can write your own gambling platform. Its advantage is that the processing of the game happens not on one server, which may be corrupted but on multiple nodes that execute the code. Even if one of the nodes is corrupted, the others will say: you argue the results of the execution are this, but we think otherwise. Since they prevail, nobody counts the corrupted node.
Overall, CSL has tried to bring up a scientificdriven approach to development for the construction of a whole new community. After all, cryptocurrency is much more than just technical decisions, algorithms and coding. It’s a community of people who believe they are doing the right thing, which may help to build a better fintech future for the whole world.
Now you can tell the difference between a DLT and a blockchain. You’ve learned about the advantages and disadvantages of blockchain as opposed to other types of distributed ledger systems. These technologies represent a new way of storing and processing data that is being adopted by more and more companies across different industries worldwide like healthcare, law, education and so on.
Stay tuned to our blog and follow us on social networks like Twitter for more engaging materials about cuttingedge technologies.
]]>Hello, my name is Ivan Gromakovskii, I am a software developer at Serokell. I’ve been working on several relatively big projects during the last few years where I was one of the main contributors at the project. Those projects include:
Two of them are hosted on GitHub and one is hosted on GitLab. I’ve been working on some other projects hosted on these platforms as well. These two platforms are among the most popular ones for Git repositories, and in this article, I want to compare them based on my experience. It should help people who are choosing between GitLab and GitHub for their new project or who consider switching from one platform to another. In general, all people who use GitLab or GitHub may discover some new features here.
Disclaimer: both GitHub and GitLab are actively developed and new features appear there from time to time. If some feature is present in only one platform, there are high chances it will be added to the other one at some point. So this comparison will inevitably become somewhat outdated sooner or later.
Let me briefly describe the way we write code and work with Git.
Therefore, we are primarily interested in two things:
Comparison is split into 4 parts:
I will not compare the design of these platforms because it is a very subjective thing and I am not competent to compare web designs anyway. I will cover only features and UX.
Let’s open some file on GitLab and GitHub.
Both platforms offer some fundamental features including:
git blame
), history (commits which modify a given file).git
.
For some people it might be convenient to edit files in a browser, but it is just not powerful enough.
For instance, you can’t edit more than 1 file in one commit, can’t sign commits, run precommit Git Hooks.y
hotkey which might be hard to discover.
The way I learnt about it is that someone just told me.
GitLab has a button in UI to do it which should be useful for those not familiar with y
.
I find this feature very useful and wish that people were using it more often.
In practice, people often send a link to a file bound to a particular branch rather than code revision and it refers to a completely different or nonexisting place after a while.Shift
and click on another line, the range of lines between these two will be selected.And here are some differences:
with import (builtins.fetchGit { url = https://github.com/NixOS/nixpkgschannels; ref = "nixosunstable"; rev = "971b731fc18c86569211a460ef62e1d8001799e9"; }) {}; haskell.lib.buildStackProject { name = "myEnv"; buildInputs = [ ghc zlib ]; buildPhase = '' export LANG=en_US.UTF8 ''; }
y
, GitLab will reload the page and this reload is not instant.
On the other hand, GitHub only changes the URL in my browser without reloading anything, it happens instantly.Apart from viewing files and folders, we may want to look at commits.
Let’s open lists of commits from master
in some repos:
They look very similar, you can see which commits are signed with verified signatures, copy identifiers, browse files at some revision, choose another branch.
The only difference is that GitLab makes it easy to filter commits by the commit message. I couldn’t find such a feature on GitHub (you can use repositorywide search and select “Commits” category there, but it’s less convenient).
Now let’s see how commits are displayed.
In both cases, you can switch between “Inline” and “Sidebyside” view (“Unified” vs “Split” in case of GitHub). You can open the whole repository or a particular file at a given commit. You can see the total number of changed files and lines. You can leave a comment at some line or the whole commit.
There is one cool feature that not everyone is aware of and the only difference I’ve discovered is related to it.
If you add ?w=1
to the commit’s URL, whitespace changes will be ignored.
GitLab has a button in UI to do it, but I couldn’t find such a button in GitHub.
Finally, I can’t but mention that both platforms have a variety of hotkeys helpful for browsing and navigation. In particular:
t
to search for a file.y
to expand URL to its canonical form.l
to jump to a line (GitHub only).gp
to go to Pull Requests on GitLab and ShiftM
for GitLab.s
or /
to go to the global search.?
opens a list of shortcuts.Full lists of hotkeys can be found here:
I do not actively use all these hotkeys, just a few ones, so it is hard to say who is the winner here. GitHub seems to have more hotkeys, but the most useful ones seem to be present on both platforms.
Let’s open random pull requests on GitHub and GitLab.
We can already see many features present in both cases. The visible differences are mostly in design and I will not cover them here. However, when we go deeper I will write about various differences. Here are the common features:
So far, we have seen only the main page of each PR. There are 3 more tabs: commits, pipelines/checks, and changes. Let’s have a look at them.
Tabs with commits are very similar:
GitHub has a button to browse the repository at a certain commit and displays CI status for each commit for which it was run. Also, GitHub shows the oldest commits first while GitLab shows the newest first. It is slightly inconvenient when you work with both platforms.
Personally, I (almost) never use Checks
and Pipelines
tabs, so I won’t write about them in this post.
Let’s proceed to the Changes
tab where most of the code review happens.
We can see a cool feature of GitLab: file browser. It shows a tree of the changed files (you can change it to show a flat list instead) and the number of changes in each of them. For me, this feature is very useful.
Even though I do not want to talk about design in this post because it is a subjective thing, I can’t but mention one design difference because it affects usability. As you can see, in GitHub changes occupy the whole width of the space that the browser gives to the webpage. While in GitLab there are other elements: on the left, there is a column with buttons to open Issues, main repository page, etc. Then there is a file manager and on the right, there is another column with buttons specific to the current PR. It may cause inconvenience on narrow screens. For example:
In such a case, it’s almost impossible to review anything. Fortunately, it is possible to collapse the file browser and the right column. The left column is collapsed automatically if necessary.
If you collapse everything, the changes will be reviewable. So I would say that GitLab provides the better experience on wide monitors, while on narrow monitors it’s almost the same except that you have to manually collapse some elements.
Let’s list the basic features of the “Changes” tab common for both platforms:
Now that we have seen all the tabs, let’s proceed to details and compare particular features related to pull requests:
In GitHub, it is very easy and convenient to request a review from somebody.
Moreover, you can request it again, for instance, if someone requested certain changes and you have made them.
GitLab apparently does not have this straightforward notion of reviewers and requests for review.
It is possible to add approval rules there: e. g. require that n
people out of a certain set of people approve the PR in question.
It seems to be more complicated and inconvenient for daytoday usage, at least in our workflow.
Alternatively, you can use the Assignee
field to request a review.
It is often the case that your PR’s branch is behind the target branch, i. e. does not have some commits present in the target branch. There is a setting that prevents PRs from being merged if they are behind the target branch and we usually enable it. GitLab shows how far (i. e. by how many commits) your branch is behind the target branch. It also allows you to rebase your branch onto the target branch. GitLab, on the other hand, allows you to merge the target branch into your branch and displays conflicting files if there are any. Of course, neither rebase nor merge are possible if there are conflicts (there are some capabilities to resolve conflicts in a browser, but I guess an average developer will prefer doing it in their local repository). Here is how it looks in GitLab:
And in GitHub:
Another GitLab feature is selfapproval. When enabled, it allows you to approve PRs created by you. It is useful in some cases. For instance, if you create a PR from a branch where someone else was working. Or if you started some work, made a PR, but then someone else finished your work. It also makes it more clear whether the author thinks that all the work is done, though for this purpose setting Draft/WIP status is more natural.
Speaking of Draft/WIP status, there is a difference as well.
Both platforms allow you to explicitly say that your PR is not ready to be merged.
In GitHub, you can create a “Draft” PR, and in GitLab, you can start the tittle with WIP
prefix.
The essential difference is that WIP
status can be added and removed at any time, while Draft status can only be changed from Draft to Ready, but not in the opposite direction.
I. e. if a PR is not a Draft, it can not be changed to Draft.
GitHub allows you to approve a PR or explicitly “reject it” (request changes). The latter prevents it from being merged. In GitLab, you can only explicitly approve, but not explicitly reject. You can reject by writing something in comments, but it won’t disable merging.
GitHub recently introduced a new feature which allows you to mark changed files as viewed and keep track of viewed files.
Under the hood, PR information generated by GitHub is static HTML, while GitLab loads its information with JavaScript as you go.
Last item is about a relatively new feature of GitHub: multiline comments. When you review changes in a PR, you can select multiple lines and leave a comment for all of them. Note that it is possible only in a PR, but not for a commit outside of any PR.
One of the biggest and most important features of GitLab that has not been mentioned yet and is not present in GitHub is the integrated CI/CD system. There are many free CI systems that one can easily setup for their GitHub repositories, but using an outofbox solution is usually more convenient. You don’t have to depend on another service and open another website in your browser to get the details of a build.
GitLab has a container registry, so you can have CI build your software and put it into this registry. After that, one can pull it using Docker and use on their machine.
Another feature is file locking. It allows you to prevent some file or directory from being modified.
Apart from features, I want to point out that I was getting error code 500 and some other errors from GitLab several times. Also, sometimes there was weird behavior by GitLab where it didn’t allow us to merge a PR even when all preconditions (pipelines, approvals, etc.) were satisfied. Sometimes it was sufficient to force push to the PR branch, in other cases the only solution was to recreate the same PR. I don’t recall internal errors from GitHub (perhaps I encountered one or two, but that is a very rare case). So, GitHub seems to work more reliably lately (but maybe we are just lucky).
Both GitLab and GitHub offer most of their features for free and have paid plans for those who need more. Here is a brief overview of what you may get if you pay.
Let’s start with GitLab, this table provides a brief summary of what you can get for which price:
The only important feature that is not available for free is PR approvals. In our workflow, we require PR to be approved before it can be merged. There is one cool thing not mentioned there: “public projects are considered to have a Gold subscription level”. Even with the Gold subscription level available in public projects, I think I haven’t used anything else (beside approvals) that is not freely available. If you are interested in an indepth feature comparison, you can find it here.
GitHub offers different plans for individuals and teams. In all cases, you get unlimited public and private repositories just like on GitLab. Here is a summary of GitHub plans:
Looking at this table, one may think that there is no way to have an organization on GitHub for free. However, if you try to create a new organization, you will see that there is a “Team For Open Source” option which is free.
As you can see, this option allows you to create only public repositories. Apart from that, it seems to be the same as “Team” which costs $9 per user per month.
Here is a short comparison:
In this article, I have compared two platforms for hosting Git repositories which are very popular nowadays: GitLab and GitHub. In general, they offer similar sets of features, but there are various differences if one pays attention to details. Both have some pros and cons. I would like to finish this article with a table which summarizes the advantages of the platforms:
GitLab  GitHub 

A button to merge a PR when CI passes  More natural way to request a review, ability to rerequest it. 
Shows how far your branch is behind the target branch and allows to rebase on it  Shows conflicting files and allows to merge the target branch 
Reply to PR comments (start a discussion/thread under a comment)  Multiline comments 
UI buttons to hide whitespace changes and get permalinks (easy to find, unlike hotkeys)  Getting a permalink is instant and works for folders 
Shows the number of resolved discussions  Allows to mark files as viewed 
Cheaper unless you want something very advanced  Subjectively more stable and reliable 
UI to filter commits  
File browser  
Own CI and CD 
Of course, there are other features and differences omitted in this post simply because I do not use them or do not even know about them. However, I have tried to cover functionality that is most commonly used by developers. If you are in doubts about which platform to choose, hopefully this article will help you make the choice.
]]>However, if you’re an absolute beginner, you have an advantage. FP is hard only because it’s so strictly opposed to what every programmer is used to in their work. By itself, functional programming is completely logical.
To help you dive into the world of FP, today we will share useful resources that can help you become a Haskell developer even if you have zero experience in functional computing.
We advise to balance these three components:
Anyway, our point is if you’re going to read a Haskell manual for 10 minutes a day, you aren’t going to become a programmer even in a hundred years. But if you combine reading a manual with watching at least 10 minutes of video courses daily and doing 10 minutes of real coding each day for a couple of months, at the end you will be able to program something valuable in Haskell.
Books are certainly useful, but don’t get too obsessed with theory.
What it is: An online manual which debunks the myth that learning a programming language should be boring.
What will you learn: Everything that a Haskell beginner needs to know – types, composing functions, doing I/O, functors, monads, etc. Unfortunately, there are no exercises involved so you will have to find them elsewhere or invent your own.
Price: Free, but if you would like to support the project, purchase a hard copy of the book on Amazon for $32.
What it is: Haskell Programming is about the most important principles of functional programming by someone who understands your pain.
What will you learn: The authors explain FP clearly enough for people who have never programmed before. It’s an inspiring guide from the practitioners that reveals the benefits of Haskell over OOP languages in production. People in Goodreads say it’s the best book about programming they’ve ever read.
Price: You can check out the contents and the first chapters for free on their website or purchase the book for $59.
What it is: An approachable introduction to both Haskell and functional programming that some suggest as the best and most uptodate introduction available right now.
What will you learn: Haskell! All the beginner stuff: manipulating types and functions, I/O, monads. The book ends with a couple of practical projects to try out.
Price: $45 for print book + ebook or 35 for just an ebook.
What it is: A book suggested by the authors of “Haskell Programming From First Principles” for people with zero computing experience.
What will you learn: It introduces functional programming concepts and will teach you enough Haskell to design and write your own pet project.
Price: $60 on Amazon.
This is the stage where theory meets practice. If you don’t have time to study Haskell in university, or you are not in the right location for that, these MOOCs can effectively give you the knowledge and interaction you desire.
What it is: A course from the University of Glasgow about the powerful possibilities of functional programming languages like Haskell.
What will you learn: Nothing less than how to supercharge your coding. The course covers pure functions, types, ADTs, a few common monads and a sprinkle of formal verification. A great course to choose if you have no previous experience in FP.
Price: Free
What it is: Introduction to Functional Programming is an online course taught by Erik Meijer, a computer scientist and contributor to Haskell community. Prior knowledge of programming and computer science basics is advisable.
What will you learn: The professor will walk you through the fundamentals of functional programming, its history and philosophy. He will use Haskell to provide you with the understanding of how to “think like a fundamentalist”. Then, you will be able to apply this knowledge to mainstream programming languages.
Price: Free access but the certificate costs $50.
There are also additional courses on Udemy:
There are a bunch of online coding games out there. Even though nothing can beat Flexbox Froggy, these help you apply your Haskell skills in a fun way that makes the practice a little bit more bearable.
What it is: One of our favorite gaming platforms for programmers. You play the game but all the commands are written in your target language.
What will you learn: Get real coding experience. The game starts simple and gradually progresses to more complicated tasks.
Price: Free.
What it is: A platform where anyone can train their programming skills by solving tasks of different complexity.
What will you learn: You will be able to put what you’ve taught yourself into practice while enjoying the game.
Price: Free.
Even though playing games is fun, and a very great way to procrastinate for hours, at one point you will have to build something.
Fortunately, there is a great way to get rid of existential anguish of notknowingwhattobuild that actually achieves practical improvements in the world around you. Automate something. You know, make the computer do the boring stuff.
At this point, you should be good enough with Haskell to throw yourself into creating programs that achieve realworld results, sit for hours resolving dumb bugs, and curse the lack of documentation in most of Haskell libraries.
So get out there, build stuff that matters and suffer the consequences.
Additionally, there are some more useful materials in our blog. To start, you can read this introductory post. To those who would like to learn more about Haskell community, we recommend to check out the history of Haskell. And, of course, stay tuned to our blog for more amazing materials about functional programming!
]]>Modal logic is one of the most popular branches of mathematical logic. Modal logic covers such areas of human knowledge as mathematics (especially, topology and graph theory), computer science, linguistics, artificial intelligence, and philosophy. Moreover, modal logic in itself still attracts by its mathematical beauty. We would like to introduce the reader to modal logic, fundamental technical tools, and connections with other disciplines. Our introduction is not so comprehensive indeed, the title is an allusion to the wonderful book by Stephen Fry.
The grandfather of modal logic is the 17thcentury German mathematician and philosopher Gottfried Leibniz, one of the founders of calculus and mechanics.
From his point of view, there are two kinds of truth. The statement is called necessarily true if it’s true for any state of affair. Note that, one also has possibly true statements. That is, such statements that could be false, other things being equal. For instance, the proposition “sir Winston Churchill was a prime minister of Great Britain in 1940” is possibly true. As we know, Edward Wood, 1st Earl of Halifax, also could be a prime minister at the same time. Of course, one may provide any other example from the history of the United Kingdom and all of them would be valid to the same degree. In other words, any factual statement might be possibly true since “the trueness” of any factual statement depends strongly on circumstances and external factors. The example of a statement which is necessarily true is $1 + 1 = 2$, where signs $1$, $2$, $+$ and $=$ are understood in the usual sense.
Modal logic became a part of contemporary logic at the beginning of the 20th century. In the 1910s, Clarence Lewis, American philosopher, was the first who proposed to extend the language of classical logic with two modal connectives $\Box$ and $\Diamond$.
Informally, one may read $\Box \varphi$ as $\varphi$ is necessary. $\Diamond \varphi$ has a dual interpretation as $\varphi$ is possible. Lewis sought to analyse the notion of necessity from a logical point of view. Thus, modalities in mathematical logic arose initially as a tool for philosophical issue analysis. Lewis understood those modalities intuitively rather than formally. In other words, modal operators had no mathematically valid semantical interpretation.
In the year 1944, Alfred Tarski and John McKinsey wrote a paper called The Algebra of Topology. In this work, the strong connection between modal logic ${\bf S}4$ and topological and metric spaces was established. Historically, topological semantics is the very first mathematical semantics of modal logic. Those results marked the beginning of a separate branch of modal logic that studies the topological aspects of modalities.
In the 1950s and 1960s, Saul Kripke formulated relational semantics for modal logic. Note that we will mostly consider Kripke semantics in this post.
Later, in the 1970s, such mathematicians as Dov Gabbay, Krister Segerberg, Robert Goldblatt, S. K. Thomason, Henrik Sahlqvist, Johan van Benthem provided the set of technical tools to investigate systems of modal logic much more deeply and precisely. So, modal logic continues its development in the direction that was established by these researchers.
Let us briefly overview some areas of modal logic use:
Modal logic provides a compact and laconic language to characterise some properties of directed graphs and topological spaces. In this blog post, we study Kripke frames as the underlying structures. Without loss of generality, one may think of Kripke frames as directed graphs. It means that formal definitions of a Kripke frame and a directed graph are precisely the same. Here, modal language is a powerful tool in representing firstorder adjacency properties. We realise the fact that not every reader has a background in firstorder logic. In order to keep the post selfcontained, we remind the required notions from firstorder logic to describe this connection quite clearly.
As we will see, modal logic is strongly connected with binary relations through Kripke frames. The properties of binary relation in a Kripke frame are mostly firstorder. We consider examples of firstorder relation properties expressed in modal language. Moreover, we formulate and discuss the famous Salqvist’s theorem that connects modal formulas of the special kind with binary relation properties that are firstorder definable. Anyway, in such a perspective, one may consider modal logic as a logic of directed graphs since there is no formal difference between a binary relation and edges in a graph. That is, one may use modal logic in directed graph characterisation in a limited way.
You may draw any directed graph you prefer on a plane. A graph is a combinatorial object rather than a geometrical one. A topological space is closer to geometry. Although, there’s a topological way of graph consideration, however. We also discuss how exactly modal logic has a topological interpretation. In the author’s opinion, topological and geometrical aspects of modal logic are one of the most astonishing and beautiful.
It is a wellknown result proved by Alfred Tarski and John McKinsey that the modal logic ${\bf S}4$ is the logic of all topological spaces. Similarly to directed graphs, modal logic might be used in classifying topological spaces and other spatial constructions in a restricted way. We will discuss the topological and spatial aspects of modal logic in the second part.
The foundation of mathematics is the branch of mathematical logic that studies miscellaneous metamathematical issues. In mathematics, we often consider some abstract structure and formulate a theory based on some list axioms that describe primitive properties of considered constructions. The examples are group theory, elementary geometry, graph theory, arithmetics, etc.
In metamathematics, we are interested in what a mathematical theory is in itself. That is, metamathematics arose at the beginning of the previous century to answer philosophical questions mathematically. The main interest was to prove the consistency of formal arithmetics with purely formal methods. As it’s well known, Gödel’s famous incompleteness theorems place limits the formal proof of arithmetics consistency within arithmetics itself.
However, modal logic also allows one to study the properties of provability in formal arithmetical theories. Moreover, the second Gödel’s incompleteness theorem has a purely modal formulation! We discuss that topic in more detail in the followup of the series.
Let us define the modal language. That’s the starting point for every logic which we take into consideration. In our case, a modal language is a grammar according to rules of which we write logical formulas enriched with modalities. Let $\operatorname{PV} = \{ p_0, p_1, \dots \}$ be a countably infinite set of propositional variables, or atomic propositions, or propositional letters. Informally, a propositional variable ranges over such atomic statements as “it is raining” or something like that.
A modal language as the set of modal formulas $\operatorname{Fm}$ is defined as follows:
Note that the possibility operator $\Diamond$ is expressed as $\Diamond$ = $\neg \Box \neg$. In our approach, the statement $\varphi$ is possibly true if it’s false that necessiation of $\neg \varphi$ is true. Also, one may introduce $\Diamond$ as the primitive one, that is $\Box = \neg \Diamond \neg$.
Also, it is useful to have constants $\top$ and $\bot$ that we define as $\neg p_0 \lor p_0$ and $\neg \top$ correspondingly.
As we said, a modal language extends the classical one. One may ask how we can read $\Box$. Here are several ways to understand this modal connective:
We will discuss items 35 in the next part of the series as promised.
We defined the syntax of modal logic above. But syntax doesn’t provide logic, only grammar. In logic, one has inference rules and the definition of proof. We also want to know what kind of modal statements we need to prove basically. Firstly, let us describe a more basic notion like normal modal logic.
By normal modal logic, we mean a set of modal formulas $\mathcal{L}$ that contains all Boolean tautologies; ${\bf AK}$axiom (named after Kripke) $\Box (p \to q) \to (\Box p \to \Box q)$ and closed under the following three inference rules:
The fact that any normal modal logic contains all Boolean tautologies tell us that modal logic extends the classical one. Literally, the Kripke axiom ${\bf AK}$axiom denotes that $\Box$ distributes over implication. Such an explanation doesn’t help us so much, indeed. It’s quite convenient to read this axiom in terms of necessity. Then, if the implication $\varphi \to \psi$ is necessary and the premise is necessary, then the consequence is also necessary. For instance, it is necessary that if the number is divided by four then this number is divided by two. Then, if it is necessary that the number is divided by four, then it is necessary that it’s divided by two.
Of course, within the natural language that we use in everyday speech the last two sentences sound quite monotonous, but we merely illustrated how this logical form works by example. In such a situation, it’s crucially important to follow the formal structure even if this structure looks wordy and counterintuitive. Of course, this sometimes runs counter to our preferences in linguistic aesthetics, although structure following allows analysing modal sentences much more precisely.
If we take into consideration some logical system represented by axioms and inference rules, then one needs to determine what derivation is in an arbitrary normal modal logic. A derivation of formula $\varphi$ in normal modal logic $\mathcal{L}$ is a sequence of formulas, each element of which is either axiom or some formula obtained from the previous ones via inference rules. The last element of such a sequence is a formula $\varphi$ itself.
Here is an example of derivation in normal modal logic:
We leave the converse implication as an exercise to the reader.
The minimal normal modal logic ${\bf K}$ is defined via the following list of axioms and inference rules:
The axioms (1)(8) are exactly axioms of the usual classical propositional logic that axiomatise the set of Boolean tautologies together with Modus Ponens and Substitution rule. The axiom (9) is a Kripke axiom ${\bf AK}$. One may also claim that ${\bf K}$ is the smallest normal modal logic. In the definition of normal modal logic, we just require that the set of formulas should contain Boolean tautologies, etc. By the way, there might be something different from the required list of formulas. For instance, the set of all formulas is also a normal modal logic, the trivial one. The minimal normal modal logic is a normal modal logic that contains only Boolean tautologies, ${\bf AK}$axiom and closed under the inference rules, no less no more. The logic ${\bf K}$ is the underlying logic for us. Other modal logics are solely extensions of ${\bf K}$. Note that modal logic is not needed to be a normal one. Weaker modal logics are studied via socalled neighbourhood semantics that we drop in our introductory post.
We have defined the syntax of the modal logic above introducing the grammar of a modal language. Let us define Kripke frames and models to have the truth definition in modal logic.
Definition A Kripke frame is a pair $\mathbb{F} = \langle W, R \rangle$, where $W$ is a nonempty set, a set of socalled possible worlds, informally. Note that we will call an element of $W$ a world or a point. World and point are synonyms for us. $R \subseteq W \times W$ is a binary relation on $W$. For example, the set $\{0, 1, …, n\}$ with strict order relation $<$ is a Kripke frame. Moreover, any directed graph might be considered as a Kripke frame, where the set of vertices is a set of possible worlds and the set of edges is a binary relation.
Definition A Kripke model is a pair $\mathcal{M} = \langle \mathcal{F}, \vartheta \rangle$, where $\mathbb{F} = \langle W, R \rangle$ is a Kripke frame and $\vartheta : \operatorname{PV} \to 2^W$ is a valuation function that maps any proposition variable to some subset of possible words. Informally, we should match any atomic statement with corresponding states of affairs in which this atomic statement is true. For complex formulas, we introduce the truth definition inductively. We denote this relation as $\mathcal{M}, w \models \varphi$ that should be readen as “in model $\mathcal{M}$, in world $w$ the statement $\varphi$ is true”.
It is not so difficult to obtain the truth conditions for other connectives. One needs to keep in mind that the other Boolean connectives are introduced via implication and bottom as
We also know that $\Diamond = \neg \Box \neg$, so the truth definition for $\Diamond \varphi$ is the following one:
$\mathcal{M}, w \models \Diamond \varphi$ iff there exists $v$ such that $w R v$ and $\mathcal{M}, v \models \varphi$.
There are a lot of examples of Kripke models, indeed. Here, we refer the reader to the book Modal Logic of Open Minds by Johan van Benthem to study miscellaneous cases in depth. Let us consider briefly the following graph as a Kripke frame with the valuation map $\vartheta$:
Also, we will use the following notions:
In logic, we often require that any true formula should be provable. If some logic satisfies this requirement, then we call that the logic is complete. Here, a modal logic $\mathcal{L}$ is a Kripke complete if and only if there exists some class of Kripke frames $\mathbb{F}$ such that $\mathcal{L} = \operatorname{Log}(\mathbb{F})$. That is, any valid formula in this class of frames is provable in $\mathcal{L}$.
So, we’re going to solve the following issue. What’s the most general logic which is valid in an arbitrary Kripke frame? In other words, we are going to characterise the logic of all possible Kripke frames. We defined above what minimal normal modal logic is. One may show that this logic is the logic of all Kripke frames:
Theorem
${\bf K} = \operatorname{Log} (\mathbb{F})$, where $\mathbb{F}$ is the class of all Kripke frames.
The left inclusion ${\bf K} \subseteq \operatorname{Log} (\mathcal{F})$ called soundness is more or less obvious. The soundness theorem claims that the logic of all Kripke frames is the normal modal one. We will prove only that any formula provable in ${\bf K}$ is valid in any Kripke frame and show that $\operatorname{Log}(\mathcal{F})$ is closed under substitution, where $\mathcal{F} \in \mathbb{F}$ is an arbitrary Kripke frame.
Let us show that the normality axiom is valid in an arbitrary Kripke frame.
Let $\mathbb{F} = \langle W, R \rangle$ be a Kripke frame and $\vartheta : \operatorname{PV} \to \mathcal{P}(W)$ a valuation map. So, we have a Kripke model $\mathcal{M} = \langle \mathbb{F}, \vartheta \rangle$. Let $\mathcal{M}, a \models \Box (\varphi \to \psi)$ and $\mathcal{M}, a \models \Box \varphi$. Let $a R b$, then $\mathcal{M}, b \models \varphi \to \psi$ and $\mathcal{M}, b \models \varphi$ by the truth definition for $\Box$. So $\mathcal{M}, b \models \varphi$, so far as Modus Ponens holds in every Kripke model. Thus, $\mathcal{M}, a \models \Box \varphi$.
Now we show that $\operatorname{Log}(\mathbb{F})$ is closed under substitution. We provide only a sketch since the full proof is quite technical.
Let $\vartheta : \operatorname{PV} \to \mathcal{P}(W)$ be a valuation and $\mathcal{M} = \langle \mathcal{F}, \vartheta \rangle$ a Kripke model. Let us put $\varphi = \{ w \in W \:  \: \mathcal{M}, w \models \varphi \}$, the set of all points in a Kripke model, where the formula $\varphi$ is true. Let $\mathcal{F} \models \varphi ( p )$ and let $\psi$ be an arbitrary formula. We build a Kripke model $\mathcal{M} = \langle \mathcal{F}, \vartheta’ \rangle$ such that $\vartheta’ ( p ) = \psi$. Then, one may show by induction that $\mathcal{M}, x \models \varphi ( p := \psi ) \Leftrightarrow \mathcal{M}’, x \models \varphi ( p )$.
The right inclusion (${\bf K} \supseteq \operatorname{Log}(\mathbb{F})$) called completeness is quite nontrivial, one needs to build the socalled canonical model. We haven’t defined what a canonical frame and model are. The idea of a canonical frame is that an observed logic itself forms a Kripke frame (and Kripke model too). Canonical frames and models often allow us to prove the fact that some normal modal logic is complete with respect to some class of Kripke frames. We provide only the main proof sketch. The following construction is very and very abstract, but sometimes one has to be patient to produce fruits.
Let $\Gamma$ be a set of formulas and $\mathcal{L}$ a normal modal logic. $\Gamma$ is $\mathcal{L}$inconsistent, if there exist some formulas $\varphi_1, \dots, \varphi_n$ such that $\neg (\varphi_1 \land \dots \land \varphi_n) \in \mathcal{L}$. That is, the set of formulas $\Gamma$ is $\mathcal{L}$inconsistent, if there exists a finite subset such that negation of its conjuction is provable in an observed logic. $\Gamma$ is $\mathcal{L}$consistent, if $\Gamma$ is not inconsistent.
A $\mathcal{L}$consistent set $\Gamma$ is maximal if it doesn’t have any nontrivial extensions. In other words, if $\Gamma$ is a subset of $\Gamma’$, where $\Gamma’$ is a $\mathcal{L}$consistent, then $\Gamma = \Gamma’$.
Now we are ready to define a canonical frame. Let $\mathcal{L}$ be a normal modal logic, then a canonical frame is a pair $\mathcal{F}^{\mathcal{L}} = \langle W^{\mathcal{L}}, R^{\mathcal{L}} \rangle$, where $W^{\mathcal{L}}$ is the set of all maximal $\mathcal{L}$consistent set. $R$ is a canonical relation such that:
$\Gamma R^{\mathcal{L}} \Delta \Leftrightarrow \Box \varphi \in \Gamma \Rightarrow \varphi \in \Delta$. That is, any boxed formula from $\Gamma$ can be unboxed in $\Delta$.
A canonical model is a canonical frame equipped with the canonical valuation $\vartheta^{\mathcal{L}} ( p ) = \{ \Gamma \in W^{\mathcal{L}} \:  \: p \in \Gamma \}$. This valuation maps each variable to set of all maximal $\mathcal{L}$consistent sets that contain a given variable.
Here comes the theorem:
Theorem
The completeness theorem for ${\bf K}$ is a simple corollary from the theorem above. Any formula that provable in ${\bf K}$ is valid in every frame. If it is valid in every frame, then it is also valid in the canonical frame. Thus, a formula provable in ${\bf K}$. That’s it. The construction above allow us to claim that any formula that valid in all possible Kripke frames is provable in ${\bf K}$. The reader might read the complete proof here.
As we told above, modal logic is strictly connected with firstorder logic. Firstorder logic extends classical logic with quantifiers as follows. Classical propositional logic deals with statements and the ways of their combinations with such connectives as a conjunction, disjunction, negation, and implication. For example, if Neil Robertson will make a maximum 147 break, then he will win the current frame (a snooker frame, not Kripke frame). This statement has the form $p \to q$. $p$ denotes Neil Robertson will make a maximum 147 break. $q$ denotes he will win the current frame. In firstorder logic, one may analyse formally universal and existential statements which tell about properties of objects.
More strictly, we add to the list of logical connectives quantifiers $\forall$ (for all …) and $\exists$ (there exists …). We extended the set of connectives, so one needs to redefine the notion of formula. Suppose also we have a countably infinite set of relation symbols (or letters) of arbitrary finite arity. We well denote them as $P$. Also one has a countably infinite set of individual variables $x, y, z, \dots$.
The notion of formula with such set of predicate symbols:
The other connectives and quantifiers are expressed as follows:
Note that there is a need to distinguish free and bound variables in a firstorder formula. A variable is bounded if it’s captured by a quantifier. Otherwise, a variable is called free. For instance, the variable $x$ is bounded in the formula $\exists x \: R(x, y, z)$. One may read this formula, for instance, as there is exists a point $x$ such that $x$ lies between points $y$ and $z$. Variables $y$ and $z$ are free in this formula since there are no quantifiers that bound them.
Now we would like to realise what truth for a firstorder formula is. Unfortunately, we don’t have truth tables with 0 and 1 as in classical propositional logic. The definition of truth for the firstorder case is much more sophisticated. An interpretation of the firstorder language (as the infinite set of relation symbols) is a pair $\langle A, I \rangle$, where $A$ is a nonempty set (a domain) and $I$ is an interpretation function. $I$ maps every relation letter $P$ of an arity $n$ to the function of type $A^{n} \to \{ 0, 1 \}$, i.e., some $n$ary predicate on a domain $A$. To define truth conditions for firstorder formulas, we suppose that we have an arbitrary variable assignment function $v$ such that $v$ maps every variable to some element of our domain $M$. An interpretation and variable assignment give us a firstorder model, the definition of truth is the following one:
Note that the truth definition for existential formulas might be expressed as follows: $\mathcal{M} \models \exists x \: A(x) \Leftrightarrow \mathcal{M} \models_{\operatorname{FO}} A(a)$ for some $a \in A$.
A firstorder formula is satisfiable if it’s true in some model and it’s valid if it’s true in every interpretation.
We use $\models_{\operatorname{FO}}$ in the same sense as in Kripke models, but with the index $\operatorname{FO}$ to distinguish both relations that denoted equally.
Let us comment on the conditions briefly. The first condition is the underlying one. As we said above any $n$ary relation symbol maps to $n$ary relation on observed domain, that is, $n$ary truth function $A^n \to \{ 0,1 \}$. On the other hand, any variable (which should be free, indeed) maps to some element of a domain. After that, we apply the obtained truth function to the result of the variable assignment. We obviously require that an elementary formula $P(x_1, \dots, x_n)$ is true in the model $\mathcal{M}$ if the result of that application equals $1$. The last fact mean that a predicate is true on elements $v(x_1), \dots, v(x_n)$.
For example, suppose we have a ternary relation symbol $R$ such that we have interpret $R(x,y,z)$ as $y$ lies between points $x$ and $z$. Suppose also that our domain is the real line $\mathbb{R}$. We map our ternary symbol to the truth function $\pi$ such that $\pi(a,b,c) = 1$ if and only if $a \leq b$ and $b \leq c$. We also define the variable assignment as $v(x) = \sqrt{2}$, $v(y) = \sqrt{3}$, and $v(z) = \sqrt{5}$.
It is clear that in the model on real numbers $\mathcal{M}$ the formula $R(x,y,z)$ is true since $I ( R ) (v(x), v(y), v(z)) = \pi(\sqrt{2}, \sqrt{3}, \sqrt{5}) = 1$. The last equation follows from the obvious fact that $\sqrt{2} \leq \sqrt{3} \leq \sqrt{5}$.
The items 2 and 3 are agreed with our understanding of what false and implication are. The last condition is the truth condition for quantified formulas. This condition describes our intuition about the circumstance when a universal statement is true. Let us consider an example. Let us assume that we want to read the formula $A(x)$ as $x$ has a father. Our domain $M$ is the set of all people who have ever lived on the planet. Then the statement $\forall x \: A(x)$ is true since every human has a father, as the reader already knows even regardless of this post.
Now let us return to modal logic and Kripke models. Let us take a look at truth defitions for $\Box$ and $\Diamond$ one more time:
In those explanations, we used firstorder quantifiers over points in Kripke models informally. But we may build the bridge between Kripke models and firstorder one more precisely. Let us assume that we have the following list of predicate and relation symbols:
The translation $t$ from modal formulas to firstorder ones is defined inductively
The translation for diamonds is the following one:
$t(\Diamond \phi)(w) = \exists v \: (R(w, v) \land t(\phi)(v))$
In other words, every modal formula has its firstorder counterpart, a formula with one free variable $w$.
The reader can see that we just mapped modalised formulas the firstorder one with respect to their truth definition. But there’s the question: is the truth of formula really preserved with such a translation? Here is the lemma:
Lemma
Let $\mathcal{M} = \langle W, R, \vartheta \rangle$ be a Kripke model and $a \in W$, then $\mathcal{M}, a \models \varphi$ if and only if $\mathcal{M}’ \models_{\operatorname{FO}} t(\varphi)(a)$.
Here $\mathcal{M}’$ is the firstorder model such that its domain is $W$, and interpretation for the predicate letter is agreed with the relation on an observed Kripke frame. An interpretation of unary predicate letters is agreed with the evaluation in a Kripke model as follows. $\mathcal{M}’ \models P_i(w)$ if and only if $w \in \vartheta(p_i)$. Here $p_i$ is a propositional variable with the same index $i$. In other words, those unary predicate letters allow us to encode an evaluation via the firstorder language.
The lemma claims that a modal formula is true at some point $w$ if only if its translation (in which a point $w$ is a parameter) in the firstorder language is true in the corresponding model and, hence, satisfiable. That is, one has a bridge between truth in a Kripke modal and firstorder satisfiability. If a formula is true in some model at some point, it doesn’t imply its provability in the minimal normal modal logic. We would like to connect provability in ${\bf K}$ and in firstorder predicate calculus. Here is the theorem proved by Johan van Benthem in the 1970s:
Theorem
A formula $\phi$ is provable in ${\bf K}$ iff and only the universal closure of its standard translation $\forall w \: t(\phi)(w)$ is provable in the firstorder predicate calculus.
That is, there’s an embedding of ${\bf K}$ to firstorder logic. The lemma and the theorem above gives more precise meaning of the analogy between modalities and quantifiers which looks informal prima facie.
Modal formulas are closely connected with the special properties of relations on Kripke frames. We mean a modal formula is able to express the condition on a relation in a Kripke frame. Let us consider a simple example.
Suppose we have a set $W$ with transitive relation $R$. It means that for all $a, b, c \in W$, if $a R b$ and $b R c$, then $a R c$. Let us show that formula $\Box p \to \Box \Box p$ is valid on frame $\langle W, R \rangle$ if and only if this frame is transitive. For simplicity, we will use the equivalent form written in diamonds: $\Diamond \Diamond p \to \Diamond p$. It is very easy to show that these formulas are equivalent to each other:
Lemma
Let $\mathcal{F} = \langle W, R \rangle$, then $\mathcal{F} \models \Diamond \Diamond p \to \Diamond p$ iff $R$ is transitive.
Let $\langle W, R \rangle$ be a transitive frame, $\vartheta$ be a valutation and $w \in W$. So, we have a Kripke model $\mathcal{M} = \langle W, R, \vartheta \rangle$. Suppose $\mathcal{M}, w \models \Diamond \Diamond p$. We need to show that $\mathcal{M}, w \models \Diamond p$. If $\mathcal{M}, w \models \Diamond \Diamond p$, then there exists $v \in W$ such that $w R v$ and $\mathcal{M}, v \models \Diamond p$. But, there exists $u \in W$ such that $v R u$ and $\mathcal{M}, u \models p$. Well, we have $w R v$ and $v R u$, so we also have $w R u$ by transitivity. Hence, $\mathcal{M}, w \models \Diamond p$. Consequently, $\mathcal{M}, w \models \Diamond \Diamond p \to \Diamond p$.
The converse implication is harder a little bit. Let $\mathbb{F} = \langle W, R \rangle$ be a Kripke frame such that $\mathcal{F} \models \Diamond \Diamond p \to \Diamond p$, that is, this formula is true for each valuation map. Let $w, v, u \in W$ such that $w R v$ and $v R u$. Let us show that $w R u$.
Suppose, we have the valuation such that $\vartheta ( p ) = \{ w \}$. So, $\langle \mathcal{F}, \vartheta \rangle, w \models \Diamond \Diamond p$. On the other hand, $\langle \mathcal{F}, \vartheta \rangle, w \models \Diamond \Diamond p \to \Diamond p$, so $\langle \mathcal{F}, \vartheta \rangle, w \models \Diamond p$. But we have only one point, where $p$ is true, $u$. So, $w R u$.
Also we may make this table that describes the correspondence between modal formulas:
Name  Modal formula  Relation 

${\bf AT}$  $\Box p \to p$  for all $x \in W$, $x R x$ 
${\bf A4}$  $\Box p \to \Box \Box p$  for all $x, y, z \in W$, $x R y$ and $y R z$ implies $x R z$ 
${\bf AD}$  $\Box p \to \Diamond p$  Seriality: for all $x \in W$ there exists $y \in W$ such that $x R y$ 
${\bf ACR}$  $\Diamond \Box p \to \Box \Diamond p$  ChurchRosser property (confluence): if $x R y$ and $x R z$, then there exists $x_1$ such that $y R x_1$ and $z R x_1$ 
${\bf AB}$  $p \to \Box \Diamond p$  Symmetry: for all $x, y \in W$, $x R y$ implies $y R x$ 
Let us take a look at the corresponding frame properties on a picture:
More formally and generally, a modal formula $\varphi$ defines (or characterises) the class of frames $\mathbb{F}$, if for each frame $\mathcal{F}$,$\mathcal{F} \models \varphi \Leftrightarrow F \in \mathcal{F}$. That is, a formula $\Box p \to p$ characterises the class of all reflexive frames, etc.
It is clear that our list is incomplete as this blog post itself, but we have described the most popular formulas and the corresponding relation properties. By the way, one may obtain new modal logics adding one of these formulae as axioms and get a botanic garden of modal logics. There are three columns in the following table. The first column is the name of the concrete logic, a sort of identification mark. The second column describes how the given logic is axiomatised. Generally, ${\bf K} \oplus \Gamma$ denotes that we extend minimal normal modal logic with formulas from $\Gamma$ as the additional axioms. The third column is the completeness theorem that claims that a given logic is the set of formulas that are valid in some class of Kripke frames.
Name  Logic  Frames 

${\bf T}$  ${\bf K} \oplus {\bf AT}$  The logic of all reflexive frames 
${\bf K}4$  ${\bf K} \oplus {\bf A}4$  The logic of all transitive frames 
${\bf D}$  ${\bf K} \oplus {\bf AD} = {\bf K} \oplus \Diamond \top$  The logic of all serial frames 
${\bf S}4$  ${\bf T} \oplus {\bf A}4$ = ${\bf K}4 \oplus {\bf AT}$  The logic of all preorders (reflexive and transitive relations) 
${\bf S}4.2$  ${\bf S}4 \oplus {\bf ACR}$  The logic of all confluent preorders (preorders that satisfy the ChurchRosser property) 
${\bf S}5$  ${\bf S}4 \oplus {\bf AB}$  The logic of all equivalence relations 
The fact that a given logic is the logic of some class of frames tells us that this logic is complete with respect to this class. For instance, when we tell that ${\bf T}$ is the logic of all reflexive frames, it means that any formula which is valid in an arbitrary reflexive frame is provable in ${\bf T}$. One may prove the corresponding completeness theorem ensuring that the formulas from the table are canonical ones.
A modal formula $\varphi$ is called canonical, if the logic $\mathcal{L} = {\bf K} \oplus \varphi$ is the logic of its canonical frame. It’s not difficult to ensure that if logic has an axiomatisation with canonical formulas, then this logic is Kripke complete. For example, if we want to prove that ${\bf S}4$ is the logic of all preorders, it’s enough to check that the relation on the ${\bf S}4$canonical frame is a preorder.
Let us remember firstorder logic once more. The first is to rewrite the first table above changing the properties for the third column with the relevant firstorder formulas as follows:
Name  Modal formula  Property of a frame written in the firstorder language 

${\bf AT}$  $\Box p \to p$  $\forall x \: (x R x)$ 
${\bf A4}$  $\Box p \to \Box \Box p$  $\forall x \: \forall y \: \forall z \: (x R y \land y R z \to x R z)$ 
${\bf AD}$  $\Box p \to \Diamond p$  $\forall x \: \exists y \:\: (x R y)$ 
${\bf ACR}$  $\Diamond \Box p \to \Box \Diamond p$  $\forall x \: \forall y \: \forall z \:\: (x R y \land x R z \to \exists z_1 \:\: (y R z_1 \land z R z_1))$ 
${\bf AB}$  $p \to \Box \Diamond p$  $\forall x \: \forall y \:\: (x R y \to y R x)$ 
The current question we would like to ask is there some bridge between modal formulas and firstorder properties of relation in a Kripke frame. Before that, we introduce a fistful of definitions.
A modal formula $\varphi$ is called elementary if the class of frames which this formula characterises is defined by some firstorder formula. For example, the formulas from the table above are elementary since the corresponding frame properties might be arranged as wellformed firstorder formulas.
Let us define the special kind of formulas that we call Sahlqvist formulas:
A boxatom is a formula of the form $\nabla p$ or $\nabla \neg p$, where $\nabla$ is a finite (possibly, empty) sequence of boxes. A Sahlqvist formula is a modal formula of the form $\Box \dots \Box (\varphi \to \psi)$. $\Box \dots \Box$ is a $n$sequence of boxes, $n \geq 0$. $\varphi$ is formula that contains $\land$, $\lor$, $\Box$, $\Diamond$, perhaps, $0$ times. $\phi$ is constructed from boxatoms, $\land$ and $\Diamond$.
For instance, any formula from our table is a Sahlqvist formula. The example of a nonSahlqvist formula is the McKinsey formula $\Box \Diamond p \to \Diamond \Box p$, which is also nonelementary. This formula should be a separate topic for an extensive discussion. The reader may continue such a discussion herself with the classical paper by Robert Goldblatt called The McKinsey Axiom is not Canonical.
Theorem (Sahlqvist’s theorem)
Let $\varphi$ be a Sahlqvist formula, then $\varphi$ is canonical and elementary.
Sahlqvist’s theorem is extremely nontrivial to be proved accurately in detail. However, this theorem gives us crucially significant consequences. If some normal modal logic is defined with Sahlqvist formulas as axioms, then it’s automatically Kripke complete since every axiom is canonical and elementary.
Moreover, any Sahlqvist formula defines some property of a Kripke frame which is definable in the firstorder language. The modal definability of firstorder properties in itself has certain advantages. Let us observed them concisely and slightly philosophically. As we said at the beginning, a modal language extends the propositional one with unary operators $\Box$ and $\Diamond$. As the reader could have seen, necessity and possibility have connections with universality and existence. We established this connection defining the standard translation and embedding the minimal normal modal logic into the firstorder predicate logic.
On the one hand, it is a wellknown result that firstorder logic is undecidable. Thus, we don’t have a general procedure that defines whether a given firstorder formula is true or not (alternatively, provable or not). On the other hand, we have already observed the analogy between quantifiers and modalities. On the third hand (yes, I have three hands, it’s practically useful sometimes), modal logics are mostly decidable as we will see a little bit later.
That is, one has a method to check the validity of some binary relation properties by encoding them in modal logic. Such a way doesn’t work for an arbitrary property, indeed. But a large class of such characteristics might be covered via modal language. In the second part, we also take a look at the example of a formula whose class of Kripke frames is not firstorder definable.
In order to remain intellectually honest, we should note quite frankly and openly that there also exist firstorder properties of Kripke frames which are undefinable in a modal language. A relation is called irreflexive, if it is false that $a R a$ for each $a$. The example of an irreflexive relation is the set of natural numbers with $<$, just because there is no natural number that could be less than itself, indeed.
Let us define the notion of $p$morphism, a natural homomorphism between Kripke frames. By natural homomorphism, we mean a map that preserves a structure of Kripke frame and validness at the same time. Let us explain the idea with the precise definition and related lemma.
Definition
Let $\mathcal{F}_1 = \langle W_1, R_1 \rangle$, $\mathcal{F}_2 = \langle W_2, R_2 \rangle$ be Kripke frames, then $p$morphism is a map $f : \mathcal{F}_1 \to \mathcal{F}_2$ such that:
A $p$mophism between Kripke models $\mathcal{M}_1 = \langle \mathcal{F}_1, v_1 \rangle$, $\mathcal{M}_2 = \langle \mathcal{F}_2, v_2 \rangle$ is a $p$morphism $f : \mathcal{F}_1 \to \mathcal{F}_2$ such that:
$\mathcal{M}_1, w \models p \Leftrightarrow \mathcal{M}_2, f(w) \models p$ for every variable $p$.
If $f : \mathcal{F}_1 \to \mathcal{F}_2$ is a surjective $p$morphism, then we will write $f : \mathcal{F}_1 \twoheadrightarrow \mathcal{F}_2$. By surjection, we mean a map $f : \mathcal{F}_1 \to \mathcal{F}_2$ such that for each $y \in \mathcal{F}_2$ there exists $x \in \mathcal{F}_1$ such that $f(x) = y$.
The following lemma describes a $p$morphism behaviour with formulas that are true in models and valid in frames.
Lemma
Proof
We prove only the first part of the lemma. Let us suppose that $\Diamond$ is a primitive modality for a technical simplicity. The base case with variables is already proved since the condition of the lemma for variables is the part of model $p$morphism definition.
Let us assume that $\varphi \eqcirc \Diamond \psi$. We prove that $\mathcal{M}_1, w \models \Diamond \psi$ iff $\mathcal{M}_2, f(w) \models \Diamond \psi$. In other words, one needs to prove two implications:
Let $\mathcal{M}_1, w \models \Diamond \psi$, then there exists $v \in R_1(w)$ such that $\mathcal{M}_1, v \models \psi$. By induction hypothesis, $\mathcal{M}_2, f(v) \models \psi$. $f$ is a $p$morphism, hence $f$ is monotone and $w R_1 v$ implies $f(w) R_2 f(v)$. Thus, $\mathcal{M}_2, f(w) \models \Diamond \psi$. We visualise the reasoning above as follows:
Let $\mathcal{M}_2, f(w) \models \Diamond \psi$. Then there exists $x \in R_2 (f(w))$ such that $f(w) R_2 x$. $f$ is a $p$morphism and $f(w) R_2 x$, then there exists $v \in W_1$ such that $w R_1 v$ and $f(v) = x$. By induction hypothesis, $\mathcal{M}_1, v \models \psi$. But $w R_1 v$, so $\mathcal{M}_1, w \models \Diamond \psi$. Take a look at the picture.
Now we show that there doesn’t exist a modal formula that expresses irreflexivity of a relation.
Lemma The class of all irreflexive frame is not modal definable.
Proof Let $\mathcal{F}_1 = \langle \mathbb{N}, < \rangle$ be a Kripke frame, a natural numbers with lessthan relation, which is obviously irreflexive. Let $\mathcal{F}_2 = \langle \{ * \}, R = \{ (*, *) \} \rangle$. Let us put $f : \mathbb{N} \to \{ * \}$ as $f(x) = *$ for all $x \in \mathbb{N}$.
It is easy to see that this map is monotone and surjective. Let us check the lifting property. Let $f(x) R *$. Let $y := x + 1$, then $x < y$ and $f(y) = *$. Then $f$ is a $p$morphism, but $\mathcal{F}_1$ is an irreflexive frame and $\mathcal{F}_2$ is merely reflexive point.
If the reader is interested in modal definability in more detail, then she might take into consideration the GoldblattThomason theorem that connects modal definability, firstorder definability with $p$morphisms and some other operations on Kripke frames for further study.
The decidability of a formal system allows one to establish whether a formula is provable or not algorithmically. In modal logic, the famous Harrop’s theorem provides the most widespread method of decidability proving. Let us introduce some definitions to formulate this theorem.
Definition
A normal modal logic $\mathcal{L}$ is finitely axiomatisable if $\mathcal{L} = {\bf K} \oplus \Gamma$ for some $\Gamma$, where $\Gamma$ is a finite set of formulae.
In other words, $\mathcal{L}$ is finitely axiomatisable if this logic extends minimal normal modal logic with some finite set of axioms.
Definition
A normal modal logic $\mathcal{L}$ has a finite model property (or finitely approximable), if $\mathcal{L} = \operatorname{Log}(\mathbb{F})$, where $\mathbb{F}$ is some class of finite frames.
That is, a finite model property is a Kripke completeness with respect to the class of finite Kripke frames. Now we formulate the famous Harrop’s theorem:
Theorem (Harrop)
Let $\mathcal{L}$ be a normal modal logic such that $\mathcal{L}$ is finitely axiomatisable and has a finite model property. Then $\mathcal{L}$ is decidable.
Proof
We provide only a quite brief proof sketch. It is clear that the set of formulas provable in $\mathcal{L}$ is enumerable since this logic merely extends ${\bf K}$ with the finite set of axioms. On the other hand, let $\varphi \notin \mathcal{L}$. Then the set $\operatorname{Bad} = \{ \varphi \in Fm \:  \: \varphi \notin \mathcal{L} \}$ is also enumerable, so far as one may enumerate finite frames such that $\mathcal{F} \models \mathcal{L}$ and $\mathcal{F} \nvDash \varphi$, where $\varphi \in \operatorname{Bad}$.
So, if we have some finitely axiomatisable logic, then one needs to can show that this logic is complete with respect to some class of finite frames. Here, we will assume that $\Diamond$ is a primitive modality for simplicity.
Definition
Let $\mathcal{M} = \langle W, R, \vartheta \rangle$ be a Kripke model and $\Gamma$ a set of formulas closed under subformulas (that is, if $\varphi \in \Gamma$ and $\psi$ is a subformula of $\varphi$, then $\psi \in \Gamma$). We put the following equivalence relation:
$x \sim_{\Gamma} y \Leftrightarrow \mathcal{M}, x \models \varphi \Rightarrow \mathcal{M}, y \models \varphi$, where $\varphi \in \Gamma$
Then, a filtration of a model $\mathcal{M}$ through a set $\Gamma$ is a model $\overline{M} = \langle \overline{W}, \overline{R}, \overline{\vartheta} \rangle$, where
Here is a quite obvious observation. Suppose, one have a model $\mathcal{M} = \langle W, R, \vartheta \rangle$ and its filtration $\overline{M} = \langle \overline{W}, \overline{R}, \overline{\vartheta} \rangle$ through the set of subformulas $\operatorname{Sub}(\varphi)$, where $\varphi$ is a arbitrary formula. Then $\overline{W} \leq 2^{\operatorname{Sub}(\varphi)}$.
Harrop’s theorem provides the uniform method to prove that some normal modal logic is decidable if this logic is finitely axiomatisable and has finite model property. We need filtration to prove that a given logic is finitely approximable.
Here comes the first lemma about minimal filtration.
Lemma
Let $\mathcal{M}$ be a Kripke model and $\Gamma$ a set of formulas closed under subformulas, then $\mathcal{M}, x \models \varphi \Leftrightarrow \overline{\mathcal{M}}, \bar{x} \models \varphi$, where $\varphi \in \Gamma$.
Proof
Quite simple induction on $\varphi$. Let us check this statement for $\varphi \eqcirc \Diamond \psi$.
At first, let us check the only if part. Let $\mathcal{M}, x \models \Diamond \psi$. Then there exists $y \in R(x)$ such that $\mathcal{M}, y \models \psi$. By induction hypothesis, $\overline{\mathcal{M}}, \bar{y} \models \psi$. But $\bar{x} \overline{R} \bar{y}$ by the definition of $\overline{R}$. This, $\overline{\mathcal{M}}, \bar{x} \models \Diamond \psi$.
The converse implication has the same level of completexity. Let $\overline{\mathcal{M}}, \bar{x} \models \Diamond \psi$. Then there exists $c \in \overline{R}(\bar{x})$ such that $\overline{\mathcal{M}}, c \models \psi$. Consequently, there exist $w \in \bar{x}$ and $v \in c$ such that $w R v$. By assumption, $\mathcal{M}, v \models \psi$. Thus, $\mathcal{M}, w \models \Diamond \psi$.
Now we may formulate the theorem which claims that the minimal normal modal logic is the logic of all finite Kripke frames.
Theorem
Proof
Let ${\bf K} \not\vdash \varphi$. It means that there exists a model $\mathcal{M}$ and $x \in \mathcal{M}$ such that $\mathcal{M}, x \not\models \psi$ according to the completeness theorem. Let $\overline{\mathcal{M}}$ be a minimal filtration of a model $\mathcal{M}$ through $\operatorname{Sub}(\psi)$, the set of subformulas of $\psi$. By the previous lemma, $\overline{\mathcal{M}}, \bar{x} \not\models \psi$. It is clear that this model is finite, since $\overline{W} \leq 2^{\operatorname{Sub}(\varphi)}$, as we observed above.
(2), (3), (4) are proved similarly, but one needs to check that a minimal filtration preserves reflexivity, seriality, and symmetry.
However, we are in trouble. We haven’t already discussed a finite model property of logics that contain transitivity as an axiom. Unfortunately, a minimal filtration doesn’t have to preserve the transitivity. Let $\mathcal{M} = \langle W, R, \vartheta \rangle$ be a transitive model and $\overline{M} = \langle \overline{W}, \overline{R}, \overline{\vartheta} \rangle$ a minimal filtration of $\mathcal{M}$ through $\Gamma$. Let $\bar{w}, \bar{v}, \bar{u} \in \overline{W}$ such that $\bar{w} \overline{R} \bar{v}$ and $\bar{v} \overline{R} \bar{u}$. If $\bar{w} \overline{R} \bar{v}$, then there exists $x \in \bar{w}$ and $y \in \bar{v}$ such that $x R y$. From the other hand, if $\bar{v} \overline{R} \bar{u}$, then there exists $y’ \in \bar{v}$ and $z \in \bar{u}$ such that $y’ R z$.
It is clear that $y$ doesn’t have to see $y’$ within the equivalence class $\bar{u}$. Thus, a relation in minimally filtrated model isn’t necessarily transitive, even if the original relation is transitive.
A solution to that problem is the transitive closure of a relation in a minimal filtration. Let us discuss what a transitive closure is. Suppose we have some set $W$ with a binary relation $R$. Generally, this relation is not transitive, but we’d like to extend it to the transitive one. What should we make? Suppose we have $x, y, z \in W$ such that $x R y$ and $y R z$. We add $x R z$ to the extended relation which we denote as $R^{+}$. We perform this action for each situation. That gives us a transitive version of a relation.
Here we going to extend a relation in a minimal filtration. This solution was proposed initially by Dov Gabbay. We will denote this closure $(\overline{R})^{+}$.
A transitive closure of a relation in a minimal filtration allows us to prove that ${\bf K}4$ is the logic of finite transitive frames. Firstly, we formulate the lemma that explains why a transitive closure is a good idea:
Lemma
Let $\mathcal{M} = \langle W, R, \vartheta \rangle$ be a transitive model and $\overline{M} = \langle \overline{W}, \overline{R}, \overline{\vartheta} \rangle$ a minimal filtration through $\Gamma$, then the following conditions hold:
Using the lemma above, it is not so hard to obtain the following theorem:
Theorem
The theorem above allows us to claim that the logics ${\bf K}4$, ${\bf S}4$, and ${\bf S}5$ have a finite model property. All these logics are finitely axiomatisable. Then, by Harrop’s theorem, ${\bf K}4$, ${\bf S}4$, and ${\bf S}5$ are decidable. That is, one has a uniform method to provide a countermodel in which every unprovable formula fails.
We discussed a brief history of modal logic and its mathematical and philosophical roots. After that, we introduced the grammar of modal logic to define what modal formula is. As we have already told, the definition of a modal language is not enough to deal with modal logic. From a syntactical perspective, we have formal proofs as derivation with axioms and inference rules. We defined normal modal logic as a set of formulas with specific limitations. As an underlying logic, we fixed minimal normal logic ${\bf K}$. Here, the other modal logics merely extend the minimal normal modal one with the additional axioms. Note that syntax doesn’t answer the question about the truth of a formula. The distinction between proof and truth is a foundation of the logical culture in itself. Truth is a semantical concept.
To define the truth condition for modal formulas, we defined Kripke frames and Kripke models. After that, we formulated the completeness theorems for the list of modal logics. As usual, completeness tells us that, very roughly speaking, any valid formula is provable. The underlying modal logic ${\bf K}$ is a logic of all possible Kripke frames. Other modal logics are complete with respect to a narrower class of frames with the relation in which is restricted somehow. Alright, we have the completeness theorem for some logic, but we also would like to define whether a given formula is provable or not algorithmically. For this purpose, we took a look at methods of proving the fact that a given normal modal logic has finite model property. Finite model property with finite axiomatisation gives us decidability according to the Harrop’s theorem.
We studied the background of modal logic so that we could continue our journey in more concrete case studies. In the second part, we will overview concrete branches of modal logic, such as topological semantics, provability logic, and temporal logic. We will discuss how exactly modal logic is connected with geometry, the foundations of mathematics, and computer science.
I sincerely hope the reader managed to survive in this landscape of such an abstract and theoretically saturated material.
]]>I’ll describe. Just after entering the first out of five pavilions you are shocked by the multitude of booths and visitors—you look from side to side and think what the strategy should be: should you visit enormous booths of ITgiants like Microsoft or SAP or is it better to start by covering tons of startups all around the area? At the end of the article, I hope you’ll know what to do.
On November 25, the CEO, COO and CBDO of Serokell visited Web Summit 2019. The event was full of smart people, bright ideas and brilliant innovations. For us as a team, it was a wonderful experience and I would love to share my thoughts about it in a way that’s helpful for first time visitors.
Therefore, I will list a couple of points which can be useful if you are considering taking part in Web Summit or a large global tech conference like it. Let’s go!
Before coming to the conference you should spend enough time on preparation to feel confident during the event.
Remember, everything is possible here. Before coming, you should formulate your precise goal for this week and work in its direction. There are many opportunities: getting new clients, raising funds, searching for outsourcing services, headhunting and testing your hypotheses about the market and its participants. You could spend all week just working on one thing from this list, and if you are well prepared, the probability of achieving your initial goal becomes very high.
The important thing to mention is that Web Summit is more for CEOs and business development executives. As a result, don’t count on a high level of technical talks there—it’s more about business.
Regarding the booths, there are several types of them. Startups have the smallest ones and you can find a huge amount of them in each pavilion according to their industry: FinTech, HealthCare, Social, Security and lots of other fields are amongst the options.
Besides startups, there are bigger booths for sustainable businesses like JetBrains and different infrastructures like accelerators, hiring aggregators, VCs and even countries. Huge zones are for industry giants like Booking.com, Lenovo, Amazon, Siemens, Porsche, SAP, and dozens of other market leaders. If you are attending for business purposes, there is probably no point to spend much time near the biggest booths. Representatives there usually don’t have the power to make any decisions, they just present their products.
A very important thing: booths are only the tip of the WebSummit iceberg, thousands of interesting people and projects come in as attendees. The best way to reach them out is through preliminary search and the eventspecific app.
Therefore, you should schedule meetings and find interesting opportunities in advance.
Go through the endless list of participants on the Summit website, get in touch with proper ones and arrange a cup of coffee before coming to Lisbon. That way, you will be able to spend all 3 days efficiently.
Don’t underestimate the application, it is an instrument that brings you a lot of opportunities. It is key to becoming a part of a huge social network. It’s simple.
Fill in your profile and become visible to other participants.
After that, find any person who is interesting to you, propose him a meeting and schedule it right inside the application using the internal calendar.
What is more, you can form your own schedule by choosing interesting speeches and presentations and combining them with your meetings. The application will always notify you about the upcoming activity linking it with the Summit map. You can even use it before and after the event to get in touch with anyone you did not have time to catch in Lisbon or just stay connected to a big base of potential partners.
All participants have a QR code on their badge. After talking to anybody, just scan his QR with an app and send him a “Hi!”— that would help you to keep in touch. Probably, this is the first event that is free of business cards. Forget about carrying kilos of carton and remembering who’s card it actually is: just scan the badge and continue the negotiations in the application chat.
After a day in the fields, Night Summit begins. Every evening all participants go to the best party locations. Streets are closed, with access only for people with WS badges. It’s an enormous networking event under the dark sky and an awesome opportunity to build business relations informally. We have to admit that it is not so easy—the amount of people is very large, and as a result, everybody is grouping and finding a lead becomes quite tough. To succeed, you need to be a networking shark.
Besides the official Night Summit, there are lots of side events sponsored by different companies and projects. In our opinion, most of them are not worth visiting—descriptions are brilliant, locations are interesting, but, frankly speaking, it is very hard to compete with the official one. Visiting a side event could be efficient only if you have a specific interest in it. If not, we believe that spending time at Night Summit is much more reasonable.
The only negative thing I can mention is also the best one: the number of visitors.
Be ready to spend a lot of time in lines of differing shape and size: to enter the location, drop off your bag, get a meal or get inside the stage zone. However, there are a few life hacks which can make life easier.
Don’t forget to go through the registration at the airport just after landing in Lisbon. There is a huge tent with volunteers which can help you out with that. Registering directly at Summit could become a disaster—on the first day you may spend an hour or more in the line.
Use the “Fast Track” application for choosing and paying for your meal and just pick up your order in a specified place.
Come earlier or later than 1010:30 AM or lose your priceless time waiting in line at the entrance.
Web Summit is not a regular IT conference. It’s a huge celebration. It’s a time of gathering for the IT world. Everything is possible here. It’s an awesome opportunity to open your mind, find out how different countries and cultures have their own individual ideas, approaches and views. It’s a good opportunity to invest or find investments, a perfect way to build up your international network.
Keep in mind that this time can be spent efficiently only in synergy with good preparation and sufficient experience. If you are counting on this event as an instrument for business development, don’t choose it as your first conference—train on smaller ones. Literally, train, because all the other events seem to be just training once you get to Web Summit.
]]>Yup. We’ve made it. Started from the bottom, now we’re here.
Exactly 365 days ago, we published our first and also second article. (You know that we have always been generous.) Last week, Serokell’s CTO Kirill Elagin published the thirtyfifth. 35 is not much, but it’s not little either. From 35 stones, you can build a reasonable pyramid.
While we have had our slow phases, it seems that our blog has survived the early death that is the fate of most company blogs. Therefore, I look forward and see only glory and fame.
To show off a little, we’ve gathered some stats from the past and the present of the blog and compiled them into an infographic.
Past year, our blog has been visited about 85 000 times. Over a third of those visits have been to Vladislav Zavialov’s excellent article about Dependent Haskell, half of that amount to our article about Haskell in industry, and a half of that to Vasiliy’s introduction to Tagless Final.
Turns out, each social media network has a different article they like the most. While Facebook loved our introduction to Haskell libraries for machine learning, Reddit wanted to learn more about the future of Haskell, and Twitter about the successes it has had. Ahh, Twitter people. Always practical and reasonable.
And here are the top articles for each month of the entire year. If one looks closely, the view counts are increasing. That’s good. I wish you kept reading the articles because it is very fun to write them.
Some of these we haven’t yet mentioned. If a headline catches your eye and you immediately want to visit it, it’s somewhere down below:
If you’ve been a reader for this past year, or literally any part of the journey, we’d like to thank you so, so much. It’s been a pleasure.
Well, but you might ask, how are we going to top this? We have a blog about Haskell that people read. What else?
Comments! Exactly. We have been planning improvements to the blog, focused on making the content more engaging and interactive for you, the readers. Right now, we are in the process of creating a comment system; other improvements like a place for feedback will follow shortly.
There’s a load of articles in the works: modal logic, free monads, comparison of GitLab and GitHub, deeper descriptions of Haskell in industry. Stay with us to find out interesting facts and answers to difficult questions.
Let’s make the next year even better.
]]>gitannex enables you to manage files with Git without actually having to check in the file contents. It’s handy when one has to deal with large files (Git might not be able to easily handle these because of memory, time, or disk space limitations ). gitannex supports both a command line and a folder synchronizer mode.
Pandoc is a free Haskell library that converts one markup format to another. It also includes a commandline tool. Some of the formats this library supports are commonmark, docx, epub, fb2, gfm and so on. It can also create HTML or PDF (via LaTeX). Pandoc usually tries to keep the structure of the original document, but some elements like complex tables might not fit into the Pandoc’s document format.
Cardano Settlement Layer is a cryptographic currency developed by Serokell in collaboration with partners. It implements the Ouroboros PoS protocol. Cardano SL provides an elegant solution for a settlement layer of a blockchain platform that increases efficiency and security.
This little program simplifies symbol search for those who work with LaTeX. The user just needs to draw a sign in a special window to get a hint. This saves a lot of time, and if you want to, you can contribute to it through the GitHub open repository.
If you’re interested to learn more about the latest trends in programming, read our post about Tagless Final.
ShellCheck is a tool for static analysis of shell scripts. The program synchronizes with the latest versions on Git. Its purpose is to:
Darcs is a version control system like Git but written in Haskell. The interface is simpler than Git, which might be appreciated by beginners.
hledger is a personal resource tracker that runs on Unix, Mac or Windows. It’s meant to be a free and efficient alternative to accounting apps such as Quicken or GnuCash.
Hledger features:
Need to move some legacy code from C to Rust? Corrode, the automatic CtoRust translator can help you with that.
It partially automates moving code that was implemented in C, but you will still need to modify the output to match Rust’s specific features and idioms.
PostgREST is a tool that serves a fully RESTful API from any existing PostgreSQL database, saving you time on writing your own API. It also states among its advantages that it is “cleaner, faster, and more standardscompliant than anything you would likely write from scratch”.
membrain is a Haskell library with a typesafe memory data type. Its purpose is to make programmers’ lives easier when they work with memory units. For example, to simplify the definition of different units without fearing to forget to multiply or divide them, combine different units and safely convert between them.
The project was created by Kowainik  a small company that cares about its opensource contributions.
If you would like to contribute to the list and share your favorite one, drop us a line! Also, if you want to progress as a Haskeller, check out our post with valuable resources for Haskell programmers.
]]>Now it’s time to fix that injustice. This post is a collection of great projects written in Haskell, which unearths the benefits of Haskell that the majority knows nothing about.
Did you know that Facebook is secretly in love with Haskell too? In 2015, Facebook switched from using their own computing language FXL to Haskell when redesigning Sigma  their main weapon against spam and fraud. FXL was slow and lacked necessary abstractions like userdefined data types and modules. Haskell made the program function 3 times faster.
The system is a rule engine that checks every interaction on Facebook for suspicious activities. It evaluates policies, then identifies and blocks “bad” activities. Sigma processes millions of requests per second.
To code this rule engine, the developers needed a fast and secure tool. Their main motivation for choosing Haskell was:
Target is a supermarket chain with a strong technology stack. The company applies Haskell in its data science department in order to solve the problem of demand uncertainty. There the probability monad has turned out to be extremely useful. Overall, Haskell helps them overcome randomness and build probabilistic models in production.
Barclays Bank is a UK financial organization famous for its passion to explore more “exotic” technologies.
Their inhouse Haskell department has created a risk management engine in Haskell. The system calculates the value of all the trades and how market changes affect it. Risk engine connects to other systems, conducts computation, does value streaming and more.
The developers at Barclays have also made the Functional Payout Framework. This also is a Haskell application. It exploits embedded domainspecific functional language to process exotic financial derivatives. Unlike other scripting languages for the same purpose, the framework is able to process multiple interpretations to price such trades and analyze the scripts.
Galois actively promotes the use of functional languages in the industry. They do cryptography research, critical software system design, humancomputer interaction optimization, and basically any other type of RnD. Haskell is their evergreen tool to guarantee security for critical systems.
One of the interesting projects they have worked on (and which is available as an open source library) is Cryptol. Galois created Cryptol for the NSA’s Trusted Systems Research Group as a standard for cryptographic algorithm specifications.
Cryptol specification can serve as a reference for a cryptographic module, and can help authors of cryptographic implementations to check or prove properties of algorithms and experiment with new ones.
If you want to explore more of their open work, take a look at their GitHub.
At Serokell, we’ve also worked on projects the Haskell community can be proud of. Cardano Settlement Layer is one of them. It’s a cryptocurrency designed for Cardano blockchain platform in collaboration with the University of Edinburgh, University of Athens and University of Connecticut.
The main technical feature of Cardano is the clear separation of the blockchain into two layers. The first is needed for cryptocurrency circulation and coin distribution, while the second is a platform working with smart contracts. This creates an advantage for Cardano in the speed of the transactions and the level of security of the entire system.
We used proofofstake instead of a proofofwork algorithm to reimagine Bitcoin. Simply put, the reward of the “miner” depends on the balance on his account.
The project is trying to solve the problem of quickly and cheaply creating decentralized applications and smart contracts in a safe and scalable way.
Under NASA auspices, the University of Copenhagen, Galois, and National Institute of Aerospace have collaborated to create a runtime verification framework Copilot to deliver runtime monitor programs. These programs function concurrently with the target software and make sure it behaves according to the specifications.
Update: The information about the developers was updated thanks to one of the developers and maintainers of Copilot who pointed out the actual participants of this project.
Copilot is embedded in Haskell (it’s a DSL), so a working knowledge in Haskell is a must if you want to operate with it. For example, Copilot types are enforced by the Haskell type system to ensure that target programs written in C are welltyped.
We hope the list of cool projects will continue to grow. Many enterprises like Tesla and Bloomsbury are nowadays hiring Haskell developers to bring new inspiring projects to life.
While you wait for Top Haskell Software Projects 2.0, check out our post about the history of Haskell. You’ll learn more about its exciting past  from being a theoretical abstraction to becoming a synonym for software with high security and efficiency.
]]>By the way, if you were looking for a big reason to create an account on Twitter or Reddit – do it now for unlimited fresh memes and stuff! For some ideas, we want to share the sources that enable us to keep our hands on the pulse. These are what our specialists suggested:
Reddit:
Youtube:
Twitter:
Let’s delve deeper into them.
The first one is a classic and we need to mention it before we dive into social media.
Planet Haskell aggregates Haskell content from all around the globe. It’s quite oldfashioned, but the materials are awesome because of the contribution of a multitude of blogs and other sources.
The main Haskell hub on the famous Reddit platform. Here you can find such Haskellrelated things as: theory, snippets with comments from the experts, materials for practice, actual problems and their correct (or notsocorrect) solutions, libraries, jobs and events.
You can find us posting releases, news and open vacancies here. Also, r/Haskell has a “Monthly Hask Anything” thread, where you can ask your questions.
Another subreddit that we recommend to join. Here you can find materials related to Haskell, OCaml, Elixir and other functional programming languages, including papers, news, theoretical materials and discussions of all new FPrelated trends, like functional programming for deep learning.
Edward Kmett is a Haskell programmer, mathematician and Research Engineer at the Machine Intelligence Research Institute. Edward is quite famous in Haskellrelated reddit threads by his informative answers and posts. You can also check his Twitter and his live coding streams on Twitch.
ICFP is one of the biggest annual FP conferences. You can go there if you want to learn a lot about theory, design, tools and practical use of functional programming languages. Also, we included Haskell symposium (which was colocated with ICFP this year) in our list of Haskell events. If you prefer to stay at home and watch recordings – ICFP videos are what we recommend you to check – they contain wellcovered, interesting topics for Haskell developers.
Compose Conference is another FP event which we recommend you to participate in. By the way, they have a lot of videos with speakers’ keynotes and presentations – we always get some useful tips out of them.
Videos presented here are more general than specific, but it doesn’t correlate with the quality of material. Here you can find detailed answers for a lot of common questions. Episodes are with modern infographics and historical throwbacks, so no Netflix today, but a lot of interesting stuff instead. We care about your personal development!
Haskell LibHunt is a source where you can find some useful Haskell packages. On Twitter, they post interesting articles which we like to save and reread. Also, you can subscribe to their Haskell newsletter – it is a good source of fresh Haskell news.
Vitaly Bragilevsky is a wellknown person in Russian Haskell community. He is a Senior Lecturer at the Southern Federal University, software developer at JetBrains and author of Haskell in Depth book, which you can use in addition to these sources.
Despite the fact that he doesn’t post his own articles, Vitaly reposts a lot of interesting things and gives his comments, so if you want to stay tuned and know what is happening in the world of Haskell – we suggest you to tap the “Follow” button on his profile (as we did a couple of years ago).
The nickname says all. Here you can find useful tips and tricks which can help you to do your work better and faster. Code snippets, retweets from experts, and links to informative articles – if that is what you are looking for, you’re welcome!
Okay, you have gathered your own list of sources to subscribe, with our help or not, but what for? For your lovely job, of course! Functional Jobs posts not only Haskell opportunities – you can go through Scala, Ocaml, Erlang and other jobs for functional programmers as well. They pick the most interesting offers and update their feed regularly, so don’t miss your chance for professional growth!
A few other interesting Twitter accounts to follow:
Iceland Jack – tweets from the author of DerivingVia
Stack Overflow Haskell – Haskell questions made on Stack Overflow
Serokell :)
If you are still using IRC channels to communicate with people – we suggest you join some Haskell channels. Despite the fact that IRC itself is quite ancient, you can get fresh updates, releases and new developments straight from the source. Also, there is a channel for beginners and a lot of channels where you can communicate with native speakers of your language.
We have come to the end of the list, but we’d love to improve it. If you want to add something of your own, feel free to DM us on Twitter.
]]>If earlier there was a problem to find a book, tutorial or course – now you can find a lot of web sources with good materials and tons of refuse at the same time. We have prepared a small springboard for you – a list of educational Haskell sources that have been verified by our specialists.
Here’s our list. Reviews are one little scroll below.
http://learnyouahaskell.com/
Book
Suitable for: newbies
Learn You a Haskell is a great nontrivial source of knowledge for beginners in the functional programming world. In addition to online material, there is a paper version of LYAH for people who prefer books instead of web sources, it costs $32 on Amazon. All of these materials are prepared and written by Miran Lipovača, a Slovenian computer science student.
It’s convenient for learning Haskell – wellillustrated, stepbystep guide with references to popculture.
Also, it has 4.3 rating on Goodreads and the online version is completely free.
“This is a fantastic book and I highly recommend it as the first book on Haskell — and possibly even the second." — Michael Fogus, author of The Joy of Clojure
https://typeclasses.com/
Courses
Suitable for: all levels
Type Classes provides courses and reference materials for learning Haskell. Type Classes Consulting was created by Julie Moronuki, author of Haskell Programming From First Principles, and Chris Martin – former CTO and cofounder of Fold, an app for shopping with bitcoin.
Type Classes can be really helpful for all kind of programmers who want to learn Haskell. Courses are more theoretical than practical, but they are great for beginners. Subscription costs $29 per month (or 300 per year), but it’s worth every cent you spend.
Already have some knowledge or have just finished your courses? Okay, let’s check your skills!
https://www.codewars.com/?language=haskell
Code practice
Suitable for: Haskell beginners
If you want to test your skills, you can do it online on this educational service. It has not only Haskell miniproblems (katas) together with test cases, but also a lot of other code examples in different languages to boost up your level of knowledge. Before you join the community and begin to sharpen your skills, you need to choose a programming language and solve a quick problem. Don’t worry, the task is simple and we believe in you.
If you already have some experience in Haskell and don’t know why you are here reading about basic tutorials, you can kill some time by solving katas too. There are different levels of difficulty, so you won’t get bored quickly.
https://exercism.io/
Code practice
Suitable for: all levels
Exercism is about the same, but has more features like peer reviews to improve general programming techniques and more languages to learn. Also, it is ranked by the Slant community as the #1 website for code learning. It is completely free and you don’t need to pass any tests before you can start your quest of becoming a blackbelted Haskeller.
We have prepared some good sources to keep your finger on the pulse of what is happening in the world of Haskell. Also, we have included a digest of advanced Haskell topics which are a must if you want to become a real diehard sensei.
https://haskellweekly.news/podcast/
A good podcast from our Haskell colleagues. Honored developers discuss the business usage of functional programming and important Haskell news. Each episode lasts for 15 minutes and they are available in Apple Podcasts and Google Podcasts.
We can’t say that it is what newbies really need, but this podcast keep us and a lot of our Haskellers updated. If you already have written some Haskell code – you can listen to this podcast, for example, to make a list with all the unknown terms to learn.
http://www.haskellforall.com/2014/03/introductionstoadvancedhaskelltopics.html
Here is a list of advanced Haskell topics prepared by experts for all who want to sharpen their skills. Also, Vlad said that the whole blog is awesome and we can’t disagree with him.
http://dev.stephendiehl.com/hask/
This one is huge. Commands, handy tips, and a lot of relevant information about a wide range of Haskell topics. Browse it in slow evenings or reference it whenever the need arises  it will work either way.
While you’re there, don’t forget to check out Stephen Diehl’s blog as well.
This is the blog of the IOHK software developer Matt Parsons. Despite the original way of blog design, his posts are interesting and wellelaborated.
If you want to have small talk with experts and developers from big companies – there is no way better than to go to one of Haskelloriented or functional programming conferences. We have a list of the best events for you. Also, check our Haskell articles to learn more about our favorite functional programming language.
Next time, we will make a list of resources that we suggest you to subscribe if you want to stay ahead in the world of Haskell and functional programming.
If there are more questions than answers and you need help with some tasks, feel free to contact us: hi@serokell.io.
Viam supervadet vadens. Good luck!
]]>You can find a lot of different tutorials, articles and blog posts about Haskell and functional programming, but we suggest you to start from this book written by Graham Hutton. Also, we have a Haskell course prepared together with ITMO scientists and suitable for newcomers. But if you are experienced Haskeller with a wizard beard, don’t waste your time on such basic things – better check out our Haskellrelated blog posts.
Here, we focus on the history of Haskell, the main programming language at Serokell. We have decided to highlight only the most important milestones, so no lengthy paragraphs and descriptions this time. We are ready to travel to the roots!
In the 80s, functional languages might have been radical and elegant, but they were also laughably impractical.
By the late 80s, FP community faced a problem caused by the emergence of a number of functional programming languages that were quite similar in their semantics and power. For example, ML, Hope and Miranda. (From Miranda, the future language will take its semantics.)
The independent development of each of these languages and the development of functional programming as a whole became ineffective because none of these languages had the critical mass of users and languagedesign effort. Neither had a stable foundation for real applications development.
The first milestone in the History of Haskell is dedicated to the FPCA ‘87 conference, which was held in Portland, Oregon. To create a language, taking into account all the problems that existed at that time in other disparate functional languages, as well as in order to popularize functional programming, a committee was organized there. From this point begins the story of a statically typed, purely functional programming language, named after an American mathematician and logician Haskell Brooks Curry.
The first Haskell Report from the committee involved in its development was published on April 1, 1990.
The report described the motivation for creating the language, the nature of the language and the process of its creation, for which the committee is responsible. This was a serious first step – a language appeared around which a community of researchers and developers began to form.
Two years later, in 1992, the first Haskell tutorial was published. It was “Gentle introduction to Haskell” written by Hudak and Fasel. Also, it is the year of creation for GHC, the most commonlyused native code compiler for Haskell language.
GHC is developing and has more than 400 contributors by now. Since 2009, thirdparty contributions to GHC have been funded by the Industrial Haskell Group. In this video, you can see the development of GHC from 1996 to 2019.
In 1994, John Peterson registered the haskell.org domain name and set up a server and website at Yale. Hudak’s group at Yale continues to maintain the haskell.org server to this day.
You can find a lot there – online communities, Haskell events and conferences, books, courses, tutorials and manual pages. Also, you can download all the necessary stuff to start, like Haskell toolchain, which is represented in 3 versions – from minimal to platform.
In 1996, the Haskell version 1.3 Report was published, edited by Hammond and Peterson.
Haskell 1.3 featured some very significant changes:
In the late 90’s, Haskell continued to develop, many small flaws in the language design and ambiguities in the report were discovered.
But now, let’s return to our times.
In 2005, at Haskell Workshop, John Launchbury called for a definition of “Industrial Haskell” to succeed Haskell 98. So many extensions had appeared since the latter was defined that few real programs adhered to the standard. A new committee was formed to design the new language, appropriately named Haskell′ (Haskellprime).
But after several years exploring the design space, it was decided that a single monolithic revision of the language was too large a task, and the best way to make progress was to evolve the language in small incremental steps, each revision integrating only a small number of wellunderstood extensions and changes.
Haskell 2010 was the first revision created in this way. The most significant language changes in Haskell 2010 relative to Haskell 98:
It is the most important point in the modern history of Haskell development and it is also the current standard for most Haskell developers.
That’s the story of Haskell from its foundations to nowadays. The language continues to develop and all of these processes are nothing without the people who contribute, fix and do significant improvements to make it work in a proper way.
Haskell for us is not only a great tool, but a wonderful example of a programming language built around the community. Thanks to all who have worked hard on this functional language before us and continue to work on it today!
References:
]]>Working from home is efficient, convenient, and beneficial for both employers and employees but it has its challenges.
Serokell has been remote from the very beginning, and our employees can tell quite a few stories about their experience. We conducted an informal, anonymous survey and asked our colleagues about the challenges they faced when starting remote work and how they solved them.
When you work from home, you don’t waste your time in traffic jams and can start the job even before the morning coffee. And here is where one of the most frequent problems appears, namely the time management issue, which includes overworking and procrastination.
As remote work usually implies flexibility, some people tend to have longer working days at home than is needed. Many of our colleagues told us that they spend much more time on work during the day than they used to at the office. “You can organize your timetable like you want, for example, to visit a doctor at 11 AM, but you do important things before and after work, so in my case, I don’t have free time for weeks,” one of the Serokell developers explained.
Discipline, supportive environment and modern time management tools can help. Some people turn on the timer and have breaks, say, every two hours, others ask relatives to remind to have some rest: “Sometimes I might work unhealthy amount of time. The best solution for me is to have an environment where people will help me to notice that I am working too much,” a colleague of ours said.
Another consequence of poor time management is procrastination. When you’re on your own and the atmosphere of the apartment is relaxing – or disturbing – you may find yourself doing anything else but work. Some ways to overcome that:
“I’m always trying to have interesting tasks to keep myself motivated, sometimes creating new ones which I and my team lead find reasonable to be implemented. Or turn on music. Usually, one of these helps.”
“Regarding procrastination – it usually helps just to start something, and then I usually get inspired and carried off.”
We may think that being remote means there’s less communication needed, so we are more effective as a result. But in fact, it’s the opposite – you need to communicate with your team a lot just to make sure everyone has all the required information. And as long as you communicate via text messages or voice chats, you need to be very careful in expressing your thoughts in a way that everyone can comprehend.
Plus, don’t underestimate the “social” aspect of work. Even though there are many tools for video chat and conferencing, the feeling of being disconnected from the team can be very stressful. This is how one of our teammates described this problem: “One of the hardest things for me is the lack of facetoface relationships with colleagues.” “I sometimes feel like I have no one to muck about with during lunchtime,” added another.
What to do? Take responsibility for your communication practices. Use words carefully, try to avoid misunderstanding, be ready to explain everything twice and, in general, communicate a lot. Turn off notifications in Slack if you need a couple of hours for uninterrupted work. When you are resting, it will save you from annoyance.
And remember that your colleagues, wherever they are, are real people, and treat them appropriately: “I solve my communication problems in the same way as if I worked in the office. Except that now technically I can’t yell at people, which I never did anyway.”
Even if you work flexible hours, working from home doesn’t give you absolute freedom – there are deadlines you have to make and projects that require your full attention, plus, remote workers often have to track their time. It may be hard to build your working schedule by yourself and even harder to stick to it and say “no” to friends and family who disturb you as long as you’re at home. Job and personal life get tied up very easily if you do not separate them consciously.
“It’s often difficult to spend 8 hours working when at home. Personal activities tend to tie up with working hours, and sometimes, I feel like my life is dedicated to my job. Also, my cat apparently cannot distinguish “I’m home and free” and “I’m home but working” states,” one of the Serokell developers said.
In order to explain to friends and family that even if you’re at home, you’re busy, you can show them your task tracker or Google calendar. It also helps to choose a suitable time of day and work during these particular hours regularly, emulating the office schedule:
“I manage my time quite easily. I have only changed my routine a couple of times, just to fit different circumstances (e.g. having lunch with family or having something to do at a specific time every day). I usually start working as soon as I can and later in the day start checking the clock that is going, when I’m close to 8 hours I start to consider how long a task will take and if I can do it in a reasonable time (or if I already have gone over it too much).”
The working environment can be a challenge when working from home. You need to have the proper equipment, devices and tools as well as a comfortable table and chair, and a calm place to put it all in.
Sometimes people start working from their rooms but then realize that there has to be a dedicated space for work only. Some people prefer just to transform their space into the working place: “I put a scrum board near the table, and that’s all. My room is an office with a bed.”
Proper furniture is not only about comfort but also about the health: think of longterm effects for your back.
“For those two years when I was combining work and university, I was living in a dormitory, and my working space was essentially a bed and a laptop. After returning home, I bought a special chair and now generally try to improve the environment with regard to my health,” said our teammate.
And of course, nothing makes a remote worker shake in fear as much as an internet outage. It may be surprising, but even in the 21st century, problems with the internet connection appears frequently. Make sure your smartphone is charged and it’s possible to use the mobile connection in case of an emergency.
Your teammates are your social circle, and when you spend all your time online, you may feel lonely at the end of the day. Remote workers often work asynchronously and, if you don’t have family members home with you, perhaps you have only houseplants to talk to. “You have less social interactions with colleagues, so you might feel lonely or bored if you don’t invest in your “normal” life,” explained a Serokell employee.
You can also try working at coworking spaces to feel you’re a part of society – and, well, to find out you don’t miss office life at all. Anyway, all of us need to contact people in person at least from time to time. Continuous isolation can even lead to mental health issues, so it’s essential to be around others during nonwork hours.
“If your personal life is not wellarranged and the social circle is not built, you can fall into a rut even worse than at the office. Social life is crucial for people,” thinks one of our teammates.
Remote work offers several different challenges. Remote employees don’t have an option to discuss tasks during a coffee break and have less natural opportunities for brainstorming, but most of the issues can be solved by proper management, convenient infrastructure and wisely built connections inside the team. The issues related to social isolation and lack of routine can be solved with some efforts as well. Despite all these challenges, remote work is very rewarding, and we at Serokell enjoy it.
]]>If you want to learn more about this language – there’s no place better than a dedicated event. To help you make a decision, we’ve found and reviewed 9 of them.
Empex is a widelyknown events organiser, especially if we talk about Elixir/Erlang conferences. The next event (in LA) will be focused on realtime applications like chat apps, online games, financial data streaming, or autoupdating dashboards. This conference is not only for developers – designers, product managers, and entrepreneurs can submit their talks and attend too. Of course, the level of knowledge doesn’t matter – it’s all about having wholesome experience and networking, that’s why it’s amazing.
ElixirConf EU is the main Elixir conference in Europe, organised by CodeSync and ElixirConf. If you decide to participate, you will get 2 days of talks by Elixir creators, technical leads, developers and enthusiasts. Keynotes and speeches will answer all range of questions – from main tendencies in the development of language to Elixir industry adoption. The 3rd day of the conference is a training day – you can participate in a bunch of industryrecognized trainings and choose the program by your interests, needs and level of knowledge.
It’s an American event with one main theme every year. This time, it is an Emerging Elixir. You will be able to get acquainted with all innovations in Elixir sphere, listen to developers from famous companies and different spheres (Elixir Fountain, Weedmaps, DockYard) and meet Elixir enthusiasts from all over the world. The Big Elixir takes place every year in New Orleans’s oldest operating playhouse, Le Petit Theatre Du Vieux Carre.
This conference has an excellent speakers list, starring Justin Schneck from Nerves Project, Amos King from Binary Noggin, Anna Neyzberg from Elixirbridge, and many others. While other conferences mostly focus on projects, tooling and topics, this conference promotes knowledge and experience exchange, organises talks from devs and industry leaders, and lets all people train by focusing on foundational skills.
ElixirConf is another big multitrack Elixir event in the USA. Here you can choose from 12 different training programs and over 40 speakers, keynotes and trainers! You will have two days of training and 2 days of talks. Topics include High Performance Processing Scripts in Elixir, Contracts for Building Reliable Systems, UI testing, Writing an Ecro Adaptor, and other hot themes by speakers who are famous all over the Elixir community.
If you have already been on the main Elixir conferences and are searching for something a bit different – it’s a good alternative tech event. Here you can meet academics, developers, users, testers, and designers. If you are looking for a person to have a discussion and collaborate with – Code Mesh is a great opportunity for networking and scaling up your skills.
1 day, 1 track, similar to other Code Sync events but in London. It’s good for your first Elixir conference experience if you’re in the town.
Code BEAM Lite events replaces Erlang Factory Lite conferences. These are local one day conferences which are focused on realworld applications of Erlang, Elixir and the BEAM. If you don’t want to spend more than one day for an event – it’s a perfect choice with really good prices.
This is a 2day single track conference in USA. One extra day is traditionally for training. Talks from industry leaders, academics and your colleagues from all over the world. Lonestar takes place in Austin, TX.
Also don’t forget about local Elixir user groups and main FP events – some of them you can find in our previous article! I hope that this info can help you make a decision. Bye!
]]>When I was studying at ITMO, we had to visit a Functional languages class organized by the university together with Serokell. The quality of the lectures was high, and we had to do a lot of homework, which I really liked as it forced us to take the material seriously and deepen the understanding of the lectures.
Our teachers checked students’ tasks individually, and that was an excellent opportunity to talk to the teachers and explain your decisions, and they could see how well you understood the material.
This class gave me knowledge qualitative enough to get the job of a junior developer in a company using Haskell as the primary working language. Also, it gave me motivation for future development. Haskell is a functional language, it differs from familiar imperative languages. For this reason, many students start to hate Haskell; I am happy it didn’t happen to me, and I even became a big fan of the Haskell programming language.
I enthusiastically did my homework, read related materials and even solved tasks in advance. It didn’t go unnoticed. Arseniy, who was one of the teachers, checking my homework once offered me a job at Serokell.
I was in doubt because the position required better English language knowledge than I had those days. But my main concern was about Haskell, as starting programming in it was a big deal. Haskell wasn’t as popular as imperative languages, such as Java or C++, which I knew as well. If I gave them up, I would lose a vast developers community and infrastructure (tutorials, wellknown libraries, development environment). At the same time, I would gain a chance to try something new without significant risk, as I was only a fourthyear student and didn’t have serious obligations. So, I decided to give it a try, and I’m still happy with this decision.
Now I also teach Haskell at ITMO, and I like it. First of all, it gives me a unique experience. Secondly, if you teach, you must know the subject perfectly. You must be ready for any questions students can ask if you don’t want to look like an idiot. Thus, I prepare for the lectures carefully and continuously improve my own level of understanding material. And this differs from what you need at work. At work, you need to be familiar with many various things while at lectures, you need to understand something specific but very deeply. Finally, I simply enjoy teaching, it’s fun.
Standard university training system gives future developers knowledge about imperative languages, such as Java and C++, which seem to be easier than functional ones. Thus, Haskell, OCaml, F# and other functional programming languages gain less attention at schools and universities, and that’s sad because they have quite a lot of advantages in comparison to imperative languages. I consider it important to popularize the culture of functional languages.
Also, I’m not satisfied with most of the socalled “unusual for programmers” courses we have at ITMO (for example, physics, general science, etc.) because students don’t take them seriously and often don’t even understand the basics of those subjects. I didn’t like it when I was a student, so I feel like I pay my debt showing how to teach properly. I think it’s a valuable contribution.
Students to whom I read lectures, often come to Serokell as interns and then keep working at the company. Besides, the company is becoming wellknown in the whole university, not only among my students. And if some people are interested in Haskell, they know where to go to try themselves as developers.
During the first year at university, we had a sort of Haskell course with another teacher. He was not from Serokell. It was awful, to be honest. And I don’t think to teach such a thing as functional programming was a good idea for the first year in general. As a result, everyone in my group, me included, hated Haskell. Some objective facts supported our hate: in reality, no one writes code in Haskell, it is slow in comparison to other languages, it is very complicated, etc. Back then, I couldn’t expect I might become a Haskell developer in the future.
I find it funny that I had a similar situation with physics, which I hated learning at school. But in the second year at university, I had a rigorous physics teacher. If I wanted to pass an exam, I had to learn a lot, and I actually liked it.
These two stories taught me that it’s unwise to neglect what you cannot understand well. Probably the reason why you think something is rubbish lies in your ignorance. Perhaps, you need to bother a bit and try to study the subject. It’s not impossible that one day you will realize it was definitely worth your time. Never say never, that’s so true.
]]>In our previous blogpost, we introduced a reader to our subject matter and briefly observed several numeric libraries in Haskell. We explained why we don’t use one of the popular libraries called GHC.TypeLits
with noninductively defined kind of typelevel natural numbers. In our approach, we put dimensions of data types on the type level to avoid a large set of errors that might arise at the runtime stage. During the compile stage, one may check some properties of matrix dimensions expressed via typelevel natural numbers and find out the reason of that error as a type error.
In this part, we describe our approach to the matrix data type that is parameterised via its numbers of columns and rows.
We had a task to implement a few machine learning algorithms for dimensionality reduction. One important goal was to find out whether Haskell fits for such sort of tasks and make an opensource library for dimensionality reduction.
At first, we took a look at the Accelerate library due to its efficiency in parallel computations, especially using GPU. We found out that a few required functions from linear algebra were not implemented in it. By the way, we are going to implement these functions later via bindings to CUDAsolver. However, we decided to switch to REPA as a base matrix library because it contains the desired functionality.
During the implementation of the given algorithms we encountered “debugging hell”, a condition, when it wasn’t possible to define the place of a mistake even if you had the place of an error. (Especially in case of PPCA which can handle and restore missed data in the input.) That’s what we had:
GPLVMHaskellexe: inconsistent dimensions in matrix product (11,2) x (3,1)
CallStack (from HasCallStack):
error, called at src/Internal/LAPACK.hs:61:31 in
hmatrix0.19.0.0GJ4OJPujscCE7zmZ8JSwjL:Internal.LAPACK
Where is the place of the exception? Why such specific dimensions are there? It is a real detective job, honestly. Each time we dealt with such errors, we launched an inquiry. It was quite monotonous and exhausting. We wanted to make debugging easier and decided to lift dimensions on the type level.
Why typelevel dimensions?
So, we decided to test this approach.
As we have said before, one needs to check matrix dimensions and their properties at the type level. For this purpose, we promoted dimensionality into the type of matrix.
DimMatrix
is a newtype on Matrix r a
. Note that dimensional type parameters are only on the left side of this definition.
newtype DimMatrix r (y :: Nat) (x :: Nat) a = DimMatrix { getInternal :: Matrix r a }
Here a
is a type of elements, r
is a representation type. y
and x
are types of kind Nat
from typenatural
library, which is the most useful for our goals, as we discussed in the introduction.
Looks good, but the dimensionality of the input data is unknown at compiletime. Thus, types might be dependent on other values received at the runtime stage. This connection might be described quite straightforwardly via dependent types. Here’s a small example written in Agda:
generateVec : {A : Set} → (ℕ → A) → (n : ℕ) → Vec A n
generateVec f zero = []
generateVec f (suc n) = (f $ suc n) ∷ generateVec f n
In this example, we generate a list with length in its type, the result of which is parametrised by value n
. In other words, it depends on the value. Here, the benefit of dependent types is that the compiler checks the function body. If the length of that list doesn’t equal n
or the compiler cannot prove this fact, then we obtain a compilation error.
At present, Haskell lacks dependent types. However, there is the necessity to jump from values to types, and we’re not able to do it with the actual Haskell type system.
In singletons
, one may emulate a dependent type by jump mapping some type a
into a datatype Sing n
, which has exactly one value in runtime. Read this article to learn more about this tool. The basic module called Singletons.Prelude
provides singleton types, preludelike typelevel functions, and promoted types. The main goal of the module is to emulate Haskell prelude at the typelevel. More information about promoted types you can find here.
This library is pretty helpful for dependent types emulating in Haskell, but it might become irrelevant when fullfledged dependent types would be available in Haskell. This tutorial introduces singletons more systematically. We only discuss some basic constructions that we have used for typesafe dimensions.
We decided to use the singletons
interface for typelevel dimensions. Here we meet an additional important characteristic of typenatural
. There is integration between typenatural
and singletons
implemented as follows.
Let us consider the following example of singletons
use with DimMatrix
data type. withMat
is a function that creates the same matrix with typelevel dimensions from the input repa
matrix. We implemented this function via continuationpassing style because typelevel dimensions x
and y
are bound by the internal universal quantifier so that they cannot appear in the result type k
. Here, we use a continuationpassing style to create a matrix with typelevel dimensionality from the usual one and avoid the disappearance of dimension in types.
This function is one of the most widely used by us:
withMat
:: Matrix D Double
> (forall (x :: Nat) (y :: Nat). (SingI y, SingI x) => DimMatrix D x y Double > k)
> k
withMat m f =
let (Z :. y :. x) = extent m
in
case toSing (intToNat y) of
SomeSing (sy :: Sing m) > withSingI sy $
case toSing (intToNat x) of
SomeSing (sx :: Sing n) > withSingI sx $ f (DimMatrix @D @m @n m)
At the early stages, we created proofs via the function called unsafeCoerce
, if some desired condition holds. After that, we used the Evidence
data type that came from dimensions
, where, however, typelevel proofs are created via the same unsafeCoerce
. Here is a simple example from the library:
sameDims :: Dims (as :: [Nat]) > Dims (bs :: [Nat]) > Maybe (Evidence (as ~ bs))
sameDims as bs
 listDims as == listDims bs
= Just (unsafeCoerce# (E @('[] ~ '[])))
 otherwise = Nothing
In addition to typesafe dimensions itself, we also need to check their properties. For example, we need to make sure that the number of columns is less or equal to the input number. We have already promoted our dimensionality into the type level, but we also should verify their properties at the type level. Let’s look at a simple example.
...
case (sing :: Sing desired) %<= (sing :: Sing x) of
Proved LEQ > foo @desired @x
Disproved _ > error "Something went wrong"
...
foo :: forall (d :: Nat) (x :: Nat). (d <= x ~ 'True) => ...
where desired
and x
are types of the kind Nat
, unknown at compiletime; foo
is an arbitrary function. Here, LEQ
is a constructor of the data type (:<=:)
that we introduce below.
This constructor stores a proof that the first argument is less or equal to the second one.
Here, @
came from the language extension called TypeApplications
. This extension allows us to apply functions to types as arguments explicitly.
The example above is quite close to real code. We don’t know any specific dimensions at compiletime; validation of the property occurs at runtime. We ensure type system that the property holds and in the case of success, we can use it and satisfy the constraint of the function called foo
. After that, we use this property in foo
and other functions which will be called in foo
.
(:<=:)
is a data type implemented as GADT that keeps a justification that $a \leq b$:
data (a :: k) :<=: (b :: k) where
LEQ :: forall k (a :: k) (b :: k). ((a <= b) ~ 'True) => a :<=: b
In other words, it can’t be even constructed if a > b
. And the very fact of its existence proves the property. It is similar to the method of the type class SDecide
called %~
type from singletons
which creates a proof of propositional type equality. Similarly, (%<=)
is a method of the kind class LEQDecide
that we’ve introduced:
class LEQDecide k where
(%<=) :: forall (a :: k) (b :: k). Sing a > Sing b > Decision (a :<=: b)
infix 4 %<=
It compares two values of the Sing x
type at runtime and yields a value of the type Decision
(a :<=: b)
. Let us describe the Decision
data type. In some cases, one may prove decidability of certain relations and predicates. In logic, a proposition $P$ is decidable if either $P$ is provable or $\not P$. In other words, we have a way to tell “Yes” or “No” based on what exactly is provable: the statement or its negation. In Haskell, the Decision
data type expresses the decidability of the proposition. This type consists of the following two constructors. The first one is called Proved
. This constructor stores the term that proves the desired statement. The other constructor Disproved
contains a proof of negation, that is, a function a > Void
, so far as an empty type is an absurd constant logically.
Let’s look at some examples of matrix operations that we implemented via our dimensional matrix data type. In these examples, functions mulM
and transposeM
are exactly sequential matrix product and transpose functions from repalinearfunctions
carried through our DimMatrix
data type. It’s not so difficult, really:
mulM
:: forall y1 x1 y2 x2 r. (x1 ~ y2)
=> DimMatrix r y1 x1 Double > DimMatrix r y2 x2 Double > DimMatrix r y1 x2 Double
mulM (DimMatrix m1) (DimMatrix m2) = DimMatrix $ m1 `mulS` m2
transposeM :: DimMatrix r y x Double > DimMatrix r x y Double
transposeM (DimMatrix m) = DimMatrix $ transpose m
PPCA is one of the simplest procedures of dimensionality reduction. PCA is a widely used technique in pattern recognition and data compression. The process of principal components computation reduces to the finding eigenvectors and eigenvalues of the given covariance matrix. A covariance matrix is a degree of a spread of data in the given observation set. An eigenvector is a nonzero vector such that an application of the given linear operator yields the same vector up to a scalar factor, i. e. eigenvalue. You may read the more detailed description of PCA and its extensions here. Also, there is quite informative visualisation of this procedure.
PCA works as follows. Suppose we have some data set, which defines as a twodimensional matrix $M \in \mathbb{R}^{n \times m}$ of real numbers and each row represents a single observation. At the next step, we need to subtract the mean from our data set for each dimension to obtain a new equivalent data set, which mean equals to zero. After that, we compute the covariance matrix, eigenvectors and eigenvalues to form the set of feature vectors. There is a statement that eigenvectors of covariance matrix form a new basis in the observed space. Eigenvector with the largest eigenvalue forms the axe with the highest dispersion along with it and the lower eigenvalue, the lower dispersions along with the corresponding axe. We can drop eigenvectors with the lowest eigenvalues and reduce the dimension with fewer information losses.
Finally, the reduced principal component matrix is the product of a feature vector matrix and a transposed meanadjusted data set that we have already obtained on the previous step. Note that the number of intended principal components is passed as a separate parameter.
Now, we take a look at our PCA implementation in Haskell. We define PCA as a record data type as follows:
data PCA = PCA
{ _inputData :: Matrix D Double
, _covariance :: Matrix D Double
, _eigenVectors :: Matrix D Double
, _eigenValues :: Matrix D Double
, _finalData :: Matrix D Double
, _restoredData :: Matrix D Double
, _meanMatrix :: Matrix D Double
}
Names of these fields correspond to their roles: _inputData
is an input matrix, etc. The typesafe version of PCA
data type is implemented via DimMatrix
data type, which we have already introduced above.
data TypeSafePCA =
forall x y d. (d <= x ~ 'True) => TypeSafePCA
{ desiredDim :: Proxy d
, inputData_ :: DimMatrix D y x Double
, covariance_ :: DimMatrix D x x Double
, eigenVectors_ :: DimMatrix D x x Double
, eigenValues_ :: DimMatrix D x One Double
, finalData_ :: DimMatrix D y d Double
, restoredData_ :: DimMatrix D y x Double
, meanMatrix_ :: DimMatrix D y x Double
}
In this data type, we have existentially quantified dimensions, where y
is a number of columns, x
is a number of rows, d
is a required number of rows for the final data in an output matrix. Also, we pass a justification that $d \leq x$ as the coercion constraint d <= x ~ 'True
between typelevel less or equal predicate and Boolean value True
promoted via DataKinds
.
Now we have the following set of function that makes PCA from an input matrix:
makePCA :: Int > Matrix D Double > PCA
makePCA dim input =
case toSing (intToNat dim) of
SomeSing (sd :: Sing desired) > withSingI sd $ withMat input $ \\(inputMatrix :: DimMatrix D y x Double) >
case checkInput (Proxy @desired) (Proxy @x) of
Proved LEQ > convertTypSafeToPCA $ makePCATypeSafe (Proxy @desired) inputMatrix
Disproved _ > error "Error: desired dimension is greater than an old number of rows"
The makePCA
function takes a new number of dimensions and matrix of real numbers as arguments and yields PCA
record. In this function, we promote our required dimension using the functions called toSing
and intToNat
, where toSing
is a method SingKind
kind class. intToNat
is a map between integers and natural numbers defined quite naturally. The result of this embedding is a value of type SomeSing (Sing desired)
, where desired
is our integer argument obtained after this sophisticated promotion and SomeSing
is a container for a singleton unknown at compiletime.
checkInput
is a function that yields the decision of $d \leq x$, where d
and x
are proxy arguments. Note that these typelevel naturals should have instances of the SingI
type class. It ensures that our type has a corresponding singleton type.
checkInput
:: forall (d :: Nat) (x :: Nat). (SingI d, SingI x)
=> Proxy d > Proxy x > Decision (d :<=: x)
The main logic is implemented in the function called makePCATypeSafe
according to the informal description above.
makePCATypeSafe
:: forall d x y. (AllConstrained SingI '[d,x,y], d <= x ~ 'True)
=> Proxy (d :: Nat) > DimMatrix D y x Double > TypeSafePCA
where AllConstrained
is a type family from vinyl
that applies the given constraint to the typelevel list.
In contrast to PCA, which is completely linear algebraic, probabilistic principal component analysis, or PPCA, is a probabilistic version of PCA. PPCA is a probabilistic extension of PCA. This technique defines principal axes of a matrix via maximumlikelihood estimation applying wellknown expectationmaximization algorithm (or, EMalgorithm). We have two versions of PPCA. The first one is PPCA with socalled missed data. The second one lacks it.
Informally, PPCA works as follows. Let ${ x_i }, {i \in { 1, \dots, m}}$ be a data set, where $x_i \in \mathbb{R}^n$ and one needs to find a way to represent these data points as ${ z_i }, {i \in { 1, \dots, m}}$, where $z_i \in \mathbb{R}^{d}$ and $d < n$. The statement of the problem tells us that we need to optimise our data set somehow. That’s the same dimensionality reduction task, but, as you know, the devil is in the detail. In the case of PPCA, we work with the following linear model:
$z = W x + \mu + \varepsilon$
where $W \in \mathbb{R}^{d \times n}$ is a linear transformation matrix; $\varepsilon$ is Gaussian noise; $\mu$ is a mean. One should reach the estimation of the linear transformation matrix $W$ maximal likelihood. There is a way to obtain this estimation straightforwardly, but it’s very inefficiently.
And here comes the EMalgorithm. It is an iterative algorithm that consists of the following steps:
This way has a few advantages:
Let us consider how we formalised this procedure in Haskell. We introduce PPCA
record data type with input and output fields with the Boolean flag on the presence of missed data.
data PPCA = PPCA
{ _noMissedData :: Bool
, _learningData :: Matrix D Double
, desiredDimensions :: Int
, stopParameter :: Either Int Double
, _variance :: Double
, _W :: Matrix D Double
, _finalExpLikelihood :: Double
, _restoredMatrix :: Maybe (Matrix D Double)
}
Input parameters:
_noMissedData
 If there are no values marked as NaN
, then, in the case of True
,
we run the fast version of the EM algorithm, which doesn’t try to
restore missed values.
_learningData
 The set of observations, matrix $M \times N$
desiredDimensions
 The desired dimension of latent space. This number should be less or equal to $M$
stopParameter
 This field stores either the number of iterations or maximally allowed
change of elements of ${\bf W}$ matrix between iterations.
Output parameters:
_variance
 The final value of $\sigma^2$
_W
 The transformation matrix between latent space and observed space
_finalExpLikelihood
 Expectation of logarithm likelihood
_restoredMatrix
 The matrix with restored values. If there are no missed values in
_learningData
, then it will be Nothing
.
The function makePPCATypeSafe
takes observations, the required dimension of latent space, and termination condition. This function generates random values for ${\bf W}$ and $\sigma^2$. This function also creates matrices with dimensions in their types and runs either emStepsFast
or emStepsMissed
. Finally, the function transforms typesafe matrices of the result into the usual matrix type and yields PPCA
record.
makePPCATypeSafe :: RandomGen gen => Matrix D Double > Int
> Either Int Double > gen > PPCA
The emStepsFast
function takes observations, initial values of the linear transformation matrix ${\bf W}$ and variance $\sigma^2$, and the termination condition. The result is final ${\bf W}$, $\sigma^2$ and expectation of likelihood logarithm. Note that we require some properties of dimensions in the constraint. The function emStepsMissed
of the same type is also quite fascinating:
emStepsFast, emStepsMissed :: forall d y1 x1 y2 x2.
(x2 ~ d , y1 ~ y2, (One <= x1) ~ 'True, AllConstrained SingI [y2, x1, d])
=> DimMatrix D y1 x1 Double > DimMatrix D y2 x2 Double
> Double > Either Int Double
> (DimMatrix D y2 x2 Double, Double, Double, Maybe (DimMatrix D y1 x1 Double))
emStepsMissed
also returns the matrix of observations with restored values. Let’s look at the function more closely. It is too huge to show the whole function, so we consider the implementation partially. First of all, let us notice that there are local functions that return matrices which dimensions depend on the elements. For instance:
...
oldWP :: forall i toDel. ((i <= x1) ~ 'True, (toDel <= y1) ~ 'True, SingI i)
=> Proxy i > Proxy toDel > DimMatrix D (y1  toDel) x2 Double
oldWP iP _ = withListOfIndexes @y1 (unknownIndexes (LessEq iP)) (deleteRowsM @toDel oldW)
...
We use this function to create the set of x1
matrices ${\bf OldW}_{present}$. We remove the rows from ${\bf OldW}$ with the same index as the index of unknown value in the $i$th column of observations matrix for each $i \in {0, \dots, x_1}$ . Here ${\bf OldW}$ is ${\bf W}$(transformation matrix between spaces) from the previous iteration. As a result, we have the matrix with (y1  toDel)
columns, where toDel
depends on the number of unknown values in the $i$th column of unknown values. Its value is unknown at compiletime, but we can ensure the type checker that we checked its property ((toDel <= y1) ~ 'True
) using singletons in the same way as we have described before.
LessEq
is a constructor of data type LEQThan (x :: Nat)
and consists of Proxy i
. One may create a value of this type only if $i \leq x$.
Secondly, we may find this is quite a strange piece of code.
expX_ ::forall (i :: Nat). ((i <= x1) ~ 'True ) => Sing i > DimMatrix D x2 i Double
expX_ SZ = emptyM :: DimMatrix D x2 Zero Double
expX_ (SS l) = case lemma1 l (Sing :: Sing x1) of LEQ > withSingI l $ (expX_ l) ^++^ ((withDelNumber expXi) (LessEq (Proxy :: Proxy (i  One))))
Here we form the expectation (in terms of probability theory) of missed values and other elements in the presence of such missed values. Of course, we can’t restore the real values of missed cells, this algorithm just finds an expectation of all values. expX_
is a recursive function: at every step of recursion, the function adds a new column to the accumulator. It is a suitable example of work with dependent types. The compiler checks the body of this function and ensures that this function creates a matrix with exactly i
columns at runtime stage.
On the other hand, there is also lemma1
. Why do we need it? Unfortunately, the type checker is not so smart as we are. We should prove such trivial statements as this one:
lemma1 :: forall (n :: Nat) (x :: Nat). (('S n <= x) ~ 'True) => Sing n > Sing x > (n :<=: x)
lemma1 SZ _ = LEQ
lemma1 (SS l) (SS k) = case lemma1 l k of LEQ > LEQ
It is obvious for us that $(n + 1) \leq x$ implies $n \leq x$, but not for the compiler. Of course, we may merely apply unsafeCoerce
for similar examples, but we prefer to use it as rarely as possible. Unsafe coercion is not the way for more complicated examples.
We need this proof because at each iteration except the last one, we call this function again on (i 1)
, but we proved only that i <= x1
, not (i  1) <= x1
.
We didn’t use the singletons
functionality initially since dimensions of intermediate matrices don’t depend on their values. In other words, these dimensions depend on the input values and applied operations at each step. The case of PPCA with missed data is completely different in this aspect. That’s why we had to use singletons
when we were working on this algorithm. A need to infer some form of properties of such dimensions that may depend on intermediate matrices values caused singletons
use. By the way, one can verify within the type system quite safely that required property really has a proof as we have already shown above.
We discussed one way to reduce the debugging time and make our programs less errorprone. In this approach, matrix dimensions are lifted to the type level with the use of the Singletons library. At first glance, our solution looks a bit sophisticated. Why? There remains a question about the way to make it less devious and more idiomatic. In other words, we have to recognise the restrictions of Haskell expressive opportunities. What about performance? Also, our approach helps to remove only errors that affect the dimensions of arrays. Can we track other array parameters to reduce a set of possible runtime errors even more? We’ll talk about it in the next part of our article.
]]>Supporting children and youth doesn’t pay off quickly, therefore not every big IT company pays much attention to it. It’s way more simple to hire an experienced specialist that will bring an immediate result than searching through thousands of freshgraduates’ CVs trying to find a brilliant there. As a result, there are not so many opportunities for young people who want to grow in this domain.
We still remember ourselves in those students’ shoes and want to change the situation by developing a supportive infrastructure for young people who like computer science and programming.
To achieve that, we act in several directions. First of all, many of Serokell employees read lectures in the Computer Technology department of ITMO – one of Russia’s National Research Universities that is wellknown in a tech community because ITMO students regularly win programming contests and championships. Teaching is mutuallybeneficial, as students take advantage of a highly practicaloriented scientific approach, and we get access to the best young talents. Sounds like a dream of every HR specialist in tech, doesn’t it? That’s why some of Serokell workers are ITMO graduates.
However, sharing experience and knowledge with younger people is not enough. Communication is equally important, that’s why we also support different sciencerelated activities for youth. For example, this year, we financed the Bioinformatics Contest and are going to sponsor a computer summer school. It is a place where school students can participate in amazing workshops, learn useful things about computer science and programming, and we hope it will help them to enter great universities.
In future, we plan to become one of the sponsors of The International Collegiate Programming Contest, programming competition among the universities of the world.
Encouraging children to learn modern technologies and programming is our investment in the future. These people will be developing the industry, which, in turn, will help us to create more advanced products. We’re interested in scientific progress and want to help clever people to grow, yet that’s not all. What drives us forward is the passion for finding ways of the practical appliance of latest scientific inventions.
) *Encouraging young people to learn modern technologies and programming is our investment in the future.
Many companies are reluctant to hire young specialists without considerable job experience. It’s easy to understand their doubts since such employees often don’t know how to work in a team and may not be very welldisciplined. But what these big and rich companies tend to forget is that young specialists are usually openminded, have uptodate knowledge as well as unhindered perspective.
For this reason, we are not scared of hiring fresh graduates. We give them an opportunity to touch real projects, and they work under the control of experienced core teams. Of course, beginners never work on crucial parts of projects, but we make sure they get their portion of real industrial coding. Serokell CEO Arseniy Seroka explains how it works:
We never judge potential employees by their age. If a person is young and clever but doesn’t have enough skills to organise their work well, we will offer them a wellorganised infrastructure and help them to operate in a way that is beneficial for both sides.
The idea is to make our company a supportive and friendly workspace for young employees. One of the basic principles of Serokell is freedom of expression, and every intern can discuss everything with the team lead or CEO, ask for help and take part in the decisionmaking process.
Serokell also runs an internship program for university students. In our company, they get an opportunity to participate in real internal projects and to hone their skills in the industry. After the internship, many of those yesterday’s students start to work at Serokell.
) *We want to make our company a supportive and friendly workspace for young employees.
It’s fair enough that big businesses are primarily interested in money. We don’t have anything against money either, actually. But some people want to move forward and drive human progress and don’t think about profits in the first place, namely scientists. One doesn’t usually get into science to make a fortune but rather to make the world a better place. Arseniy states:
Science, selfdevelopment and progress are the most important things in life, and I want Serokell to help the progress and reach the most ambitious goals.
It’s not an exaggeration to say that most of the Serokell team members share that view.
The company has farreaching plans to keep supporting science. In particular, we are working on organising our commercial departments so that they can support a noncommercial department where scientists will be free to work on projects they’re passionate about and occasionally create something that may change the world.
We’re doing some good stuff already – we contribute to GHC, conducted a Numeric Haskell research, organised a Machine Learning Laboratory, and we’re going to keep developing our noncommercial activity. Our goal here is not only to blend together business and scientific progress but to prove that such an approach can be successful in the hope that others may follow the example.
And that’s not all we do. In Serokell, we always encourage our colleges to write scientific papers and participate in conferences in their paid time. We also translate the most interesting articles written by young scientists and post them in a special section of the blog (just filter out Mathematics, and you’ll see them).
We’re always eager to collaborate with openminded, passionate people, so if you feel we have similar views or want to discuss anything, don’t hesitate to contact us in social media or via email: hi@serokell.io.
]]>Let us take an interval $[0,1)$ of real numbers and lengthen it with the same interval to the right; now we have $[0,2)$. Nonetheless, nothing has changed topologically: $f : [0, 1) \to [0, 2)$, such that $f(x) = 2 x$, is a homeomorphism. Adding one more such an interval will result in [0,3) which is still homeomorphic, i.e., topologically equivalent to $[0,1)$.
Now, let us combine an infinite number of such intervals. For every natural number, let us take a copy of $[0,1)$ and concatenate them. More precisely: let us take a Cartesian product $\mathbb{N} \times [0,1)$ and with the lexicographic order – for $(n,x)$ pairs we first compare $n$ and then $x$. If we interpret elements of a pair as integral and fractional components of a real number, we can say that we have built an infinite ray $[0, \infty)$. Sadly, we still have not got anything new, as rays $[0,\infty)$ and $[0,1)$ are homeomorphic, for example, by mapping $x \mapsto \frac{x}{x + 1}$.
Let us not despair and add more and more intervals until we obtain something that differs from [0,1). But we will have to keep adding it infinitely, or uncountably. It can be shown that for any countable ordinal $\alpha$ a set $\alpha \times [0, 1)$ with the lexicographic order is homeomorphic to $[0,1)$.
But there are uncountable ordinals as well. Let us take the smallest uncountable ordinal expressed as $\omega_1$ and build $\omega_1 \times [0, 1)$ the same way. The obtained space is called the closed long ray and serves as one of the basic counterexamples of topology. Here are several of its remarkable properties:
By removing the smallest element (0,0) from the closed long ray, we obtain the open long ray. By attaching the reversed open long ray to the left of it, we obtain the long line. The aforesaid spaces have the same insane properties as the closed long ray.
References
Do you like the subject? Then read more articles from the mathematical series in our blog.
]]>Photo by Manuel Chakravarty
YOW is one of the major organizers of events for software developers. If we needed to pick only one event for functional programmers to participate, Lambda Jam would be our choice. They have a good program committee and experience to gather Scala, Elixir, Haskell, Clojure, Elm, F#, and Functional Javascript developers under one roof every year in a very friendly atmosphere.
Price: n/a.
Photo by Patrik Jansson
The Symposium is one of the most significant Haskell events that is not focused exclusively on theory or practice. All in one. There you will find discussions about the future of the Haskell programming language and current researches as well as tool reviews and experience reports. This year, the program committee includes our colleagues from WellTyped, NIA / NASA Formal Methods, Bloomberg and Universities all over the world.
Price: 400 USD; free for student volunteers.
Photo by Kalev Alpernas
The annual Symposium on Principles of Programming Languages is a forum where one can discuss all aspects of programming languages and programming systems. This event is not only for Haskellers. Why did we choose it? Because of the speaker list that stays impressive year by year. On this event, people from big companies and small startups meet together and share their outstanding ideas from the same stage.
Price: n/a; free for student volunteers.
Photo by Marcelo Lazaroni
This conference is a traditional event held by Skills Matter, one of the biggest communities for education and skills exchange. During the event, you will have an opportunity to meet the best Haskellers and discuss new technologies in a relatively informal way, so that even beginners will have a chance to communicate with toplevel professionals from different countries. By the way, this year the list of speakers is pretty awesome – take a look at their program.
Price: 695 GBP (495 GBP until July 8th); free for volunteers.
Photo by Compose:: Conference
This conference is not only about speakers and companies they represent. The announced topics are stunning. For example, Donya Quick will have a talk about the process of algorithmic music creation. Sounds good, doesn’t it?
Price: 250–500 USD.
Photo by Manuel Chakravarty
If you plan to visit London next month, there’s also one of the remarkable events you may attend without waiting for Skills Matter’s conference. There will be speakers from Independent, Microsoft Research, Mozilla, IOHK, Google, Facebook and other big companies. Hot topics and small talks during coffee breaks – don’t miss a chance to get the most from this summer.
Price: 100–600 EUR.
Photo by Masha Traskovskaya
When we talk about big conferences, we think about London or other big cities in Western Europe. But the world around us is changing, and it’s time to look at what is happening in the East. f(by) is held in Belarus, and the event definitely can be compared with famous big conferences all over the world. From the IT side, Belarus is famous not only for the f(by) conference. If you’re going to visit Minsk, find time to look at their Tech Park.
Price: n/a.
Photo by Rahul Goma Phulore
LambdaWorld is a perfect choice for people who want to share their experience with the functional community – Scala, Clojure, Haskell, Kotlin developers will be there to talk about their work and business opportunities. It is held in Seattle and Cadiz, an ancient Spanish port city.
Price: 335–350 USD in Seattle, 75–150 EUR in Cadiz.
Photo by Stephen Pimentel
It is one of the biggest conferences for developers in North America – just take a look at their topics. All that you want – from overhyped blockchain to programming language theory. Workshops, unconference program, gourmet meals (we know how it can be important) and good prices – there’s no reason to ignore this event.
Price: 850–1350 USD.
Photo by BOB Konf
BOB is a conference for those who are interested in functional programming languages. It takes place during ICFP – the premier gathering of functional programmers. The program is divided into two tracks: for practitioners and researchers. If you are going to ICFP, you can attend this conference without any fees.
Price: 75–240 EUR.
Photo by ZuriHac
ZuriHac is the biggest Haskell hackathon organised by the Zürich Friends of Haskell association. The main goal of this coding festival is to expand the community and improve Haskell tools, libraries and infrastructure. By the way, ZuriHac is not only about hacking – but there are also interesting keynotes, tracks and opportunities for small talks. The main advantages: it is entirely free for all participants, it is both for beginners and professionals (beginners can take a mentorship during all three days of ZuriHac), and it takes place not far from Zürich, in a financial center of Switzerland.
Price: Free.
In addition, a lot of different local meetups happen every month, and they’re worth visiting if you’re in the area. For example, Toronto and Berlin Haskell users meetups. Some of such meetups possibly happen near you from time to time.
That’s all for now. If Haskell events are not enough, feel free to check out our list of Elixir conferences.
]]>While the white paper has only been out for several days, it has already received wildly contrasting opinions. Some say it will bring crypto into the mainstream and empower people all around the world, others point to several innate faults in the blockchain and lack of trust in Facebook as a company.
Together with our software engineer George Agapov, we’ve decided to skip the hot takes and controversy (no mentions of Orwell below) and review the blockchain from the technical side.
Some questions that we will answer: what are the main features of Libra, are the technical choices made there reasonable, and what do they say about the future of this project.
Libra has two main features that distinguish it from blockchains you are used to seeing.
First, it is permissioned. Facebook has partnered with several other companies (including Visa, Stripe, Uber) to create the Libra Association. Right now, its members are the only ones who will be able to validate transactions on the network and have a voice in determining the future of the protocol. Libra plans to start moving to being permissionless in about 5 years or so, but there isn’t any detailed info about it.
Second, Libra is a stablecoin. The Libra Association will store a reserve of lowrisk instruments which will back the value of the coin and provide income to sustain operation of the blockchain (users of Libra won’t receive any return from those instruments).
Libra also features a very interesting programming language for transaction processing and smart contracts, Move. Move is inspired by Rust and its resource control. We will expand more on Move in the corresponding section.
The creators of Libra have taken a safe and predictable path in their technical choices. They use a custom BFT protocol based on VMware’s HotStuff, use a variation of Merkle trees to store transactions with their history (and they have a clear strategy on how to prune all the history, retaining only a snapshot of the blockchain).
Several problems are solved by the closed quorum. For example, the intuition about 1000 transactions per second seems fair and reasonable because there are a lot of old tricks to achieve that amount of transactions if consensus is handled by a small number of entities.
The white paper states that they don’t have a solution for solving these problems in a permissionless blockchain right now, so it is unclear how and whether the transition will proceed in the future.
Move language is the most interesting part of Libra for us. And not only because we have recently been working on smart contract languages for Tezos.
Basically, Move is a contract language with an execution model close to that of Ethereum. It’s compiled to typed assembly, similarly to Michelson.
The most innovative part of it is that it applies techniques for resource control at compile time (similar to those in Rust). These are a part of Move’s type system and help prevent the creation of faulty smart contracts.
To show an example, below is some Move code from the white paper. The main idea is that the coin can be only moved (not copied like payee) and that can happen only once. Anything else will be rejected by the type system.
public main(payee: address, amount: u64) {
let coin: 0x0.Currency.Coin = 0x0.Currency.withdraw_from_sender(copy(amount));
0x0.Currency.deposit(copy(payee), move(coin));
}
This use of linear logic matches the semantics of financial contracts nicely, although the syntax they use seems overcomplicated (it’s not entirely clear whether all the complexity from Rust is necessary).
Overall, innovations in this space are welcome and necessary, because smart contract languages in use right now are far from perfect. Bitcoin script is elegantly simple, but hard to use and easy to make mistakes with. Solidity is notorious for its design flaws. Even Michelson has some strange semantics. Move doesn’t look like anything that will be miles above the rest, but this feature will make it stand out.
Is Libra a magnificent failure like some say? Probably not, at least technically. It’s wonderful to see large companies trying out blockchain solutions and even innovating in some places.
The solutions used seem reasonable and suitable for their goals. Whether Libra will fulfill its promises of becoming open, we will have to see. Right now, we can only wish Zuckerberg the best of luck in fighting the regulators.
]]>Michelson is a smart contract language from the Tezos community. Akin to Forth, Michelson contract is described by a sequence of instructions operating on a typed stack. Each instruction assumes a stack of a certain type as input and produces an output stack of determined type. For example, the PAIR
instruction presumes a stack of the type a : b : s
and produces a stack of the type pair a b : s
for any stack tail s
. You can read more about Michelson instructions and typing in the official documentation.
In January 2019, in collaboration with Tocqueville Group, Serokell started the Morley project. One of its goals was to implement a comprehensive framework for testing arbitrary Michelson contracts that would support simple unit testing as well as more complex propertybased testing. Being more precise, we wanted to take a contract and feed with various sets of input and output values and see if it behaved as expected.
A small remark before we go forward. In this article, we cover only a small subset of the Michelson’s instructions and consider only the core of the Michelson’s type system without taking annotations into account. Clearing up all these details was a complicated task we performed during our work on the Morley framework, and we welcome everybody to go and check the repository to see the implementation of a type check and interpretation with all underlying details.
It was decided to use Haskell for the Morley implementation, and firstly we developed the Michelson language as a very simple AST data type:
data T =
Tint
 Tnat
 Toption T
 Tlist T
data UVal =
UInt Integer
 USome UVal
 UNone
 UList [UVal]
data UInstr =
UNOP
 UDROP
 UDUP
 USWAP
 UPUSH T UVal
 UPAIR
 UCAR
 UCDR
 UADD
 UCONS
 UNIL T
 UIF_CONS
Soon we understood this simple AST suffered from certain limitations. First of all, it was untrivial to generate arbitrary looselytyped values. In our AST, list was merely a constructor UList [UVal]
and we couldn’t write an Arbitrary
instance that would generate an arbitrary list of integers or strings depending on the type context.
The answer to this problem was obvious: create an AST with stronger types. Expression then becomes annotated with a type, which is easy to implement thanks to awesome GADTs
and DataKinds
extensions. This immediately solves the problem of arbitrary value generation. And moreover, it becomes possible for the interpreter to stop unpredictably failing with runtime type errors.
data Val t where
VInt :: Int > Val 'TInt
VNat :: Word > Val 'TNat
VList :: [Val t] > Val ('TList t)
VPair :: Val p > Val q > Val ('TPair p q)
But after introducing this type, we quickly found ourselves at a challenge. It was very easy to parse textual code representation to a simple AST but was obscure how to do the same for a typed representation of Michelson. To simplify things, instead of parsing, we considered a task of conversion from a simple AST to a typed AST.
The first problem with conversion from a simple AST to a typed representation was that conversion of a parent branch in an AST depended on the types of children. A useful trick for solving this issue can be found in the blog post from 2009. In short, we create an existential type holding value along with its type and return this existential type from our type check function:
data Sing (t :: T) where
STInt :: Sing 'TInt
STNat :: Sing 'TNat
STList :: Sing t > Sing ('TList t)
STPair :: Sing p > Sing q > Sing ('TPair p q)
data SomeVal1 where
SomeVal1 :: Val t > Sing t > SomeVal1
typeCheckVal1 :: UVal > Maybe SomeVal1
typeCheckVal1 (UInt i) = Just $ SomeVal1 (VInt i) STInt
typeCheckVal1 (UPair p q) = do
SomeVal1 pv pt < typeCheckVal1 p
SomeVal1 qv qt < typeCheckVal1 q
pure $ SomeVal1 (VPair pv qv) (STPair pt qt)
typeCheckVal1 (UList _) = error "not implemented"
The Sing
data type can be derived automatically with the use of the singletons library. That library provides the Sing
data family and useful helper functions and classes for work with singletons. Throughout this article, we’ll stick to handwritten Sing
and conversion functions.
There are two major problems with this construction. First, a reader may have noticed that neither STNat
nor VNat
constructors were ever used. Indeed, the UInt
constructor from a simple AST was meant to represent both signed and unsigned integers because they have roughly the same representation. We can not really distinguish between TInt
and TNat
literals during parsing.
A similar issue appears in the case of a list constructor with an empty list wrapped in it. When given a list constructor, we have no idea what type of values this list contains. In the case of an empty list, we must return the forall t. TList t
type, but our type representation does not support such a construction.
The second problem with this snippet is similar. In case of a nonempty list, we can take t
as a type of the first element and figure out if other elements in the list have the same type by comparing them. But to compare a t1
type of the first list element with a t2
type of the second list element, we need constraints Typeable t1
and Typeable t2
to hold.
It’s relatively easy to address the second problem. We introduce a SomeVal
data type with Typeable
constraint put in the scope of the constructor:
data SomeVal where
SomeVal :: Typeable t => Val t > Sing t > SomeVal
The first problem requires a switch to a different approach for type checking. One way to solve the problem is to introduce some sort of constrained forall
quantifier into our simplistic type system, similar to what we have in Haskell. For the empty list case, we can write something like SomeVal (VList []) (forall n. Num n => n)
. This approach is more universal but far heavier to implement and maintain.
Luckily for us, a lighter approach is possible for type checking Michelson programs. Although all Michelson instructions are polymorphic, Michelson programs are always given in the context of a contract. A contract defines an input stack type, and each instruction modifies a stack type in an unambiguous way. Hence, there is no actual need to implement a type checking algorithm that derives a type (with some forall
quantifiers)
for an arbitrary sequence of Michelson instructions. We are going to start with the input stack type and iterate through instructions. This way, we’ll be able to stick to the type system represented by the T
data type.
In this example, we considered only the type checking of values. Following the rule defined above, we’ll implement the typeCheckVal
function with the first argument being a type of an expression we’re trying to parse. There are only two instructions that introduce new value to the stack (namely, UPUSH
and UNIL
) and both of them explicitly have type representation included.
typeCheckVal :: Sing t > UVal > Maybe (Val t)
typeCheckVal STInt (UInt i) = Just (VInt i)
typeCheckVal STNat (UInt i) = do
guard (i >= 0)
pure (VNat $ fromIntegral i)
typeCheckVal (STPair pt qt) (UPair p q) = do
pv < typeCheckVal pt p
qv < typeCheckVal qt q
pure $ VPair pv qv
typeCheckVal (STList t) (UList l) =
VList <$> mapM (typeCheckVal t) l
typeCheckVal _ _ = Nothing
We do not actually need to wrap Val t
into SomeVal
here because the first argument nicely defines the output of the function. It’s important to emphasise the role of singletons in the construction above. What we do is patternmatching on the type of value that should be parsed. Patternmatching on type in the code of termlevel functions is not common in Haskell, and singletons are perhaps the most straightforward way.
Now, let’s implement a conversion for instructions. First, we’ll have to modify our Sing
data type slightly and provide some required helper functions:
data Sing (t :: T) where
STInt :: Sing 'TInt
STNat :: Sing 'TNat
STList :: Typeable t => Sing t > Sing ('TList t)
STPair :: (Typeable p, Typeable q)
=> Sing p > Sing q > Sing ('TPair p q)
fromSing :: Sing t > T
fromSing = ...
data SomeSing where
SomeSing :: Typeable t => Sing t > SomeSing
withSomeSing
:: SomeSing
> (forall t . Typeable t => Sing t > a)
> a
withSomeSing (SomeSing a) f = f a
toSing :: T > SomeSing
toSing = ...
Similarly to values, we’ll define a typed representation of an instruction. The Instr
data type is parametrized by type parameters inp
and out
of kind [T]
which state for input and output stack types corresponding to an instruction. This Michelson instructions representation is very elegant as it perfectly mimics the notation given in Michelson’s documentation.
data Instr (inp :: [T]) (out :: [T]) where
Seq :: Instr a b > Instr b c > Instr a c
Nop :: Instr s s
DROP :: Instr (a ': s) s
DUP :: Instr (a ': s) (a ': a ': s)
SWAP :: Instr (a ': b ': s) (b ': a ': s)
PUSH :: Val t > Instr s (t ': s)
PAIR :: Instr (a ': b ': s) ('TPair a b ': s)
CAR :: Instr ('TPair a b ': s) (a ': s)
CDR :: Instr ('TPair a b ': s) (b ': s)
NIL :: Instr s ('TList t ': s)
CONS :: Instr (t ': 'TList t ': s) ('TList t ': s)
ADDii :: Instr ('TInt ': 'TInt ': s) ('TInt ': s)
ADDnn :: Instr ('TNat ': 'TNat ': s) ('TNat ': s)
ADDin :: Instr ('TInt ': 'TNat ': s) ('TInt ': s)
ADDni :: Instr ('TNat ': 'TInt ': s) ('TInt ': s)
IF_CONS :: Instr (a ': 'TList a ': s) s'
> Instr s s'
> Instr ('TList a ': s) s'
The representation of the ADD
instruction is not very nice but can be improved with the use of a type class. We may create a type class AddOp
which takes two type arguments (for two operands of the ADD
instruction). It will contain one function for type checking, one function for interpretation and a type family for the result type. For simplicity, this is not implemented in the article’s code.
Our function typeCheckI
will take an input stack type and an untyped instruction and should return an output stack type along with a typed instruction. Hence, we introduce Stack
and SomeInstr
data types. The Stack
data type is similar to Rec
from the vinyl package. The only difference is that we impose the Typeable
constraint on the first argument of :&
.
data Stack inp where
SNil :: Stack '[]
(::&) :: (Typeable s, Typeable a)
=> Sing a > Stack s > Stack (a ': s)
infixr 7 ::&
data SomeInstr inp where
(:::) :: Typeable out
=> Instr inp out > Stack out > SomeInstr inp
infixr 5 :::
Now we’re able to finally implement the typeCheck
function:
typeCheckI
:: Typeable inp => Stack inp > UInstr > Maybe (SomeInstr inp)
typeCheckI s UNOP = pure (Nop ::: s)
typeCheckI (_ ::& s) UDROP = pure (DROP ::: s)
typeCheckI (a ::& s) UDUP = pure (DUP ::: a ::& a ::& s)
typeCheckI (a ::& b ::& s) USWAP = pure (SWAP ::: b ::& a ::& s)
typeCheckI s (UPUSH t v) = withSomeSing (toSing t) $ \\t' > do
val < typeCheckVal t' v
pure (PUSH val ::: t' ::& s)
typeCheckI (a ::& b ::& s) UPAIR = pure (PAIR ::: STPair a b ::& s)
typeCheckI (STPair a _ ::& s) UCAR = pure (CAR ::: a ::& s)
typeCheckI (STPair _ b ::& s) UCDR = pure (CDR ::: b ::& s)
typeCheckI (STInt ::& STInt ::& s) UADD = pure (ADDii ::: STInt ::& s)
typeCheckI (STNat ::& STNat ::& s) UADD = pure (ADDnn ::: STNat ::& s)
typeCheckI (STInt ::& STNat ::& s) UADD = pure (ADDin ::: STInt ::& s)
typeCheckI (STNat ::& STInt ::& s) UADD = pure (ADDni ::: STInt ::& s)
typeCheckI s (UNIL t) = withSomeSing (toSing t) $ \\t' >
pure (NIL ::: STList t' ::& s)
typeCheckI ((_ :: Sing a) ::& STList (e :: Sing b) ::& s) UCONS = do
Refl < eqT @a @b
pure (CONS ::: STList e ::& s)
typeCheckI (STList a ::& s) (UIF_CONS consCase nilCase) = do
nc ::: (s' :: Stack out1) < typeCheck s nilCase
cc ::: (_ :: Stack out2) < typeCheck (a ::& STList a ::& s) consCase
Refl < eqT @out1 @out2
pure (IF_CONS cc nc ::: s')
typeCheckI _ _ = Nothing
typeCheck
:: Typeable inp => Stack inp > [UInstr] > Maybe (SomeInstr inp)
typeCheck s [] = pure (Nop ::: s)
typeCheck s (i : []) = typeCheckI s i
typeCheck s (i : is) = do
a ::: s' < typeCheckI s i
b ::: s'' < typeCheck s' is
pure (a `Seq` b ::: s'')
In typeCheckI
, we patternmatch on an input stack type and an untyped instruction. In the case of CONS
, we need to additionally check the equality of first element of the stack and an subtype of the list at the second element of the stack. In the case of IF_CONS
, a recursive call to typeCheck
is used to check both continuations.
Now, when we have finally settled down how to type check a sequence of Michelson instructions, let’s see how our eDSL can be interpreted.
interpret
:: Rec Val inp > Instr inp out > Rec Val out
interpret s Nop = s
interpret s (Seq a b) = interpret (interpret s a) b
interpret (_ :& s) DROP = s
interpret (a :& s) DUP = a :& a :& s
interpret (a :& b :& s) SWAP = b :& a :& s
interpret (a :& b :& s) PAIR = VPair a b :& s
interpret s (PUSH v) = v :& s
interpret (VPair a _ :& s) CAR = a :& s
interpret (VPair _ b :& s) CDR = b :& s
interpret (VInt a :& VInt b :& s) ADDii = VInt (a + b) :& s
interpret (VInt a :& VNat b :& s) ADDin = VInt (a + fromIntegral b) :& s
interpret (VNat a :& VInt b :& s) ADDni = VInt (fromIntegral a + b) :& s
interpret (VNat a :& VNat b :& s) ADDnn = VNat (a + b) :& s
interpret s NIL = VList [] :& s
interpret (a :& VList l :& s) CONS = VList (a : l) :& s
interpret (VList [] :& s) (IF_CONS _ nilCase) = interpret s nilCase
interpret (VList (a : l) :& s) (IF_CONS consCase _) =
interpret (a :& VList l :& s) consCase
Interestingly, the interpret
function is total, which is a definite benefit of advanced type representation. The Val
data type contains enough information for a type checker to consider all possible cases of an input stack and instruction, and there’s no need to perform additional checks in runtime, which is an errorprone practice. In short, if the program type checks, it won’t produce an error in runtime.
To sum up, in this article we’ve seen how to apply some advanced typing techniques of Haskell to typecheck simple strictlytyped stackbased language. We took a simple representation of language AST (which can be easily obtained from a text using any technique for parsing) and converted it to a strictlytyped representation of the same language. The strictlytyped representation is in 1:1 correspondence to the type system of defined language, and by having a term in strictlytyped representation, we are assured that it’s welltyped. The approach described is heavily dependent on the fact that the type system doesn’t define abstract types and all types are concrete, i.e. only builtin constructions are polymorphic. This restriction applies to a wide variety of languages, hence we see our approach as highly reusable.
]]>Nowadays, most of the cryptographic operations use computers. Since a computer is a deterministic device, it isn’t able to simply generate a truly random number. In this case, one may use various sources of entropy form the outer world, such as thermometer or voltmeter readings. Those numbers can be considered fairly random.
The solution mentioned above still has a problem: it’s impossible to get a lot of samples very fast, while sometimes that’s exactly what is needed. In order to solve this problem, pseudorandom number generators were invented. They generate several genuinely random numbers which become seeds for generating as many pseudorandom numbers as needed.
However, this solution has its weaknesses as well. If a generator turns out to be predictable, malicious actors will be able to break the encryption. We need to prevent that from happening! Thinking the same way, Intel engineers added the RDRAND to the x86 instruction set. According to Intel, the instruction allows quickly generating random data based on some hardware signals. Tempting, right?
Unsurprisingly, the Linux kernel developers took it with a grain of salt but, nevertheless, considered the approach. In a similar way, Linux allows getting random numbers by reading the file /dev/urandom
. Although one may suggest using /dev/random
for true randomness, there exist some concerns even about the safety of /dev/random
.
So, back to /dev/urandom
. How does the data get in it? One can use the following trick. Let we have several independent sequences of random numbers. Then, if any of them is ‘good,’ a sequence obtained as a result of XOR
of all of them will be ‘good’ as well. Perfect. Intel gave us a new opportunity to generate random numbers, so let’s take a sequence of pseudorandom numbers and XOR
it with an output of RDRAND and use the resulting sequence as an output of /dev/urandom
. If RDRAND is a good generator, then the /dev/urandom
also becomes good. If RDRAND is not good, nothing gets worse. In any case, we win, thank you Intel!
But all of that is not true. The old version of /dev/random
is a random number generator, but there’s one problem. The trick described earlier requires that the data comes from independent sources. One may argue: they must be independent because RDRAND isn’t used in generating a pseudorandom sequence for /dev/urandom
and vice versa. But what if Intel lies us about the randomness of the data that RDRAND outputs, and a processor has been designed in a particular way to harm users? For example, a processor before executing RDRAND can check that the executing code behaves like a process of generating pseudorandom numbers for /dev/urandom
, read what has already been generated and return the value that after the use of XOR
to what we have will output the needed number. This way they can control the data /dev/urandom
generates. If such output is used for generating cryptographic keys, they will be able to access those keys.
‘But come on, nobody would do it,’ you may say. ‘We are talking about a processor, a piece of hardware. After it’s been produced, nothing can be changed in it. They won’t try so hard to configure it.’ Well, I have bad news for you: what I’ve described earlier had already been implemented in Bochs, an emulator for several stock CPUs. I have good news as well: an emulator is not a processor; this modification wasn’t implemented in a processor. Oh, some more bad news: it actually can be modified in a processor even after a factory production stage. For more than 20 years processors have supported microcode updates – and one can download an update that will change some of the instructions.
Final good news: RDRAND is not used in Linux anymore, so we’re safe. Well, actually, disregard the last statement about safety – but that should be a topic for another article.
Do you like the subject? Read other articles from the series – about the problem of intermediate recursively enumerable Turing Degrees and about the challenge of modeling large quantum computers.
]]>We investigate features of functional programming in the context of machine learning. Python and R are the most widely used languages in this area. One can implement almost any model using such libraries as NumPy or TensorFlow. In this post, we discuss Haskell advantages in comparison to Python and R. Linear algebra is one of the main mathematical tools in machine learning, together with probability theory and statistics. Therefore our research aims to investigate and improve the Haskell tools.
Haskell is a polymorphic functional programming language with lazy evaluation. Haskell has a set of impressive characteristics such as parametric polymorphism, strong and powerful type system, easier refactoring and debugging. Haskell has already proved its efficiency in programming language design, blockchain systems, concurrent and parallel computation, etc. In our opinion, the potential of using Haskell in machine learning is not investigated deep enough. We want to highlight several examples of previous works related to both functional programming and machine learning.
In addition, we recommend reading quite interesting blog post series called Practical Dependent Types in Haskell: TypeSafe Neural Networks by Justin Le. It’s also useful to have a look at the haskellml library, where machine learning examples are based on Le’s ideas, and at this recourse as well, where Haskell ecosystem is described pretty fine in projection to machine learning and data science.
We’re going to discuss rather technical aspects of implementation than machine learning itself. In machine learning and related areas, one often has to deal with multidimensional data, which we usually express as a matrix. In the case of Haskell, purity, immutability and easiness in translator development provide ways of making a computation unparalleled and transforming a code for execution on alternative architectures such as NVidia microarchitectures.
Working with multidimensional data requires using linear algebra. We consider a dataset as a twodimensional matrix, each column of which is a single observation of some phenomena from the external world. Dimensions of such dataset are quite large, which makes data processing and storing too expensive. On the other hand, there might be latent correlations between observations. The presence of these correlations facilitates the representation of the given data set with lower dimensions. That is, seeking dependencies of this kind allows optimising input data. Machine learning provides a huge set of procedures for extracting the primary information from our dataset and representing the given observations up to correlation between them.
When one writes code with a huge number of matrix operations, such as matrix transposition, a lot of regrettable errors arise. These mistakes have no connection with types, so typechecker cannot detect them. The best case scenario is a runtime error. Sometimes, a process of evaluation completes successfully without any runtime errors, but the result might differ from the expected one. For instance, an output matrix might have the wrong dimensions. Sometimes there is no way to identify such bugs immediately. Worst of all, there might be a socalled “floating” error that reproduces periodically, on a casebycase basis. Floating errors are the most difficult in debugging so far because it’s not straightforward to determine the cause of such failures. Moreover, it’s not clear why there are exactly these dimensions, not any other.
These errors often affect matrix dimensions. For this reason, we gave them the highest priority. One should always pay attention to the dimension coherence when applying some matrix operations, for instance, matrix product or linear solver, where the relationship between input dimensions matters. We tried to solve the problem of typelevel dimensions to provide a typesafe implementation of widelyused and common machine learning procedures. During our investigation, we were working on the Haskell implementation of such dimension reduction procedures as (probabilistic) principal component analysis (briefly, PPCA) and Gaussian process latent variable model (GPLVM).
In our view, it’s worth to deal with typelevel matrix dimensions. Such approach to array dimension allows one to catch the whole class of errors described above and deal with some quite simple proofs, as long as we require the relationship between the dimensions and the relationship is provable.
Below we discuss and review related matrix and numerical libraries to understand which array libraries are useful for machine learning in Haskell and which approaches to typelevel natural numbers might be applied to safe matrix dimensions.
hmatrix
is one of the most popular matrix libraries in Haskell. hmatrix
provides an interface over wellknown libraries BLAS and LAPACK. The hmatrix
library was described pretty fine in the blog post by colleagues from Tweag.io and here. We’ll discuss this library quite briefly and take a look at several examples to understand how hmatrix
works.
For instance, let us consider the Cholesky decomposition. The Cholesky decomposition is an operation on matrices that decomposes the given matrix (it should be Hermitian and positivedefined) into the product of a lower triangular matrix and its conjugate transpose. This operation is widely used for solving linear systems and similar stuff. In our work, we also use the Cholesky decomposition several times.
In hmatrix
, the Cholesky decomposition is a function with the following signature:
chol :: Field t => Herm t > Matrix t
where Field
is a typeclass denoting that t
is a field. Matrix
is a data type of matrix consists of strict dimensions and storable vector which is a content of the matrix itself. Herm
is just a newtype
over Matrix
. In our research, we used hmatrix
functions implicitly. As we’ll describe later, we decided to use repa
library as the main one, and matrix and related operations in repa
are obtained from HMatrix
. See the following library for more information.
Note, that in hmatrix
there is a module called Numeric.LinearAlgebra.Static with the same functionality, but with static typelevel dimensions. For instance, there is a linear solver in hmatrix
with static dimensions:
(<>) :: forall m k n. (KnownNat m, KnownNat k, KnownNat n) => L m k > L k n > L m n
Where L
is a data type of a twodimensional matrix parameterised via its number of rows and columns. m
, n
, and k
are natural numbers (as a matter of fact, the types of kind Nat
from GHC.Types); KnownNat is a type class that connects integers and typelevel naturals. Thus, the problem of typesafety in hmatrix
solved partially, but Nat
from the module mentioned above is just a representation of typelevel natural numbers, not natural numbers in itself. The reason is that arithmetical operations on these natural numbers are implemented as open type families, but we would like to have these operations implemented explicitly. Under the circumstances, we should claim that this version of typelevel dimensions in hmatrix
is inconvenient for proofs and basic verification.
accelerate
is a package with regular multidimensional arrays. accelerate
allows writing code which will be compiled in runtime into the special domainspecific language, and then it will be compiled through LLVM for execution either on multithreaded CPU or NVidia GPU. Unfortunately, there are no such useful functions as SVD or the Cholesky decomposition, so accelerate
is not as rich as a numeric library. On the other hand, these operations are available as lowlevel bindings to CUDAsolver, although explicit functions are not implemented yet.
Examples:
map :: (Shape sh, Elt a, Elt b) => (Exp a > Exp b) > Acc (Array sh a) > Acc (Array sh b)
fold :: (Shape sh, Elt a) => (Exp a > Exp a > Exp a)
> Exp a > Acc (Array (sh :. Int) a) > Acc (Array sh a)
Exp a
represents an expression for calculation of a single value. All operations are executed sequentially in such expressions.
Acc (Array sh a)
represents an expression for calculation of an array of values of type a
and consists of many expressions of type Exp a
. Execution of all expressions happens in parallel. Such separation helps to avoid nested parallelism which is not supported now.
sh
represents dimensionality of the resulting array (DIM1
for a vector, DIM2
for matrix, etc.).
Elt
is a typeclass of array elements.
Shape
is a typeclass that represents array dimensionality.
Despite the fact that one can work with “Acc (…)” and “Exp a” like with arrays and scalars, they are not real arrays or values in runtime, but the code for their calculation. This code might be compiled and executed via the run
function either on CPU or GPU.
If you are interested in accelerate
, take a look at these examples to learn in more details how it works.
repa is a package with regular multidimensional arrays like accelerate
. But repa
has an important distinctive feature. In repa
, one may deal with arrays of a large number of representations. Array data type in repa
is an associated data type in a type class called Source, which defined as follows:
class Source r e where
data Array r sh e
extent :: Shape sh => Array r sh e > sh
linearIndex :: Shape sh => Array r sh e > Int > e
deepSeqArray :: Shape sh => Array r sh e > b > b
where r
is a type of representation and e
is a type of elements. In addition to this associated array type, this class has three methods (as a matter of fact, more than three, but these methods form the minimal complete definition).
In contrast to accelerate
, there are several kinds of array representation in repa
. D
is a data type that corresponds to a delayed representation of an array via a function from index to element. C
is a delayed representation via socalled cursor functions. Delayed and cursor arrays are rather “pseudo arrays” than real arrays. Real arrays have another kind of representation: U
and V
are unboxed and boxed arrays respectively. B
is a strict bytestring array. F
represents a foreign memory buffer. In our work, we decided to use delayed arrays by default for convenience and simplicity.
Unfortunately, it’s arrested development.
This library takes the main ideas from repa
, but it’s being actively developed by our Russian colleague Alexey Kuleshevich. In contrast to repa
and accelerate
, massiv
provides a broad set of tools for working with mutable arrays. massiv
is more productive than repa
. On the other hand, the ecosystem of massiv
lacks numerical functionality: such functions as the Cholesky decomposition are not implemented yet. But unlike repa
, this library and its ecosystem are actively developing.
dimensions is a library with safe typelevel dimensions for multidimensional data that might be used regardless of the particular data structure. This library is a part of EasyTensor package. The purpose of this library is to provide typesafety in matrices in tensor algorithms. But this efficiency was achieved to the detriment of rigidity. For example, there is a lot of unsafe coerce between types for proof creation in dimensions
. Moreover, there is a distinction between unknown and known dimensions that increases the complexity of code and multiplies boilerplate. Plus, the authors used typelevel natural numbers from GHC.TypeLits. This approach has some disadvantages that we describe below: they don’t depend on dimensions
functionality.
Typelevel natural numbers are already implemented in module GHC.TypeLits included in the base
library. This version of typelevel natural numbers works pretty fine and is widely supported with side libraries, but these numbers are not inductive datatype, so it’s quite difficult to implement the very basic proofs of trivial facts related to natural numbers. In other words, literals “2” and “134” have no connection between each other. This problem might be solved partially via some special tools, but this doesn’t solve the problem completely.
From our point of view, the library called typenatural is more suitable for this purpose. In contrast to GHC.TypeLits
, here natural numbers are introduced inductively à la Peano that allows proving some simple properties inductively.
In this part, we have introduced the problem of typesafe dimensions in Haskell and reviewed related libraries and tools. We have briefly described matrix and dimensional libraries and discussed their strengths and limitations.
In our opinion, most of the tasks we’ve solved have a more elegant solution via dependent types. In the second part, we will also discuss useful libraries that allow emulating dependently typed programming in Haskell and related difficulties. We will overview our own approach to matrix data type parameterised via its dimensions.
]]>Our СTO Jonn Mostovoy finds his hobbies to be useful for improving his strategy skills and fighting with anxiety.
I’m fond of cycling, like Arseniy, but I prefer aggressive urban biking because there are no trails in the area where I live. Also, I watch MTB races to learn which skills I should work on and pick the routes in the urban environment that have lines resembling real trails. Since I suffer from anxiety, biking is often combined for me with exposure therapy to improve my mental state. Bike riding also teaches me how to react faster.
Biking is not the only way I’m spending my free time. When the weather is not suitable, or I just don’t want to take my hardtail and go, I like playing Magic: the Gathering, which happens to be the world’s most complex game, watching matches of the ancient board game Go and big StarCraft tournaments. MTG satisfies my passion for thinking, love for unique puzzles unfolding in front of my very eyes. It helps me to evolve my style of thinking. Watching Go and StarCraft reminds me of how important it is to be patient as well as and tickles my strategyappreciation nerve.
Jonn loves playing Magic
From my point of view, a hobby is something you’re deeply passionate about, to an extent you spend a lot of your time on it without getting money. If a hobby earns you side money, it’s not a hobby anymore. It is a job already.
Chris Höppner is one of the Serokell team leads. He likes to play online games and get knowledge in different fields.
Like our CTO, I love playing StarCraft. Video gaming is fun, but in my opinion, it’s crucial to deal with discipline and patience before starting to play. And of course, one needs a thick skin (welcome to competitive online gaming). StarCraft makes me feel accomplished through the slow acquisition of a specific skill set. I spend around 10 hours a week playing games, but I also have another activity I’d call a hobby.
Gaming vibes.
My other hobby is talking to smart people and being astonished at all the things I don’t know. For example, one PhD engineer in mechanics and aeronautics is currently lecturing me on why combustion engines are more efficient than electric ones on motorised vehicles.
I describe hobby as a leisure activity, something you do regularly in the spare time for the purpose of pleasure and entertainment. It becomes a job once other people start to rely on your continued performance of the named activity and there appears sufficient monetary compensation.
Hobbies may help cure many of workers’ disorders, such as anxiety, stress and job burnout. Hobbies are also useful when it comes to socialisation: it is cool to join a group of people who do the same stuff as you. It is especially important if you’re working remotely, as having the same hobbies with your teammates gives you a chance to know more about each other. At the same time, activities that you perform alone are perfectly suitable when you want to take a break from people and concentrate on what really satisfies you.
Hint: there’s no need to chose between social and lonely activities. One can combine hobbies and get perfectly balanced results in terms of emotions. Hm, isn’t it time to have some rest?
]]>In computability theory, it is common to introduce the class of decidable languages first, and after that, prove that there are undecidable languages as well. There are two common ways to show it: a nonconstructive proof (there is an uncountable number of subsets of $Σ*$ and there is a countable number of decidable subsets) and a constructive proof (one or another way of defining the halting problem). After that, it is common to prove that a lot of more ‘natural’ problems are undecidable, but all of them are basically reduced to the halting problem. It can be shown that the set of undecidable languages built this way is countable as well. Here a natural question appears: what about all other undecidable languages since there are so many of them?
For languages $A$ and $B$, $A$ is Turing reducible to $B$ ($A ≤ B$) if $A$ is decidable with an oracle for $B$. Languages are Turing equivalent ($A ≡ B$) if $A ≤ B$ and $B ≤ A$. The ≡ relation determines equivalence classes, or Turing degrees. The set of all decidable languages is one equivalence class, while the set of all undecidable languages which are Turing equivalent to the halting problem, is another. Let us try to find more classes.
Let us assume there is an oracle $O$ for the halting problem. For a program and an input, we can determine in constant time, whether the program will halt on the input. Note, that $O$ takes as input a program without an oracle, but a program that uses $O$ (in a meaningful way) cannot be rewritten without $O$ (it is not a real program as its oracle contains something noncomputable), thus, such a program cannot be given as input to an oracle for determining, whether it will halt. Can it be determined, whether an arbitrary ‘program’ with such an oracle will halt ($O$ can be used for the solution as well)? The answer is: of course not. The proof is analogous to the proof of the halting problem undecidability (the proof is relativising, i.e., adding an oracle will not change it). It means, such a ‘superhalting’ problem is not Turing reducible to ‘normal’ undecidable languages, and it determines the third equivalence class.
Using an oracle for this new problem, we can build the fourth equivalence class, then the fifth, and so on. For an arbitrary Turing degree, its ‘+1’ can be defined as the Turing equivalence class of the halting problem relativised for the given Turing degree. Such an operation is called the Turing jump. It is usually denoted by a prime symbol. If the Turing degree containing decidable languages is 0, then we have the following simple case:
Is there anything in between?
Friedberg and Muchnik (1956) independently proved that there exists a pair of languages $A$ and $B$, such that both are decidable with an oracle for the halting problem, but $A$ is undecidable with an oracle for $B$, as well as $B$ is undecidable with an oracle for $A$. This result instantly complicates the structure of languages: we see that Turing degrees do not have a total order, and there are also intermediate degrees.
Today it is known that Turing degrees form a lower semilattice with a lot of interesting properties, such as:
Serokell team members love to spend their spare time actively and make good use of it. These people are fond of cycling, video gaming, learning foreign languages and assembling hiend audio amplifiers. What do hobbies give to our colleagues and will the hobby turn into the job if it starts to earn them the money? These were our questions, and here are the answers.
Let’s start with Arseniy, our CEO. He likes to ride a bike to get his dose of adrenaline.
I’d describe a hobby like an activity you really like on the one hand, but which is not included in your job responsibilities, on the other. And my hobby is downhill mountain biking.
Arseniy loves mountain biking.
I believe that any style of cycling is good for one’s physical and mental shape but, as for me, downhill mountain biking is also the way I get a dose of adrenaline. It is easy to start this sort of activity – just choose the right bike and don’t forget to prepare your physical form before you go mad. NB! Wear a helmet! In the future, I also want to start kitesurfing in addition to my twowheels activity.
Of course, cycling and kitesurfing cost money. But in my opinion, even if your hobby does generate some money instead of taking them from your wallet, it doesn’t become a job.
Daniel Rogozin is famous for his article series about Constructive and NonConstructive Proofs in Agda. Here is another side of our Haskell software engineer: he likes English literature and French language.
A hobby is a kind of activity out of my professional area, which I like to do from time to time. The main distinction between a hobby and a job is like that: your hobby is always connected with pleasure, and you may postpone your hobby for a better time. On the contrary, when you do your job, you should remain professional and sometimes forget about your own emotions.
I love to read English literature and poetry. My favourite English poets are William Blake and Percy Shelley. Also, I prefer Oscar Wilde and Sir Arthur Conan Doyle as novelists. It’s good that nowadays you usually don’t need to spend any money on reading, as classics are available in electronic format without any fees. I think reading classic fiction forms my cultural background.
Also, I’m trying to study French because I like this country, language and culture a lot.
I don’t have very much time for my French lessons, but I’ve found a quite interesting way of learning the language: at first, I’ve set French as a default language on my laptop. Now, my git messages sound like a passionate declaration of love:
suedehead$ git push
Énumération des objets: 13, fait.
Décompte des objets: 100% (13/13), fait.
Compression par delta en utilisant jusqu'à 4 fils d'exécution
Compression des objets: 100% (7/7), fait.
Écriture des objets: 100% (7/7), 1.39 KiB  1.39 MiB/s, fait.
Total 7 (delta 4), réutilisés 0 (delta 0)
I’m a Francophile to a certain degree.
Rinat Stryungis is one of the strictlytech guys in our company. When he is not busy doing ML research, he makes tube amplifiers!
I like tube amplifiers and DIY hiend audio. So, my hobby is assembling these things by myself. You need a basic knowledge of electrical circuit theory and intention to study more specific sections, such as feedback theory, to build your own amplifier. The main profit is sound. I like to be able to adjust the sound for myself (and I don’t mean equalisation;) ). And of course, I like to project schemes of tube amplifiers.
Rinat makes tube amplifiers. Yes, it’s fun.
Although I usually don’t spend a lot of time on it, most of my vacations I spend projecting and building amplifiers. It is a fascinating but costly hobby, and right now I am saving money for the tube curve tracer. Besides, I once tried to make my own DAC (digital to analogue converter), but it required too much learning. I gave it up when I understood that I needed to program FPGA.
In addition to being fun, hobbies make us more attractive. Imagine you cannot talk about anything except your working relations, tasks and so on. Not very tempting, huh? If you don’t know yet what is suitable for you, try thinking about your mindset, the amount of free time you have and cash you’re ready to spend — and check one or another list of hobbies. For example, this is a good one. If you are interested in more of our hobbies, check out the second part of the series.
]]>In the era of digitalisation of all areas, the manufacturability of the product becomes the main driver of business growth. Giants of the world market, such as Apple, Google, Facebook, Amazon, evolve rapidly. If your company is not so huge, to keep the pace might be tricky. Here’re just several reasons why:
The high cost of developing and supporting complex products Each development company sooner or later faces the problem of evaluating the effectiveness of departments and employees. The reasons might vary from wrong management of expectations to spending the staff time on unnecessary things.
Multiculturalism Technological giants attract specialists from all over the world, thus broadening the view on approaches to product creation. The process of relocating and onboarding new team members is fairly expensive.
Stress management Employers try to create an environment and working conditions that seem to be ideal from their point of view. However, when new team members start to work in the company, they need some time to get used to the new situation, including a workflow, control and corporate ethics. The adaptation is stressful, and one cannot just pretend it doesn’t exist. And at the same time, this period is costly for a company, as an employee is usually less efficient during it.
From these three points, it’s already possible to conclude that attracting employees from other countries and cities can be difficult, expensive and even unprofitable. And indeed it is. At the same time, an option of recruiting your own team of remote programmers, as a rule, disappears when assessing risks: it is almost impossible to control a remote team without the specific experience of such work and staff selection.
There exists a solution, of course: you can use the services of remote software development. In addition to obvious things, such as reducing the associated costs, outsourcing provides other considerable advantages:
1. You only pay for the result Fully transparent software development process with perminute control of elapsed time.
2. You hire subject matter experts We’ve already mentioned the world leaders of the IT market, who find their employees in the best educational institutions and invest in their academical and professional development. The software development outsourcing service will find the right people in the right places, invest in them and ‘grow’ the staff for you, and you will get all the benefits without a headache.
3. The product will be exactly as you expect Management expectations are the cornerstone of software development. Unclearly described technical specifications, undescribed functions or outdated protocols may prevent you from creating the perfect software. Remote work solves these issues: to remain in the market, companies that provide services for software development outsourcing, must follow or even create trends.
4. Save on infrastructure The outsourcing partner becomes responsible for all the development processes and the related infrastructure. Hence, with outsourcing one doesn’t need to invest in infrastructure.
5. Risk management Some processes may be challenging to manage and control. By delegating this responsibility to an external company, you improve flexibility and efficiency as well as lower all kind of software production risks.
6. Process transparency Remote work requires more control over the activities of the employees, and only the transparency of processes allows it to be achieved.
7. Сode reusability When hiring professionals, you can always be sure that all the program code will be well described and all features will be explained.
Software development itself is a complex and timeconsuming process. The latest achievements of computer science, such as the Internet of things, the blockchain, artificial intelligence, are becoming a part of everyday life as well as an essential part of businesses. It is simply impossible to develop without new approaches and technological solutions.
At Serokell, we compete with market giants in hiring graduates of the best technical universities from West and East Europe. We sponsor science competitions and exhibitions, conduct noncommercial researches in various branches of computer science. We do it for the professional growth of our employees who may become yours tomorrow.
Contact us, and we will tell you more.
]]>A traditional computer processes widelyknown bits. Here everything is simple: N bits imply N binary cells which at any given time can be either 0 or 1. Quantum computers use quantum bits, i.e., qubits. A qubit state is not binary. The general quantum state of a qubit can be represented by a linear superposition of its two basis vectors which refer to two classical states of a classical bit. However, when changing a qubit value, one of two classical values comes out, but now the qubit has a probability of the value ‘0’ and a probability of the value ‘1’. The mentioned probability is calculated from the vector coordinates.
It is more or less simple so far but, if we deal with a lot of qubits, it becomes more terrific. The thing is, in quantum mechanics, a tensor product of the spaces of the subsystems refers to the joint states of the two systems. But a tensor product implies that dimensions of the spaces are multiplied. Thus, to describe a 3qubit system, we need 2^3=8dimensional space. When we add 1 bit in a traditional computer, the size of the required memory increases by 1 bit, i.e., by 1 binary cell. When emulating a quantum computer using a traditional one, adding 1 qubit results in doubling of the memory in use. Can you see the problem?
For instance, when modeling coordinates of a complex vector of doubleprecision floating point types which require 8 bytes each, one complex coordinate will require 16 bytes, and N qubits require 16⋅2^N = 2^(N+4) bytes. If your computer memory equals to 8 GB (8⋅2^30 = 2^33 bytes), you’re able to emulate a quantum computer with no more than 29 qubits. I suppose, now the news about a 50qubit quantum computer sounds more impressive, right?
]]>So, the first good news is that we are working on Tezos.
Tezos is a selfgoverning blockchain providing a platform for formally verified smart contracts and decentralized applications. Stakeholders vote on amendments to the protocol, including amendments to the voting procedure itself, to reach a social consensus on proposals.
In January, we began to cooperate with CamlCase. We’re working together on the Morley library for the Tocqueville Group (TQ). The Morley library aims to make writing smart contracts in Michelson effective and enjoyable, while Michelson is the language used to write smart contracts on the Tezos blockchain. It is a stackbased and strongly typed language, designed to facilitate formal verification of contracts. As you can already guess, we are thrilled to work on it.
One of the basic principles of Serokell is to help companies that are moving forward the technological progress. In February, we sponsored and provided information support to an olympiad in bioinformatics held by the Bioinformatics Institute.
Our haskeller Danya Rogozin who is famous for his saga in three parts about Constructive and NonConstructive Proofs in Agda gets ready to participate in FPure Conference in May. It is the only functional programming conference in Russia. Danya will talk about our machine learning adventure in Haskell and discuss our solution on the question of typesafe dimensions.
We’re proud to report another scientific success of this brilliant mind. An abstract for Danya’s paper on his results in Lambek calculus has been accepted for Topology, Algebra and Categories in Logic conference in Nice. Our congratulations!
Let’s briefly check what else has been done in these 3 months. We have signed new contracts and extended several existing ones. Our famous Vladislav Zavialov came back to hacking on GHC: we’ll show you his developments in the next report.
There is a lot of work ahead, and we will keep you informed about our achievements.
Our blog evolves with us. You might have noticed a small “thumb up” button in the top right corner. Feel free to upvote and show us which articles you like more.
Stay tuned!
]]>In modern blockchains, if some node wants to verify a block, it either has to be a full node storing the whole network state, or it has to continuously ask some remote storage for various parts of it. Each of these solutions possesses either inconveniences (storing 100+ GB of data) or risks (the storage forging the data it sends you).
I present a proper solution for a light client which can validate blocks  an AVL+ tree. Instead of storing N
pieces of data, it requires the client to store 1 hash (which uniquely identifies state) and receive a log(N)
sized proof along with each transaction or block. The client can verify that the proof was generated from the same state the client root hash refers to. This enables the implementation of a verifying light client on smartphones and other devices that are incapable of storing the full state.
AVL+ tree is a map between keys and values. It enables proving of operations that are performed on it. The proof itself is the only thing anyone needs to replay the operation and end with the same result as you, essentially verifying that you did not cheat.
Each AVL+ tree has a unique root hash. If the hash is cryptographic enough, you can view it as a “name” for that particular state of a tree. That means, you can send a provable operation as follows: (operation, proof, endHash)
, where endHash
is the root hash you ended with.
“AVL” is an abbreviation of the names of its inventors: Georgy AdelsonVelsky and Evgenii Landis.
The tree from implementation is kept in memory only partially; you can force it to “melt” into the underlying storage at will. It will materialize gradually, only the nodes that are actually needed for the operation.
It can be used in blockchains to reduce the amount of data (stored on the client) you need to verify a transaction to a single hash.
However, it increases the weight of the transaction, as each object to be proven carries O(log(N))
sized data, where N
is the count of keys in the map.
If you get bored at any time, go to the next section. It contains most of the technical details, compressed.
At first, I was given the task to construct an AVL+ tree. It essentially is an AVL tree, which is, in turn, a balanced binary tree which stores its keys and values in leaf nodes and only technical data in branches. The “+” means that each node has its hash computed and is stored inside it, and the hash of the node depends on the hashes of both child nodes. By induction, this means that any change of any node in the tree will change root hash, thus  and by properties of hashes  proving that root hash identifies the state.
For those interested  the hash itself, which is stored inside the node, cannot be used inside its own computation. The user of the tree selects the hashing algorithm, and there is a protection layer which prevents the situation entirely.
With the help of our theory specialist, we found out that we cannot simply store the height of each node in each subtree as that would require us to access both child nodes when checking the balance, inducing inefficiency. Instead, we decided to store the height difference  or how I called it  tilt
. The tilt
of any node from a balanced tree can only be from the set [1, 0, 1].
I was also told that the tree should generate proof on each operation (insert
, delete
, or even lookup
). We needed to somehow include the preceding and succeeding key into the proof, so that the receiver, for the deletion of an absent key, can check if the key was really absent by looking at the surrounding keys.
Proofs (in all the sources I read) were the “hash chains” + the modified leaf node. So, if you change some node, the proof would look like this:
In this image, the only path to leaf we have changed was drawn, so we leave details of subtrees sprout13
. The only things we need from them are their hashes.
The proof from the picture above is represented as
( [ (sprout1, L, info1)
, (sprout2, R, info1)
, (sprout3, L, info1)
]
, leafBefore
, newHash
)
where info13
are rest of the technical pieces of data from the node.
First, you need to prove it refers to the same root hash you have. For that, you have to do the following:
oldHash’ = foldr recomputeHash (hash leafBefore)
[ (sprout1Hash, L, info1)
, (sprout2Hash, R, info2)
, (sprout3Hash, L, info3)
]
where
recomputeHash (sideHash, _, info) rightHash = hash(sideHash + hash info + rightHash)
Then you have to check if the oldHash’
you get from that computation is the same hash you are holding. This action will prove that this proof is referring to the same tree as your root hash. Then you perform an operation on the leaf on that proof and recompute the hash of it, which should be equal newHash
. If it is, the operation is considered proven.
The first thing that struck me was that the proof looks a lot like the tree itself. Should I write all the operations twice  for the tree and the proof  or should I write them once and reuse? At that moment, the first difference was born between my implementation and what is typically described in articles  my proof is a tree with uninteresting elements pruned. I added a tree state to my ADT:  Pruned hash
.
There was another problem. At the time, I stored the surrounding keys inside each leaf node because some implementations did that (they claimed that this raises the verifiability level). This implied that after each mutation I needed to go to both of the neighboring keys to fix their next and previous key. The recursive insert
didn’t look like it would pull that off and not become a complete mess.
So, I decided to use a zipper to navigate the tree freely.
Zipper is a “functional iterator” which can go up and down into subtrees inside a recursive treelike structure and batch local modifications. When you exit the zipper, all changes are applied.
Zipper gave me descentLeft/Right
(to go to child nodes), up
(to go to a parent node, applying all local changes and maybe rehash) and change
(perform a local modification, schedule a node to be rehashed) operations.
This was a huge change. The zipper actually let me call rebalance
only in the up
operation, which isolated the rebalance and made it possible for me to forget about the issue in all other cases. Rebalance was only called in an up
operation if the source node (or any of its transitive children) was change
d.
Since my proofs were the same trees (but cut), I had to collect them somehow. I thought about collecting the cut tree while traversing full one, but that looked hard. So I split the task in two.
First, I collected hashes of nodes I’ve touched during an operation. Even if I just read the node  the read operation has to be proven, too. After I ended the zipper traversal, I fed the node hashes collected and an old tree into a pruner, which cut away any subtrees I didn’t touch, producing a proof.
So, now you don’t have to run different insert
on the proof  you just get your tree from it and run the same insert
you do on a full tree.
This also allows proving any batch of consecutive operations with a single proof, by computing union of their sets of node hashes and feeding the result to pruner.
Pruning is just a recursive descent into the tree that for each node checks if that is in the “interesting” set. If it is, it’s kept and its children are traversed; if it’s not, it’s replaced with Pruned itsHash
and the algorithm doesn’t descend further. The result of pruning is called a Proof
.
So, in the end, I was able to write insert
, delete
, and lookup
as simple operations without any care for rebalance and (almost) no care for proofs.
I was discussing it with our tech lead, and he got an idea to partially store the tree inside a kvdatabase, not materialize the full tree. He also proposed the usage of Free
monad, instead of the Fix
plumbing that I had at the time. That went well, and I ended with a simple interface for the underlying monad of store
and retrieve
actions  representing some plain KVdatabase holding the (hash, treeNode)
pairs. The Pruned
constructor was replaced with the Pure
one from Free
monad. I wrote a loader in the Internal
layer, which only materialized nodes you need. I also made a save
operation, which synchronized the tree with the DB returning its dematerialized version. This enabled the “melting” and gradual “solidification” of the tree into/from the database.
During the time we plugged in rocksdb as storage underneath the AVL+, a feature was added: storing in and restoring current root from the storage, as well as two variants of tree save: append
and overwrite
.
The append
worked by writing the tree along with any other tree contained in the database and then changing root pointer to the new one. This made possible of loading up any previous state of the tree from the same storage. The overwrite
operation attempted to remove the unshared part of the previous tree (pointed on by the root ptr) before storing diverged nodes of the current one.
In the next incursion, I removed the “next” and “previous” keys from leaves. The reason was  we can just navigate to them deterministically while doing lookup
/insert
/delete
, there’s no need to store their keys and complicate things by updating those keys every time.
One problem I had before was now solved properly  when multiple operations on the same node were done, for each of them the node hash was recalculated. The cryptographic computations are heavy, so there was a need to reduce the load.
The solution was tricky  I made the hashes lazy (we had StrictData
language extension applied). That way, the hash will be calculated on request, and only for the last version of the tree. It does increase the memory consumption to +1 thunk per node, but other changes from that patch (2 keys (next/prev) and 1 Integer per node) are a good compensation for that.
A small improvement in node change tracking was made: instead of comparing a node hash with its previous state  stored in a descent stack  I introduced a boolean isDirty
“variable” (within the zipper state). That caused some spurious test failures, which occurred 10% of the time, to just disappear.
For an extremely large state, implementation supports for the tree to be only partially materialized in memory.
The proof can be generated from and applied to any sequence of changes  to the transaction or the whole block.
To prove the block, a client doesn’t need to communicate with the source of the block or any holder of the full state, all data required for the verification is contained within a proof.
The special storage to plug in while you prove is included in the same package.
The “materialized” and “nonmaterialized” states are indistinguishable from the user and operation standpoint. You just have a Map hash key value
and do things on it. It can be compared for equality with another tree, even if key
and value
types aren’t supporting equality comparison.
For the operations, it supports insert
, delete
, lookup
, lookupMany
, and fold[If]
actions. Each of them returns: a result, a new tree, and a nodeset. (Yes, lookup
returns a new tree as well. The tree, where the visited nodes are materialized.) You then join all the nodesets and prune
them against the initial tree; this gives you a proof which can be used to rerun the same operations on some other client (including a light one) to prove the changes you’ve made before.
The current tree can be stored in the storage via append
or overwrite
and then restored via currentRoot
, which is an O(1)
operation returning the tree in a nonmaterialized way.
The tree, with some operations performed, acts as a cache, synchronizing with the storage on append
and overwrite
 but the usage of overwrite
requires an external lock. You can also just throw the tree out, disposing of any changes you’ve made.
The hidden zipper layer takes care of rebalancing and proof collection. Although you still need to at least visit the nodes you want for them to end in a proof, this is not a problem: visiting is a part of the domain operation.
I have presented a solution to the problem that can be used in blockchains to make light clients that can validate a block without holding the full state or making additional requests to the server. The tree itself can be safely stored elsewhere, due to its storage scheme, thus eliminating the problems of another way of light client implementation. The tree is also capable of working with a state that doesn’t fit into the memory.
]]>Hi, I’m Domagoj Cerjan. I work for Oradian, a SaaS company that enables financial institutions in some of the poorest and remote regions of the world to reap the benefits of moving their operations to the cloud. I’m part of the frontend team and vigilantly bore my colleagues with FP and dabble with all things Typescript, React and Redux. In my free time, I develop 3d rendering engines for fun and since that is the domain I know well, I often try out different languages and approaches to solve the same problem over and over again.
Today, nVidia’s highend GPUs can do realtime raytracing, opensource AMD GPU drivers rock and crossplatform gaming is more of a reality than a dream. A few things remain as they were decades ago  3d graphics is the domain where languages such as C/C++ have been and still are used almost exclusively for writing game engines, GPU interfacing APIs are still plenty  from old and tried varieties of OpenGL and DirectX 9  11, to all modern and extremely lowlevel Vulkan, DirectX 12 and Apple’s Metal.
While C and C++ do provide us with the ability to write really fast programs, they lack in a lot of other areas, from memory safety, debuggability and ease of use to portability and flaky type systems  even though one can do wonders with C++ templates which are in themselves a pure FP language with a weird syntax, they are not languages someone can pick up in a week or two.
Having crossplatform in mind, one could argue that there is but one platform which is available to everyone, from any flavour of Linux, Unix or Windows be it on mobile phones, tablets or personal computers or even washing machines and Roomba’s (because IoT). That platform is of course  the Web. Developing 3d accelerated games for web is nothing new, WebGL has been with us for some time, WebGL 2.0 is already supported in newest Firefox, Chrome and Opera and there exist already well battletested engines such as Three.js, or one could even use emscripten to port any of the C/C++ engines to web more or less.
But what if we want to write an engine using something more modern, specifically targeting the web browser as the environment and trying to make it as robust as possible? Well, I chose Typescript because of its pretty powerful type system, awesome tooling and amazing integration with IDEs (emacs via tide, VSCode by itself, …) and WebGL 2.0 because this engine is my toy and will probably never see the light of day so there are no issues with only supporting a few web browser. I intentionally chose not to use Idris here, even though it compiles down to Javascript, because i wanted to see how far i can push Typescript’s type system.
After the lengthy intro, a little heads up  WebGL 2.0 API is still a lowlevelish API dealing with a bunch of byte buffers, very specific rules what values can go to which arguments of functions and when can they even be invoked and in which order. It’s a mess for anyone who is used to write and/or look at pure functional code. In its essence 3d APIs are as far away as you can possibly get from purely functional. You are dealing with the outside world after all ;)
Speaking of said rules  they often come from tables defined in WebGL specification that define which combinations of arguments are valid for a certain API function. This post will deal with how to make one of the most commonly used functions  a function which uploads an image to the texture  gl.texImage2D
safe from runtime errors caused by not obeying those rules.
First to understand what can go wrong, let’s look at the gl.texImage2D
signature:
void gl.texImage2D(
target: GLenum, // Of interest
level: GLint,
internalformat: GLenum, // Of interest
width: GLsizei,
height: GLsizei,
border: GLint, // **Must** be 0
format: GLenum, // Of interest
type: GLenum, // Of interest
pixels: ArrayBufferView, // Of interest
srcOffset: GLint
) // _May_ throw DOMException
Sources: MDN, Khronos WebGL 2.0 Specification
For simplicity, we will only look at internalformat
and format
since what will be applied to them can easily be applied to target
, type
and pixels
.
Said function arguments format
and type
values are dependant on the value of the internalformat
argument which is in no way visible just from the function prototype. To know which combinations of values are legal, one must take a look at the specification and carefully craft the code so it can never pass invalid values to the gl.texImage2D
by obeying rules defined in tables here.
Now, dependant types come as a natural solution to not write a bunch of runtime code making sure no invalid values get passed down. Thankfully, Typescript actually has a kindof dependant type system since Typescript 2.8.
Lets assume we have a renderer capable of rendering Mesh
, Light
and ParticleEmiter
objects to the screen:
type Renderable = Mesh
 Light
 ParticleEmiter
const renderer = (context: WebGLRenderingContext) =>
(renderables: Renderable[]): void =>
{ ... }
Now, some time passes and we get another entity our renderer can deal with  Gizmo
 but only if it was created from a WebGL 2.0 context. How do we deal with that without having to do nasty runtime checks and throw exceptions?
Welcome the conditional types to the scene:
type WebGL1Renderable = Mesh
 Light
 ParticleEmiter
type WebGL2Renderable = WebGL1Renderable
 Gizmo
// actual conditional type
type Renderable<Context> = Context extends WebGL2RenderingContext ? WebGL2Renderable
: Context extends WebGLRenderingContext ? WebGL1Renderable
: never
And then enforce the context type via
type ValidContext = WebGLRenderingContext
 WebGL2RenderingContext
 Canvas2dRenderingContext
const renderer = <Context extends ValidContext>(context: Context) =>
(renderables: Renderable<Context>[]): void =>
{ ... }
As an added extra, that never
will cause an additional compilation type error if we provide the Canvas2dRenderingContext
as a context to renderer
const webgl1renderer = renderer(canvas.getContext('webgl'))
webgl1renderer([Mesh('a mesh'), Light('a light')])
// no error
webgl1renderer([Mesh('a mesh'), Light('a light')]), Gizmo('a gizmo')])
// error > Gizmo is not found in the type union of WebGL1Renderable
const canvas2drenderer = renderer(canvas.getContext('2d'))
canvas2drenderer([Mesh('a mesh'), Light('a light')])
// error > Mesh and Light do not have anything in common with 'never'
Conditional types are a powerful tool which gives us the freedom to ignore some runtime errors because the compiler will not even allow us to write a codepath which will lead to the said runtime errors in the first place. But we have a problem, we want to typecheck against values and not types for our gl.texImage2D
function.
Well, we need to cheat a bit now. Welcome the typescript enum
to the scene. The only language construct besides literal values that lives both in the type and value realms.
Like many other languages, Typescript supports enums
. A Typescript’s enum
is nothing more but a dictionary of keyvalues where by default, values are numbers starting from 0
unless specified differently or strings. In order to bring GLenum
values, which are nothing more but unsigned integers into the type realm we will create an enum
.
NOTE: This now becomes tedious since it boils down to copying what is defined in the tables into the code via enums:
For the internalformat
argument we use this enum:
enum PixelInternalFormat {
// Sized Internal Formats
// rgba
RGBA4 = 0x8056,
RGBA8 = 0x8058,
RGBA8I = 0x8D8E,
RGBA16I = 0x8D88,
... // and so on and so on
For the format
argument we use this enum:
enum PixelFormat {
LUMINANCE_ALPHA = 0x190A,
LUMINANCE = 0x1909,
ALPHA = 0x1906,
RED = 0x1903,
... // and so on and so on
}
For the type
argument we use this enum:
enum PixelType {
UNSIGNED_SHORT_5_6_5 = 0x8363,
BYTE = 0x1400,
FLOAT = 0x1406,
... // and so on and so on
}
Armed with conditional types and a bunch of enums we now have everything we need in order to implement a function that will cause compiletime type errors when trying to pass in the wrong combination of values to internalformat
, format
and type
arguments.
Implementing the AllowedPixelFormat
dependant type:
type SizedRGBAPixelFormat = PixelInternalFormat.RGBA8
 PixelInternalFormat.RGBA16F
 PixelInternalFormat.RGBA32F
 ... // and a bunch more
type AllowedPixelFormat<P extends PixelInternalFormat> =
P extends SizedRGBAPixelFormat ? PixelFormat.RGBA :
P extends SizedRGBPixelFormat ? PixelFormat.RGB :
P extends SizedRGPixelFormat ? PixelFormat.RG :
P extends SizedRedPixelFormat ? PixelFormat.RED :
... // and a bunch more
never
And the safe function wrapper:
const safeTexImage2D = <
InternalFormat extends PixelInternalFormat,
Format extends AllowedPixelFormat<InternalFormat>,
Type extends AllowedPixelType<InternalFormat>,
Buffer extends AllowedPixelBuffer<Format, Type>,
>(gl: WebGL2RenderingContext) => (
pixelInternalFormat: InternalFormat,
pixelFormat: Format,
pixelType: Type,
width: Int,
height: Int,
pixels: Buffer
): void =>
gl.texImage2D(
gl.TEXTURE_2D, // target, for simplicity just set to gl.TEXTURE_2D
0, // level
pixelInternalFormat, // internalformat
width, // width, shocking
height, // height, also shocking :)
0, // border which must be 0 because specs
pixelFormat, // pixel format dependant on pixelInternalFormat
pixelType, // pixel type dependant on pixelInternalFormat
pixels, // pixels buffer type dependant on both pixelFormat and pixelType
0, // offset
)
And all what is left now is to call this safe wrapper function and witness the awesomeness of dependant types in Typescript:
const texImage2D = safeTexImage2D(canvas.getContext('webgl2'))
// legal
texImage2D(PixelInternalFormat.RGBA, PixelFormat.RGBA, PixelType.UNSIGNED_BYTE, 256, 256, validBuffer)
// ilegal
texImage2D(
PixelInternalFormat.RG16F, // constraint to FLOAT  HALF_FLOAT comes from here
PixelFormat.RG,
PixelType.UNSIGNED_BYTE, /*
^ Argument of type 'PixelType.UNSIGNED_BYTE' is not assignable to
parameter of type 'PixelType.FLOAT  PixelType.HALF_FLOAT' */
256,
256,
validBuffer, // has to be exactly 256 * 256 * 2 * (2  4) bytes big
// 2 channels from PixelFormat.RG ^
// 2 or 4 bytes from half or float ^
)
By applying the same approach it is possible to create safe wrapper functions above unsafe functions coming from not only WebGL but from other commonly used APIs with complex rules not immediately visible from the function signatures. The power to prove that if the code compiles it will not end up in a codepath that might result in weeks worth of headaches to debug is priceless in the world of large codebases and should be appreciated.
The fact that even Typescript which is a typed dialect of Javascript is headed towards dependant types in its type system serves as a strong indicator how dependant types are the future.
And remember, catch errors in the compile time, not runtime :)
]]>This year we launched 9 projects.
Some of them you already know, others we will reveal next year. Our key projects of the year:
We continue to work on Disciplina and on our new project, Ment.
Ment is a human resource management tool that helps with the daily management of employees. Ment simplifies the processes of hiring/firing and provides useful statistics on employee activity.
With the help of Ment, any business can save resources (temporal, financial, human) by combining complex business processes into a unified system. You needed a lot of people to do this before. Now you need only one.
Our team is always on the move. In 2018, we visited offline events in more than 30 cities in more than 14 countries. Now we are experts on both blockchain and jetlags.
We visited the World Economic Forum in Davos, the World Blockchain Forum in Dubai, and the Blockchain Leadership Summit in Basel. We also sponsored the Asia Blockchain & Fintech conference and NixCon (as Gold Sponsors).
This year, we launched a blog. Our blog contains articles about technologies that we use for developing our software. You can find tips and case studies, as well as nontechnical articles on software outsourcing.
We have collected a lot of material that we want to share with you in 2019. We are still at the very beginning of the way and want to thank you for the 40,000 views in the last few months!
The blog is not our only tool of communication – you can interact with us on Twitter, Medium, Reddit, and Facebook. Also, we have an Instagram account – follow us to see all the myriad ways a developer can take a photo with a baguette.
We are growing, and now our staff consists of 50+ people across the globe. All together, we have made a big step forward this year. And not only in social media activities, blog articles, and business trips.
Our competencies, professionalism, and ability to solve difficult tasks grow from quarter to quarter. Only in this way we can be sure to meet the rapidly rising standards for international R&D companies.
We have come a long way this year for one purpose only – to make the world a little bit better by providing brave products and solutions in the IT sphere.
On behalf of all the employees of the company, we wish you prosperity and great success in work. Happy holidays, and see you in the next year!
]]>Dependent types are a feature I’d love to see in Haskell the most. Let’s talk why. There’s a tension between writing code that is performant, code that is maintainable and easy to understand, and code that is correct by construction. With available technology, we are in a “pick two” situation, but with direct support for dependent types in Haskell we might have our cake and eat it too.
Haskell, at its core, is simple: it is just a polymorphic lambda calculus with lazy evaluation plus algebraic data types and type classes. This happens to be just the right combination of features to allow us to write clean, maintainable code that also runs fast. I will briefly compare it to more mainstream languages to substantiate this claim.
Memory unsafe languages, such as C, lead to the worst sort of bugs and security vulnerabilities (buffer overflows and memory leaks). There are specific circumstances in which one would want to use such a language, but most of the times it’s a horrible idea.
Memory safe languages form two groups: the ones that rely on a garbage collector, and Rust. Rust seems to be unique in that it offers memory safety without a GC (there’s also the now unmaintained Cyclone and other research languages in this group, but unlike them, Rust is paving its way towards mainstream use). The downside is that, while safe, memory management in Rust is still manual and nontrivial, and in applications that can afford to use a garbage collector, developer time is better spent on other issues.
This leaves us with garbage collected languages, which we’ll further break into two categories based on their type systems.
Dynamically typed (or, rather, unityped) languages, such as JavaScript or Clojure, do not offer static analysis by design, therefore cannot offer the same level of confidence in code correctness (and no, tests cannot replace types – we need both!).
Statically typed languages, such as Java or Go, often come with severely limited type systems that force programmers to write boilerplate or fallback to unsafe programming techniques. A notable example is how the lack of generics in Go necessitates the use of interface{}
and runtime casts. There’s also no separation between effectful (IO) and pure computations.
Finally, among memorysafe garbagecollected languages with powerful type systems, Haskell stands out by being lazy. Laziness is an important asset for writing composable, modular code, making it possible to factor out any parts of expressions, including control structures.
It almost seems to be the perfect language until you realize how far it is from reaching its full potential in terms of static verification when compared to proof assistants such as Agda.
As a simple example of where the Haskell type system falls short, consider the list indexing operator from Prelude
(or array indexing from the primitive
package):
(!!) :: [a] > Int > a
indexArray :: Array a > Int > a
Nothing in these type signatures asserts that the index is nonnegative but smaller than the length of the collection. For high assurance software, this is immediately a nogo.
Proof assistants (for example, Coq) are software tools that enable computerassisted development of formal proofs of mathematical theorems. To a mathematician, using a proof assistant is a lot like writing a penandpaper proof, yet with unprecedented rigour required by the computer to assert the correctness of such proof.
To a programmer, however, a proof assistant isn’t that different from a compiler for an esoteric programming language that has an incredible type system (and perhaps an IDE, then it’s called an interactive theorem prover), and mediocre (or even lacking) everything else. A proof assistant is what happens if you spend all your time developing a type checker for your language and forget that programs also need to be run.
The holy grail of verified software development is a proof assistant that is also a good programming language with industrialstrength code generator and runtime system. There are experiments in this direction, for instance Idris, but Idris is a language with eager (strict) evaluation and its implementation isn’t particularly stable at the moment.
The proof assistant closest to a Haskeller’s heart is Agda, as in many ways it feels a lot like Haskell with a more powerful type system. We use it to prove various properties of our software, and my colleague Danya Rogozin wrote a post series about it.
Here’s the type of the lookup
function analogous to Haskell’s (!!)
:
lookup : ∀ (xs : List A) → Fin (length xs) → A
The first parameter here is of type List A
which corresponds to [a]
in Haskell. However, note that we also give it a name, xs
, to refer to it later in the type signature. In Haskell, we can refer to function arguments only in the function body at the term level:
(!!) :: [a] > Int > a  can't refer to xs here
(!!) = \xs i > ...  can refer to xs here
In Agda, however, we can refer to this xs
value at the type level as well, as we do in the second parameter of lookup
, Fin (length xs)
. A function that refers to its parameter on the type level is called a dependent function, which is an example of dependent types.
The second parameter of lookup
is of type Fin n
for n ~ length xs
. A value of type Fin n
corresponds to a number in the [0, n)
range, so Fin (length xs)
is a nonnegative number smaller than the length of the input list, which is precisely what we need to represent a valid index into the list. Roughly speaking, this means that lookup ["x","y","z"] 2
is welltyped, but lookup ["x","y","z"] 42
is rejected by the type checker.
When it comes to running Agda, we can compile it to Haskell using the MAlonzo backend, and yet the produced code cannot offer competitive performance. This is no fault of MAlonzo, however – it has no choice but to insert numerous unsafeCoerce
so that GHC would accept code that has been previously checked by the Agda type checker, but the very same unsafeCoerce
is what destroys the performance.
This puts us in a difficult situation where we have to use Agda for modelling and formal verification, and then reimplement the same functionality in Haskell. With this setup, our Agda code serves as a machinechecked specification, which is, while an improvement over a natural language specification, not even close to the endgoal of “if it compiles, it works as specified”.
Striving for the static guarantees of dependently typed languages, GHC has a long history of adding extensions that increase the expressive power of the type system. I started using Haskell when GHC 7.4 was the newest and shiniest Haskell compiler, and by then it already had the main extensions for advanced typelevel programming in place: RankNTypes
, GADTs
, TypeFamilies
, DataKinds
, and PolyKinds
.
And yet, we still don’t have proper dependent types in Haskell: no dependent functions (Πtypes) or dependent pairs (Σtypes). We do have an encoding for them, though!
The state of the art is to encode functions on the type level as closed type families, use defunctionalization to allow unsaturated function application, and bridge the gap between terms and types using singleton types. This leads to a sizeable amount of boilerplate, but there’s the singletons
library to generate it with Template Haskell.
This means that for the very brave and determined, there is an encoding of dependent types in today’s Haskell. As a proof of concept, here’s an implementation of the lookup
function analogous to the Agda version:
{# OPTIONS Wall Wnountickedpromotedconstructors Wnomissingsignatures #}
{# LANGUAGE LambdaCase, DataKinds, PolyKinds, TypeFamilies, GADTs,
ScopedTypeVariables, EmptyCase, UndecidableInstances,
TypeSynonymInstances, FlexibleInstances, TypeApplications,
TemplateHaskell #}
module ListLookup where
import Data.Singletons.TH
import Data.Singletons.Prelude
singletons
[d
data N = Z  S N
len :: [a] > N
len [] = Z
len (_:xs) = S (len xs)
]
data Fin n where
FZ :: Fin (S n)
FS :: Fin n > Fin (S n)
lookupS :: SingKind a => SList (xs :: [a]) > Fin (Len xs) > Demote a
lookupS SNil = \case{}
lookupS (SCons x xs) =
\case
FZ > fromSing x
FS i' > lookupS xs i'
Here’s a GHCi session to demonstrate that lookupS
indeed rejects indices that are too large:
GHCi, version 8.6.2: http://www.haskell.org/ghc/ :? for help
[1 of 1] Compiling ListLookup ( ListLookup.hs, interpreted )
Ok, one module loaded.
*ListLookup> :set XTypeApplications XDataKinds
*ListLookup> lookupS (sing @["x", "y", "z"]) FZ
"x"
*ListLookup> lookupS (sing @["x", "y", "z"]) (FS FZ)
"y"
*ListLookup> lookupS (sing @["x", "y", "z"]) (FS (FS FZ))
"z"
*ListLookup> lookupS (sing @["x", "y", "z"]) (FS (FS (FS FZ)))
<interactive>:5:34: error:
• Couldn't match type ‘'S n0’ with ‘'Z’
Expected type: Fin (Len '["x", "y", "z"])
Actual type: Fin ('S ('S ('S ('S n0))))
• In the second argument of ‘lookupS’, namely ‘(FS (FS (FS FZ)))’
In the expression:
lookupS (sing @["x", "y", "z"]) (FS (FS (FS FZ)))
In an equation for ‘it’:
it = lookupS (sing @["x", "y", "z"]) (FS (FS (FS FZ)))
This example demonstrates that “possible” does not mean “feasible”. I am joyful that Haskell has the features needed to implement lookupS
, but I’m also in distress because of the accidental complexity that it brings. I would never advise to use this style of code in a nonresearch project.
In this particular example, we could’ve used lengthindexed vectors to achieve a similar result at lesser complexity. However, the direct translation of the Agda code serves better to reveal the issues that we have to deal with in other circumstances. Here are some of them:
The typing relation a :: t
and the kinding relation t :: k
are different. 5 :: Integer
holds in terms, but not in types. "hi" :: Symbol
holds in types, but not in terms. This leads to the need for the Demote
type family to map typelevel encodings to their termlevel counterparts.
The standard library uses Int
to represent list indices (and singletons
uses Nat
in the promoted definitions). Int
and Nat
are noninductive types, and while more efficient than the unary encoding of naturals, they don’t play well with inductive definitions such as Fin
or lookupS
. Thus we had to redefine length
as len
.
Functions are not promoted out of the box. singletons
encodes them as closed type families, using defunctionalization to work around the limitation that type families may not be partially applied. This is a complex encoding, and we have to put our definition of len
into a Template Haskell quote so that singletons
will generate its typelevel counterpart, Len
.
Due to lack of builtin dependent functions, we have to use singleton types to bridge the gap between terms and types. This means we are using SList
as input to lookupS
instead of a regular list. This results not only in mental overhead of having multiple definitions for lists, but also in runtime overhead of doing the conversions back and forth (toSing
, fromSing
) and carrying evidence for the conversion procedure (the SingKind
constraint).
Bad ergonomics are only a part of the problem. The other part is that none of this works reliably. For example, I reported Trac #12564 back in 2016, and there’s Trac #12088 from around the same time – both are posing significant trouble when it comes to things more advanced than textbook examples such as list lookup. These GHC bugs are still not fixed, and the reason for this, I believe, is that GHC developers simply don’t have enough time, being busy with other issues. The amount of people actively working on GHC is surprisingly small, and some areas are destined to be left unattended.
I said earlier that we’re in a “pick two” situation, so here’s a table to show what I mean by that:
Standard Haskell 
Agda  Haskell Extended 


Runtime performance 
✔️  ❌  ✔️ 
Correctness by construction 
❌  ✔️  ✔️ 
Ergonomics and maintainability 
✔️  ✔️  ❌ 
Out of the three options we have, each has a flaw. However, we can fix them:
singletons
. (Easier said than done).The good news is that all three options seem to converge at a single point (sort of). Imagine the tidiest possible extension to Standard Haskell that adds dependent types and, consequently, the ability to write code correct by construction. Agda could be compiled (transpiled) to this language without unsafeCoerce
. And Haskell Extended is, in a sense, an unfinished prototype of that language – we need to enhance some things and deprecate others, but eventually we’ll arrive at the desired destination.
singletons
A good indication of progress is the removal of special cases and workarounds from singletons
. Bit by bit, we will make this package redundant. For instance, in 2016 I made use of the XTypeInType
extension to remove KProxy
from SingKind
and SomeSing
. This change was possible due to an important milestone – unification of types and kinds. Compare the old and the new definitions:
class (kparam ~ 'KProxy) => SingKind (kparam :: KProxy k) where
type DemoteRep kparam :: *
fromSing :: SingKind (a :: k) > DemoteRep kparam
toSing :: DemoteRep kparam > SomeSing kparam
type Demote (a :: k) = DemoteRep ('KProxy :: KProxy k)
data SomeSing (kproxy :: KProxy k) where
SomeSing :: Sing (a :: k) > SomeSing ('KProxy :: KProxy k)
In the old definitions, k
occurs in kind positions exclusively, on the right hand side of kind annotations t :: k
. We use kparam :: KProxy k
to carry k
in types.
class SingKind k where
type DemoteRep k :: *
fromSing :: SingKind (a :: k) > DemoteRep k
toSing :: DemoteRep k > SomeSing k
type Demote (a :: k) = DemoteRep k
data SomeSing k where
SomeSing :: Sing (a :: k) > SomeSing k
In the new definitions, k
travels freely between kind and type positions, so we don’t need the kind proxy anymore. The reason is that since GHC 8.0, types and kinds are the same syntactic category.
In Standard Haskell, we have three completely separate worlds: terms, types, and kinds. If we look at the GHC 7.10 sources, we can see a dedicated parser for kinds, and a dedicated checker. In GHC 8.0, these are gone: types and kinds share the same parser and checker.
In Haskell Extended, being a kind is just a role that a type fulfils:
f :: T z > ...  'z' is a type
g :: T (a :: z) > ...  'z' is a kind
h :: T z > T (a :: z) > ...  'z' is both
Unfortunately, that’s only 97% true. In GHC 8.0–8.4 there are still differences in renaming and scoping rules for types in type and kind positions. These remnants of the past are being removed, though. In GHC 8.6, I unified the renamer for types and kinds by adding the StarIsType
extension and merging the functionality of TypeInType
into PolyKinds
. There’s an accepted proposal to unify the scoping rules, but its implementation is scheduled for GHC 8.10 to comply with the 3 Release Policy. I’m looking forward to this change and I added a warning to GHC 8.6, Wimplicitkindvars
, that you can use to write forwardcompatible code today.
Fastforward a couple of GHC releases, when it will be true that kinds are types, no strings attached. What’s the next step? Let’s take a brief look at SingKind
in the latest version of singletons
:
class SingKind k where
type Demote k = (r :: Type)  r > k
fromSing :: Sing (a :: k) > Demote k
toSing :: Demote k > SomeSing k
The Demote
type family is needed to account for the discrepancy between the typing relation a :: t
and the kinding relation t :: k
. Most of the times (for ADTs), Demote
is the identity function:
type Demote Bool = Bool
type Demote [a] = [Demote a]
type Demote (Either a b) = Either (Demote a) (Demote b)
Therefore, Demote (Either [Bool] Bool) = Either [Bool] Bool
. This observation prompts us to make the following simplification:
class SingKind k where
fromSing :: Sing (a :: k) > k
toSing :: k > SomeSing k
Look, ma, no Demote
! And, in fact, that’d work for the Either [Bool] Bool
case, and for other ADTs. In practice, though, there are other types besides ADTs: Integer
, Natural
, Char
, Text
, and so on. They are not inhabited when used as kinds: 1 :: Natural
is true in terms, but not in types. That’s why we have these unfortunate lines:
type Demote Nat = Natural
type Demote Symbol = Text
The solution to this issue is to promote all primitive types. For example, Text
is defined as:
  A space efficient, packed, unboxed Unicode text type.
data Text = Text
{# UNPACK #} !Array  payload (Word16 elements)
{# UNPACK #} !Int  offset (units of Word16, not Char)
{# UNPACK #} !Int  length (units of Word16, not Char)
data Array = Array ByteArray#
data Int = I# Int#
If we properly promote ByteArray#
and Int#
, then we can use Text
instead of Symbol
. Do the same thing with Natural
and a perhaps a couple of other types, and we can get rid of Demote
, – right?
Not really. I’ve been ignoring the elephant in the room: functions. They, too, have a special Demote
instance:
type Demote (k1 ~> k2) = Demote k1 > Demote k2
type a ~> b = TyFun a b > Type
data TyFun :: Type > Type > Type
The ~>
type is the representation that singletons
uses for typelevel functions, based on closed type families and defunctionalization.
The initial instinct could be to unify ~>
with >
, as both stand for the type (kind) of functions. The issue here is that (>)
in a type position and (>)
in a kind position mean different things. In terms, all functions from a
to b
have type a > b
. In types, on the other hand, only constructors from a
to b
have kind a > b
, but not type synonyms or type families. For reasons of type inference, GHC assumes that f a ~ g b
implies f ~ g
and a ~ b
, and this holds for constructors, but not functions, hence the restriction.
Therefore, in order to promote functions but keep type inference, we will have to move constructors into their own type, let’s call it a :> b
, which does indeed have the property that f a ~ g b
implies f ~ g
and a ~ b
, and all other functions will continue to be of type a > b
. For example, Just :: a :> Maybe a
, but isJust :: Maybe a > Bool
.
Once Demote
is dealt with, the last mile is to remove the need for Sing
itself. For this, we will need a new quantifier, a hybrid between forall
and >
. Let’s take a close look at the isJust
function:
isJust :: forall a. Maybe a > Bool
isJust =
\x >
case x of
Nothing > False
Just _ > True
The isJust
function is parametrized by a type a
, and then a value x :: Maybe a
. These two parameters exhibit different properties:
Visibility. When we call isJust (Just "hello")
, we pass x = Just "hello"
visibly, but a = String
is inferred by the compiler. In modern Haskell, we can also use a visibility override and pass both parameters visibly: isJust @String (Just "hello")
.
Relevance. The value that we pass as input to isJust
will be indeed passed at runtime, as we need to match on it using case
to see if it’s Nothing
or Just
. It is thus considered relevant. On the other hand, the type itself is erased and cannot be matched on: the function will treat Maybe Int
, Maybe String
, Maybe Bool
, etc, uniformly – therefore, it is considered irrelevant. This property is also called parametricity.
Dependency. In forall a. t
, the type t
may mention a
and, therefore, depend on the concrete a
that is passed. For example, isJust @String
has type Maybe String > Bool
, but isJust @Int
has type Maybe Int > Bool
. This means that forall
is a dependent quantifier. Contrast that with the value parameter: it doesn’t matter if we call isJust Nothing
or isJust (Just ...)
, the result is always Bool
. Therefore, >
is not a dependent quantifier.
To subsume Sing
, we will need a quantifier that is visible and relevant, just like a > b
, but dependent, like forall (a :: k). t
. We will write it as foreach (a :: k) > t
. To subsume SingI
, we shall also introduce an invisible relevant dependent quantifier, foreach (a :: k). t
. At this point, we no longer need singletons
, as we have just introduced dependent functions to the language.
With promotion of functions and the foreach
quantifier, we will be able to rewrite lookupS
as follows:
data N = Z  S N
len :: [a] > N
len [] = Z
len (_:xs) = S (len xs)
data Fin n where
FZ :: Fin (S n)
FS :: Fin n > Fin (S n)
lookupS :: foreach (xs :: [a]) > Fin (len xs) > a
lookupS [] = \case{}
lookupS (x:xs) =
\case
FZ > x
FS i' > lookupS xs i'
This code isn’t any shorter – after all, singletons
does a good job of hiding the boilerplate. However, it is much cleaner: there are no Demote
, SingKind
, SList
, SNil
, SCons
, fromSing
. No use of TemplateHaskell
, as we now can use the len
function directly instead of generating type family Len
. The runtime performance will be better, too, as we no longer need the fromSing
conversion.
We still had to redefine length
as len
to return inductively defined N
instead of Int
. It is probably best to declare this issue out of scope of Dependent Haskell, as Agda uses inductively defined N
for its lookup
function too.
There are ways in which Dependent Haskell is simpler than Standard Haskell. After all, it merges the worlds of terms, types, and kinds, into a single, uniform language. I can easily imagine using this style of code in a production codebase to certify crucial components of the application. Many libraries will be able to offer safer APIs without the complexity cost of singletons
.
Getting there will not be easy. There are many engineering challenges ahead of us in all of GHC components: the parser, the renamer, the typechecker, and even the Core language, all require nontrivial modification or even a redesign. Lately, my focus has been on parsing, and I’m going to publish a fairly large diff in the coming weeks.
Stay tuned for future posts where I will dive into technical details of the problems that I’m solving as part of my work on GHC at Serokell to make dependently typed programming practical!
Comments, discussions, and more on Reddit and Hacker News.
]]>Recently, my team decided to implement an eDSL using the tagless final style for one of the new projects. Although it’s a fairly known technique, I had zero experience with tagless final and there were some difficulties associated with terms tagless, final, and eDSL.
In preparation for the task, I’ve done some research and organized the learned material into a small HowTo. Now I want to share it with others.
I assume that the reader is fairly comfortable with MTL because I will use a lot of analogies with MTL.
Recall your everyday MTLstyle programming. Forget about concrete monad transformers and concentrate on type classes. Without transformers, there are only two things that are left:
Functions are declared using type constraints instead of concrete types:
getUser :: (MonadReader r m, Has DatabaseConfig r, MonadIO m) => Name > m User
Instantiation of a polymorphic function to a concrete type (aka interpreter) happens somewhere “in the end”:
liftIO $ runReader (getUser (Name "Pedro")) env
That’s all. We’ve just covered the tagless final style:
All good (and bad) in tagless final comes from adhoc polymorphism and type classes (i.e. overloading). Your output depends directly on your commitment to overload things.
A distinct feature of tagless final is extensibility in two dimensions, which is, in fact, a significant achievement (see The Expression Problem Revisited for an explanation for why it’s hard to achieve this property).
Let’s discuss extensibility while keeping this function signature in mind:
wimble :: (MonadReader Env m, MonadState State m) => m ()
We can run wimble
using a custom new implementation of MonadReader
and MonadState
(by defining a new data type and defining instances for it). This is an extension in the first dimension: a new interpreter. Furthermore, we can use a new set of operations, say MonadWriter
, and use wimble
in a new function which uses all 3 classes: MonadReader
, MonadState
and MonadWriter
(i.e. old and new operations). This is an extension in the second dimension: a new set of operations.
From my point of view, available learning resources show two different approaches to using tagless final:
Define operations abstracted over a monad
In that case, we can use do
notation.
Define an Abstract Syntax Tree using overloaded functions
In that case, potentially, we can pretty print, inspect and optimize AST.
People who have experience with the technique might say that the two approaches are exactly the same. After learning about tagless final, this opinion makes sense for me. But earlier, when I had just started searching through available learning resources, I was confused by the difference in the look and feel of the resulting code. Also, some people say that having do
notation is enough to call something an eDSL, others say that eDSL should define an AST. So, by saying tagless final, different people might assume slightly different approaches which might look as completely different techniques to a novice Haskell programmer. We will briefly explore programming with Monads and defining ASTs using tagless final, and also will touch a few other relevant topics.
It’s common among Haskell programmers to organize effectful application code using monads. Details vary between implementations but the basic idea is to define a monad together with a set of operations available in this monad. Similarly to this blog post, I’ll call a monad for organizing effectful application code an application monad.
Tagless final is a suitable technique for defining application monads. In facts, thanks to MTL, it is one of the most widely used tools for that task.
Let’s take a simplified problem of fetching/deleting a user from a database as an example to demonstrate how tagless final can be used to define operations available in do notation. Our application monad will provide two operations: getUser
and deleteUser
. By applying tagless final approach, we will define a set of overloaded functions and later will provide their implementation. Right from the start there is a design decision to make: which operations to overload. We can define a new typeclass with getUser
/deleteUser
operations, or we can use more generic functions from MTL and build on top of them. Although in practice I often will choose the 2nd option, here I’ll show the 1st one because in our particular case it leads to shorter code:
data Name = Name String
data User = User { name :: Name, age :: Int }
class Monad m => MonadDatabase m where
getUser :: Name > m User
deleteUser :: User > m ()
Using operations given above we can define some logic like this:
test :: MonadDatabase m => m ()
test = do user < getUser (Name "Pedro")
when (age user < 18) (deleteUser user)
Note that the test
function is abstract: it can be executed using a different implementation of MonadDatabase
. Now, let’s define a MonadDatabase
instance suitable to run that test
function. One way to do it is to build on top of MTL transformers. I’ve assumed that getUser
/deleteUser
functions can be implemented on top of Reader
and IO
monads and I’ve also omitted some implementation details (marked by ...
):
data DatabaseConfig = DatabaseConfig { ... }
newtype AppM a =
AppM { unAppM :: ReaderT DatabaseConfig IO a }
deriving (Functor, Applicative, Monad, MonadIO, MonadReader DatabaseConfig)
instance MonadDatabase AppM where
getUser name = do cfg < ask
...
deleteUser user = do cfg < ask
...
runAppM :: AppM a > DatabaseConfig > IO a
runAppM app config = runReaderT (unAppM app) config
Now, we can execute the abstract test
function using a particular AppM
implementation:
main = do cfg < ...
runAppM test cfg
By using tagless final style, we have separated the definition of abstract operations from their implementation which gives us extensibility. With such an approach it is possible to define a new set of operations and use it together with MonadDatabase
. It is also possible to add a new interpretation (for example, for testing purposes).
Even with such a small example, there were many possibilities to organize code. The first question: how to choose the set of overloaded operations? Is it better to define a new typeclass such as MonadDatabase
with applicationspecific functions, or is it better to stick to MTL
typeclasses and define operations on top of more generic functions? The second question is: how to write the implementation? Are there practical alternatives to MTL transformers? Although it’s very tempting to discuss those and several other questions here, I don’t know all answers and the topic of proper application architecture is too broad. For more indepth resources on application architecture, you can visit other blogs:
(1, 2, 3, 4, 5, 6).
Q. I heard you were describing tagless final using MTL. What about the famous n^2 problem?
A. n^2 problem appears in transformers implementation (because transformers need to propagate methods of their child monads). Transformers have nothing to do with tagless final. We were only talking about type constraints and freedom to switch between implementations.
If you are still wondering about the n^2 problem, here is a small trick to mitigate it (export method implementations as separate functions with a hope that other instances will use your implementation).
If you create many similar implementations, tagless final causes some effort duplication. In that case, you might want to use transformers, which leads to the n^2 problem.
Q. You were talking about an “Application” monad and MTL style. Is it really an eDSL?
A. Even if there is a canonical scientific definition for the term eDSL, people use the term to talk about different things. Opinions range from “eDSL is a completely distinct language with its own semantics” to “eDSL is a library with a nice, consistent interface”. Here are the answers I got in several public Haskellrelated channels to the question “what are good examples of eDSLs implemented in Haskell?”: SBV, diagrams, accelerate, blaze, esqueleto, shake, lens, gloss, streaming libraries (pipes, streamly, etc.), Servant, opaleye, frparduino, HHDL, ivory, pandoc. As you can see, those answers clearly show that the term eDSL is vague. But anyway, tagless final can be used to create both “true” eDSLs and nice library interfaces (probably with monads).
The most complete discussion of the tagless final approach was done by Oleg Kiselyov and his colleagues. He talks mostly about the embedding of different versions of typed lambda calculus using the tagless final encoding. He achieves very motivating results, such as embedding lambda calculus with linear types and transforming ASTs.
Let’s pick a simple language as an example and explore two ways to encode an AST: using Initial and Final encodings. The chosen language has integer constants, an addition operation, and lambda functions. From one hand it’s quite simple to put most of the implementation in this blog post. From the other hand, it’s complicated enough to discuss two versions of Initial encodings and to demonstrate extensibility of tagless final approach.
Initial encoding means that we are representing AST using values of a given algebraic data type. The term “Initial encoding” was inspired by the category theory and it follows from the observation that inductive data type can be viewed as an “initial algebra”. Bartosz Milewski gives a gentle description of what an initial algebra is and why inductive data structure can be viewed as an initial algebra.
Here is one way to represent an Abstract Syntax Tree for our simple language (we are reusing Haskell lambda functions in the definition of Lambda
for simplicity so that we don’t need to implement identifiers assignment/lookup by ourselves, this approach is called higherorder abstract syntax):
data Expr = IntConst Int
 Lambda (Expr > Expr)
 Apply Expr Expr
 Add Expr Expr
This representation allows us to define wellformed eDSL expressions like these:
 10 + 20
t1 = IntConst 10 `Add` IntConst 20
 (\x > 10 + x) 20
t2 = Apply (Lambda $ \x > IntConst 10 `Add` x) (IntConst 20)
Unfortunately, it also allows us to define malformed expressions like these:
 Trying to call integer constant as a function
e1 = Apply (IntConst 10) (IntConst 10)
 Trying to add lambda functions
e2 = Add f f where f = Lambda (\x > x)
Evaluation of Expr
values can produce errors because the representation of our eDSL allows encoding malformed expressions. Consequently, interpreter eval
should check for type errors during its work. To be more precise, it should patternmatch on resulting values to find out which concrete values came out from eval
function in runtime to ensure that the Add
operation was applied to integer constants and the Apply
operation was used with a lambda function. We define the Result
data type to represent possible resulting values and use Maybe Result
to represent possible errors:
data Result = IntResult Int
 LambdaResult (Expr > Expr)
eval :: Expr > Maybe Result
eval e@(IntConst x) = Just (IntResult x)
eval e@(Lambda f) = Just (LambdaResult f)
eval (Apply f0 arg) = do
f1 < eval f0
case f1 of
LambdaResult f > eval (f arg)
_ > Nothing
eval (Add l0 r0) = do
l1 < eval l0
r1 < eval r0
case (l1, r1) of
(IntResult l, IntResult r) > Just $ IntResult (l + r)
_ > Nothing
The technique is called “tagged” because sum types in Haskell are tagged sum types. At runtime such values are represented as a pair (tag, payload)
and tag
is used to perform patternmatches. The eval
function uses pattern matching on IntResult
and LambdaResult
to perform type checking and errors checking, or in other words, it uses tags in runtime. Hence the name.
The idea is that we can use GADT to add information about values into Expr
type and use it to make malformed eDSL expressions unrepresentable. We no longer need a Result
data type and there is no more runtime type checking in eval
function. In the Finally Tagless, Partially Evaluated paper authors refer to their versions of data constructors IntResult
and LambdaResult
as “tags”. And because the GADTsbased approach has no tags, they call it “tagless initial” encoding.
The GADTsbased AST definition and corresponding interpreter eval
are given below. New AST is capable of representing examples t1
, t2
from the previous section while making e1
, e2
unrepresentable. The idea of Expr a
data type is that a
parameter holds a type to which a given expression should evaluate. IntConst
and Lambda
just duplicate its field types in a
parameter because evaluating a value just means unwrapping it. In the case of Add
constructor, a
parameter is equal to Int
which means that Add
evaluates to an integer. Apply
evaluates to a result of a passed lambda function.
data Expr a where
IntConst :: Int > Expr Int
Lambda :: (Expr a > Expr b) > Expr (Expr a > Expr b)
Apply :: Expr (Expr a > Expr b) > Expr a > Expr b
Add :: Expr Int > Expr Int > Expr Int
eval :: Expr a > a
eval (IntConst x) = x
eval (Lambda f) = f
eval (Apply f x) = eval (eval f x)
eval (Add l r) = (eval l) + (eval r)
Although the term “Initial” came from category theory, the term “Final” didn’t. Oleg shows that “the final and initial typed tagless representations are related by bijection” which means that these approaches are equivalent in some sense and both are “Initial” from the category theorist’s point of view. The Finally Tagless paper states “We call this approach final (in contrast to initial) because we represent each object term not by its abstract syntax, but by its denotation in a semantic algebra”. My best guess is that the name “Final” was chosen to differentiate from the term Initial as much as possible.
With tagless final, we build expressions using overloaded functions instead of data constructors. The expression from the previous section will look like this:
test = lambda (\x > add x (intConst 20))
Machinery to make it work consists of two parts.
Combinators definition:
class LambdaSYM repr where
intConst :: Int > repr Int
lambda :: (repr a > repr b) > repr (a > b)
apply :: repr (a > b) > repr a > repr b
Interpreter implementation:
data R a = R { unR :: a }
instance LambdaSYM R where
intConst x = R x
lambda f = R $ \x > unR (f (R x))
apply f a = R $ (unR f) (unR a)
eval :: R a > a
eval x = unR x
Applying interpreter:
testSmall :: LambdaSYM repr => repr Int
testSmall = apply (lambda (\x > x)) (intConst 10)
main = print (eval testSmall)  10
Interesting points:
eval
function instantiates testSmall
expression to a concrete type R Int
(aka interpreter).
It’s easy to define other interpreters. For example, a pretty printer. There is a little twist, though: a pretty printer needs to allocate names for free variables and keep track of allocated names, so the printing interpreter will pass an environment and it will look very similar to a Reader monad.
It’s extremely easy to extend the language with new operations.
Adding a new add
operation to our previous example requires only defining a new type class and implementing a new instance for each interpreter. Functions which use new add
operations should add additional AddSYM repr
constrain to its type.
class AddSYM repr where
add :: repr Int > repr Int > repr Int
instance AddSYM R where
add a b = R $ (unR a) + (unR b)
test :: (LambdaSYM repr, AddSYM repr) => repr Int
test = apply (apply (lambda (\y > lambda (\x > x `add` y))) (intConst 10)) (intConst 20)
Please note that in this particular case we are lucky because it’s possible to write instance AddSYM R
. Or, in other words, it’s possible to implement the new operation on top of an existing interpreter. Sometimes we will need to extend existing interpreters or write new ones.
In Oleg’s papers, he pretty prints and transforms tagless final AST. It’s very counterintuitive to expect the existence of such utilities because combinators are functions, and we are used to manipulating values of Algebraic Data Types. Yet, it is possible to extract facts about the structure of Final Tagless ASTs (i.e. introspection is possible) and to transform them. For the proof of that claim, check section 3.4 (page 28) of Oleg’s course where he presents prettyprinter and transformer.
The prettyprinter and the transformer of ASTs are just tagless final interpreters which keep track of some additional information and propagate it during interpreting from parents to children. Both are extensible in the same ways as other tagless final interpreters.
However, if we return to our first section and think about applying a tagless final approach to a simple case of defining Application monad, then we will quickly find out that we can’t inspect and transform resulting monads. Consider our simple example:
class Monad m => HasDatabaseConfig m where
getDatabaseConfig :: m DatabaseConfig
getUser :: (HasDatabaseConfig m, MonadIO m) => Name > m User
getUser = ...
test :: (HasDatabaseConfig m, MonadIO m) => m String
test = do user < getUser (Name "Pedro")
if age user > 3 then pure "Fuzz"
else pure "Buzz"
Although the getDatabaseConfig
function is overloaded, a lot of logic is expressed using functions and other constructions which are not overloaded. Therefore, there is no way to statically inspect the resulting monadic value. This is an important point: if you want introspection and transformation of ASTs, then you need to keep track of what’s overloaded and what’s not. Oleg obtained his great results because he overloaded everything and expressed embedded lambda calculus by using only overloaded functions. In other words, the power of tagless final depends on how far you want to go with overloading.
People often compare tagless final and free monads. Both approaches give you machinery to define overloaded operations inside a monadic context. I am not an expert in free monads, but tagless final:
One argument for free monads is that it’s possible to statically introspect free monads. That is not completely true. Yes, you can easily execute actions one by one, and it helps to combine monadic values by interleaving actions (we can achieve a similar thing by interpreting into continuation with tagless final). But here is a blog post which describes the difficulties of free monad introspection (we’ve already covered the gist of the problem in the previous section). Also, see this blog post where the author describes difficulties associated with free monads and suggests using tagless final instead.
Here is a very good overview of free monad performance challenges. Here Edward Kmett gives his perspective on the same problem.
In few words:
[1, 2, 3] ++ [4]
.DList
package) gives O(n) binds, but makes some operations slow (for example combining two sequences of commands by interleaving them).Seq
data structure leads to a good asymptotic behaviour for all operations, but also gives significant constant overhead.General techniques regarding optimizing Haskell code with polymorphic functions apply here. In few words, sometimes using overloaded functions cause compiler to generate code which uses methods dictionaries to dispatch calls. The compiler often knows how to specialize functions and get rid of dictionaries, but module boundaries prevent that from happening. To help the compiler, we need to read the “Specializing” section of this document and then use an INLINEABLE
pragma like this
getUser :: (MonadReader r m, Has DatabaseConfig r, MonadIO m) => Name > m User
...
{# INLINEABLE getUser #}
Haskell lacks firstclass polymorphism (aka impredicative polymorphism), which means that we can’t specialize an existing data type to hold a polymorphic value like this:
Maybe (LambdaSym repr => repr Int)
It follows that we can’t interpret such polymorphic value twice (but this situation doesn’t appear very frequent in cases where we just want an Application monad with some overloaded operations). This is an issue when, for example, we parse some text file, obtain a tagless final AST, and want to interpret it twice: to evaluate and to pretty print. There is a limited workaround: define a newtype wrapper around a polymorphic value. The wrapper specifies concrete type constraints and hence kills one extensibility dimension of tagless final.
Oleg’s paper also presents another workaround: a special “duplicating” interpreter. Unfortunately, It is presented using a simple eDSL without lambda functions and I failed to apply the same technique to a more complicated AST with lambdas. I mention it here just for the sake of completeness.
Also, note that sometimes people want to change implementations (aka interpreters) in the runtime, not in the compile time, or even change only a part of the existing behaviour. For example, change the data source but leave all other applicationspecific logic intact. Tagless final can support it by implementing an interpreter configurable in the runtime which uses some sort of a method dictionary (see the handle pattern).
Thanks to MTL, tagless final style of programming was battletested and has wide adoption. In my opinion, it’s quite a natural way to write Haskell code because it utilizes a very basic Haskell feature: type classes. It also goes far beyond MTL — it can be used both for writing applicationspecific logic with and without monad transformers and “true” eDSLs with their own semantics.
I also found that it’s not a hard concept to grasp, so it can safely be used in a large team of developers with different backgrounds.
That’s all, I hope my post will help others to grasp the main idea of tagless final and to use it in their projects.
Many thanks to Gints Dreimanis, Vlad Zavialov and others from Serokell for their help in writing this article. Without their reviews and suggestions, this post would not have happened.
Not in technologies we use, not in management strategies, but in the entire way we think about work.
At the moment of writing this, I am sitting in a hip café in the centre of Frankfurt, Germany. I don’t live in Frankfurt, and neither am I on a vacation. Rather, I am just sneaking in some work between city hikes and the rain that reminds me of the Genesis Flood.
Although I am sorrowful about being a living, breathing stereotype, I couldn’t be more thankful about working at Serokell. I love the job to pieces. Serokell has been remote from the very beginning. Our employees work from a diverse set of countries and time zones, yet the results are stunning.
Why did we choose to be this way? There are multiple, objective benefits to going remote. Supporting remote work helps you to hire better talent, make your employees happier, and decrease the operating costs. Based on my personal and corporate experience, I will outline these benefits in more detail. Here we go!
Vision, processes, technologies are all important. Yet, the small everyday interactions of human beings make a company.
If your line of work is rather specific like ours, top talent will be scarce in any single geographic location. Hiring remote workers enables to hire driven and knowledgeable employees that are also a great fit for the work culture.
Furthermore, hiring remotely enables to recruit teams that are diverse in their knowledge, skills, and culture. Diversity helps in multiple ways.
First, the technologies in the modern world change at a blistering pace. Diverse teams help to always have a headstart in the research of technology trends. In diverse teams, you always have someone eager to tinker with the potential “next great thing”. Second, teams with members from different cultures enable to better understand the individual needs of clients and endusers.
People, from our brave CEO to each and every one of our inquisitive junior developers, are the most vital part of Serokell, and remote work is how we attract the best of them.
Studies show that employees are more satisfied and productive when doing remote work.
Autonomy (sense of control over events that happen in our life) is very important for the modern workforce, and luckily, essential to remote work. When to work, how to implement the small details, and other things that don’t matter to the quality of the work being done are left to our employees. In exchange, we help them to achieve the best result they can, because that is what matters.
Remote work also enables employees to find a better worklife balance. We want to achieve a balanced, sustainable rhythm of work so that our employees don’t burn out, so we allow them to choose when to participate and when to turn off their Slack.
Having the permission to work autonomously and work when they are inspired means that employees are able to unleash their creative and scientific potential. They feel at home not because of a cozy office, but because they are actually there.
Remote is, quite frankly, cheaper.
Offices and their surrounding paraphernalia use up a lot of resources that could be better spent elsewhere. It doesn’t really matter how much cash you spend on your office, pingpong tables, and bean bags, it will still be an office, and your employees will still yearn freedom. And don’t get me started on commuting.
Remote work has some operating costs as well — we have to pay for different software services, devices, and other benefits. Most of the resources, though, we can spend pursuing interesting and creative side projects, opportunities, and research that makes our employees more engaged with the work they are doing at Serokell.
In the end, though, being remote isn’t easy.
I don’t want to paint an unnecessarily rosy picture. There are several problems a remote company has to tackle: productivity, friction in communication, inner group cohesion/employee loneliness, wrong incentives and faulty management leading to a blurred boundary between life and work.
Still, it is the way remote companies solve these problems (the issue trackers, the wiki’s, the teambuilding events online and offline, the gettogethers, the funny emoticons in Slack, the design thinking applied to understand the individual needs of every single employee) that will make them prevail. Many of these problems are present in regular companies as well, but as they are not as pressing, they are usually not being solved at all. Working remote brings us closer to understanding the real nature of work.
All in all, remote work is creeping into our lives whether we want it or not. It’s the outcome of technology making everyone more and more connected. If your employees are responding to emails in the Sunday afternoon, it is necessary to recognize that as productive work. And to properly do that, you have to put in place an infrastructure that is halfway ready to support fully remote work.
Whether to take the leap then, your choice, but your employees will thank you.
(As you might have read in the text, I work at Serokell. It’s a wonderful company, and we create software using a bit braver (and better) technologies than your usual software vendor. One of our upcoming projects is a human resources management system for remote businesses. If you want to hear more about the project, software development, or just get news about the latest technologies and solutions in the field of IT, follow us on Twitter. Thanks, and have an amazing day.)
]]>Let us define the datatype ⊥
, an empty type with no constructors (socalled bottom):
data ⊥ : Set where
We may read ⊥
as the datatype that corresponds to an absurd statement or contradiction.
The equivalent Haskell datatype is Void
:
data Void  no constructors
¬
defines negation. As we have told before, $\neg A$ denotes that any proof of $A$ yields a proof of a contradiction:
¬_ : Set → Set
¬ A = A → ⊥
Negation in Haskell is defined similarly:
type Not a = a > Void
exFalso
is an elimination of ⊥
that derives an arbitrary statement from bottom:
exFalso : {A : Set} → ⊥ → A
exFalso ()
This notation is the equivalent of Haskell’s EmptyCase, \case{}
.
We consider the following examples of propositions with negation that are provable constructively.
exContr : {A B : Set} → ¬ A → A → B
exContr f x = exFalso (f x)
exContr
derives an arbitrary formula from a contradiction. The first argument f
has a type ¬ A
(or A → ⊥
). The second argument x
has a type A
. Thus f x
is an object of type ⊥
.
Hence exContr (f x)
has a type B
.
contraposition : {A B : Set} → (A → B) → (¬ B → ¬ A)
contraposition f g = g ∘ f
Contraposition is a composition of g
and f
, where g
proves B → ⊥
and f
proves A → B
. Hence, g ∘ f
proves A → ⊥
, i.e. ¬ A
.
¬intro : {A B : Set} → (A → B) → (A → ¬ B) → ¬ A
¬intro f g x = g x (f x)
¬intro
introduces negation on the formula A
. If we can derive contradictional consequences B
and ¬ B
from the statement A
, then any proof of A
yields ⊥
.
disjImpl : {A B : Set} → ¬ A ⊎ B → A → B
disjImpl (inj₁ x) a = exContr x a
disjImpl (inj₂ y) a = y
disjImpl
establishes a connection between disjunction and implication. Suppose we prove $\neg A \vee B$ and have a proof of $A$. If we have a proof of $\neg A$, then we have proved $B$ by exfalso. If we have a proof of $B$, then we have already proved $B$ trivially. Anyway, we have obtained a proof of $B$ by case analysis (by patternmatching in the functional programming terminology).
Let us consider some examples with more informative statements.
Remember the Lie ring. A Lie ring has another interesting property that may be proved using constructive negation.
Theorem 5 Let $\langle R, +, \cdot, , 0 \rangle$ be a Lie ring. Then there is no $e \in R$ such that $e \neq 0$ and forall $a \in R, a \cdot e = a$. In other words, the Lie ring has no multiplication identity.
Proof Suppose there exists $e \in R$ such that $e \neq 0$ and forall $a \in R, a \cdot e = a$. Then $e \cdot e = e$. On the other hand, $e \cdot e = 0$ by alternation. Then $0 = e \cdot e = e$, thus $e = 0$. But $e \neq 0$. Contradiction. $\Box$.
In the firstorder language, we write the statement from the theorem above as: $\neg (\exists e \in R, (\neg (e = 0) \land (\forall x \in R, x \cdot e = x)))$, that is: $(\exists e \in R, (\neg (e = 0) \land (\forall x \in R, x \cdot e = x))) \to \bot$.
We read this formula as “if there exists $e \in R$, such that $e \neq 0$ and forall $x \in R$ $x \cdot e = x$, then bottom is proved”. That is, if we present some $e$ with required properties, then we proved a contradiction. According to the definition of $\Sigma$type, a proof of an existence of $e$ with those properties is an ordered pair $(e, P)$, where $e$ is some element of a Lie ring $R$ and $P$ is a proof that a property holds for $e$. In our case, $P$ is a proof of $\neg (e = 0) \land (\forall x \in R, x \cdot e = x))$, i.e., $P$ is an ordered pair (¬e≡θ , identityProof)
, where ¬e≡θ
proves $\neg (e = 0)$ and identityProof
proves $\forall x \in R, x \cdot e = x$.
Accordingly, we need to prove a contradiction with the ordered pair (e, (¬e≡θ, identityProof))
, what we have already done above, but informally.
We formalise this proof in Agda:
noIdentity : ¬ (Σ R λ e → (¬ (e ≡ θ)) × ((x : R) → x · e ≡ x))
noIdentity (e , (¬e≡θ , identityProof)) =
let identityIsZero = sym $ trans (sym $ alternation e) (identityProof e) in
¬e≡θ identityIsZero
We may unfold the negation in the signature of the given function and obtain the type
Σ R λ e → ¬ (e ≡ θ) × ((x : R) → x · e ≡ x) → ⊥
.
In other words, we have a triple e , ¬e≡θ , identityProof
and we should prove ⊥
, where e : R
, ¬e≡θ
is a proof that $e$ doesn’t equal to zero and identityProof
is a proof that for all $x \in R, x \cdot e = x$.
It is easy to show that $e$ equals to zero (follows from alternation axiom and assumption identityProof)
. We declare locally a term called identityIsZero
for this purpose. Hence, identityIsZero : e ≡ θ
. On the other hand, ¬e≡θ : ¬ e ≡ θ
, i.e. ¬e≡θ : e ≡ θ → ⊥
. Thus ¬e≡θ identityIsZero : ⊥
.
Now, we introduce double negation elimination as a postulate that allows producing classical proofs.
postulate
¬¬elim : ∀ {A} → ¬ ¬ A → A
¬¬elim
cannot be defined constructively, so this postulate does not need a definition. This is a doubleedged sword: we can also postulate things that are clearly false, e.g. 1 ≡ 0
, and derive any statement from the postulate.
The following propositions are provable classically, but not constructively:
The law of excluded middle is a consequence from double negation elimination so far as $\neg \neg (A \vee \neg A)$ is provable constructively:
lem : ∀ {A} → A ⊎ ¬ A
lem = ¬¬elim $ λ f → f $ inj₂ $ λ x → f $ inj₁ x
Similarly, we obtain disjunction $\neg A \vee B$ from an implication $A \to B$.
functionToDisj : ∀ {A B} → (A → B) → ¬ A ⊎ B
functionToDisj f = ¬¬elim (λ x → x $ inj₁ λ y → x $ inj₂ $ f y)
Converse contraposition is also provable classically but not intuitionistically. We may prove constructively only $(\neg A \to \neg B) \to (\neg \neg B \to \neg \neg A)$, but there is no possibility to remove double negations in conclusion of this implication. We can do it using the double negation elimination law.
contraposition' : ∀ {A B} → (¬ A → ¬ B) → B → A
contraposition' f x = ¬¬elim λ a → f a x
This constructively bad De Morgan’s law is fine from a classical point of view:
deMorgan₁ : ∀ {A B} → ¬ (A × B) → ¬ A ⊎ ¬ B
deMorgan₁ f = ¬¬elim λ g → g $ inj₁ $ λ a → g $ inj₂ λ b → f (a , b)
We leave as an exercise to figure out whether this De Morgan law is provable constructively or classically:
deMorgan₂ : ∀ {A B} → ¬ (A ⊎ B) → ¬ A × ¬ B
deMorgan₂ = {!!}
The second exercise: prove Peirce’s law:
peirce : ∀ {A B} → ((A → B) → A) → A
peirce = {!!}
Moreover, it is possible to get some useful consequences for quantifiers. We inhabit the following two types in a constructive way:
quantifier₁ :
∀ {A} {P : A → Set} →
(f : (x : A) → P x) →
¬ Σ A λ a → ¬ P a
quantifier₁ f (fst , snd) = exContr snd (f fst)
quantifier₂ :
∀ {A} {P : A → Set} →
(Σ A λ a → P a) →
¬ ((x : A) → ¬ P x)
quantifier₂ (fst , snd) f = exFalso (f fst snd)
Double negation elimination makes it possible to prove converse implications (we leave the second one as an exercise):
quantifier₃ :
∀ {A} {P : A → Set} →
(¬ Σ A λ a → ¬ P a) →
((x : A) → P x)
quantifier₃ f x = ¬¬elim $ λ p → f x , p
quantifier₄ :
∀ {A} {P : A → Set} →
¬ ((x : A) → ¬ P x) →
(Σ A λ a → P a)
quantifier₄ f = {!!}
Markov’s principle is the statement proposed by Russian logician A. Markov Jr., the founder of the Russian school of constructive mathematics and logic.
Suppose that for all $a$ we have a method that tells whether $P(a)$ is true or not (a property $P$ is decidable). If it is not true that there is no $x$ such that $P(x)$ holds, then there exists $x$ such that $P(x)$. In other words, we may use the double negation elimination rule, if we work with decidable relation.
Markov’s principle has the following form:
$\forall x, (P (x) \lor \neg P(x)) \to (\neg \neg (\exists x, P (x)) \to \exists x, P (x))$.
Classically, $\forall x, (P (x) \lor \neg P(x))$ is an universal closure of the law of excluded middle. Constructively, $\forall x, (P (x) \lor \neg P(x))$ denotes that we can define satisfiability of a property $P$ for each $a$. That is, $\forall x, (P (x) \lor \neg P(x))$ denotes decidability of a predicate $P$. Suppose that there is no $x$ such that $P(x)$ is rejected. Thus, we can conclude that there exists some desired $x$, provided that a given property is decidable.
Thus, Markov’s principle is a restriction of the double elimination rule, which is acceptable to use in constructive mathematics in the case of decidable predicates.
We declare Markov’s principle in Agda as a postulate because this principle is not provable constructively as a general case.
postulate
MarkovPrinciple :
∀ {a} {A : Set a} {P : A → Set a} →
(f : (x : A) → P x ⊎ ¬ (P x)) →
¬ ¬ (Σ A P) → Σ A P
Let us explain the use of Markov’s principle in Agda through an example. At first, we declare the datatype called Dec
to define a decidable type in Agda:
data Dec {a} (P : Set a) : Set a where
yes : ( p : P) → Dec P
no : (¬p : ¬ P) → Dec P
This datatype consists of two constructors: yes
and no
. In other words, Dec P
defines a characteristic function of this type.
This lemma claims that the universal closure of the law of excluded middle for predicate P
follows from its decidability:
DecLem :
∀ {a} {A : Set a} {P : A → Set a} →
(f : (x : A) → Dec (P x)) →
((x : A) → P x ⊎ ¬ P x)
DecLem f x = case (f x) of
λ { (yes p) → inj₁ p
; (no ¬p) → inj₂ ¬p
}
That is, if we have a general method that for each x : A
tells “yes” or “no” for some predicate P
, then the law of excluded middle is derivable almost for free!
Let us apply Markov’s principle that is applied to propositional equality of natural numbers. It is obvious that this binary relation is decidable:
Decℕ : (a b : ℕ) → Dec (a ≡ b)
Decℕ zero zero = yes refl
Decℕ zero (suc b) = no λ()
Decℕ (suc a) zero = no λ()
Decℕ (suc a) (suc b) with Decℕ a b
Decℕ (suc a) (suc b)  yes refl = yes refl
Decℕ (suc a) (suc b)  no ¬p = no (¬p ∘ ℕlemma a b)
After that, we prove that the double negation elimination laws hold for propositional equality of natural numbers. That is, we can conclude by using the Markov’s principle that there exist equal natural numbers if the nonexistence of those numbers leads to a contradiction:
¬¬elimℕ : ¬ ¬ (Σ ℕ λ a → Σ ℕ λ b → a ≡ b) → Σ ℕ λ a → Σ ℕ λ b → a ≡ b
¬¬elimℕ f = MarkovPrinciple (DecLem ¬¬elimℕlemma) f
where ¬¬elimℕlemma
proves that for all x : ℕ
, the relation Σ ℕ (λ b → x ≡ b)
is decidable.
Note that Markov’s principle is provable trivially in classical firstorder logic:
$\begin{array}{lll} (1) &(\neg \neg (\exists x, P (x)) \to \exists x, P (x)) \to (\forall x, (P (x) \lor \neg P(x)) \to (\neg \neg (\exists x, P (x)) \to \exists x, P (x)))& \\ &\:\:\:\: \text{Axiom schema 2}& \\ (2) &\neg \neg (\exists x, P (x)) \to \exists x, P (x)& \\ &\:\:\:\: \text{Axiom schema 10}& \\ (3) &\forall x, (P (x) \lor \neg P(x)) \to (\neg \neg (\exists x, P (x)) \to \exists x, P (x))& \\ &\:\:\:\: \text{(1), (2), Modus ponens}& \\ \end{array}$
We formalise the classical proof of this statement in Agda as follows:
MarkovClassically :
∀ {a} {A : Set a} {P : A → Set a} →
(f : (x : A) → P x ⊎ ¬ (P x)) →
¬ ¬ (Σ A P) → Σ A P
MarkovClassically f p = const (¬¬elim p) f
In this subsection, we demonstrate the difference between constructive proofs and classical proofs. In other words, we are going to find out what exactly changes when we resolve to use the double negation elimination and its consequences. Let us consider some examples.
Remember the theorem that for all list xs
there exists list ys
, such that ys
equals to reversed list xs
. Now we prove the same statement using the double negation elimination:
theoremReverse₂ : ∀ {a} {A : Set a} (xs : List A) →
Σ (List A) λ ys → IsReverse xs ys
theoremReverse₂ [] = ¬¬elim λ p → p ([] , ReverseNil)
theoremReverse₂ (x ∷ xs) =
¬¬elim λ p →
p (proj₁ (theoremReverse₂ xs) ++ [ x ] ,
ReverseCons x xs (proj₁ (theoremReverse₂ xs))
(proj₂ (theoremReverse₂ xs)))
We prove this theorem by induction on xs
, but we suppose that for every case there is no list ys
such that ys
is a reversed xs
and obtain a contradiction. In other words, we are going to show that the statement is true and it is not necessary to build an explicit construction to solve this problem. In fact, we proved that the nonexistence of the desired list leads to a contradiction, so we concluded that the required list exists using double negation elimination.
We consider a bit more complicated example with a Lie ring again. We have already proved constructively before that a Lie ring has no identity multiplication. We prove below an equivalently formulated statement using the double negation elimination law.
Informally, the statement sounds like “Let $R$ be a Lie ring, then there is no $e \in R$ such that it is not true that $e = 0$ or not for all $x \in R, x \cdot e = x$”. Formally, $\neg (\exists e \in R), \neg ((e = 0) \lor \neg (\forall x \in R, x \cdot e = x))$.
noIdentity₁ : ¬ (Σ R λ e → ¬ ((e ≡ θ) ⊎ ¬ ((x : R) → x · e ≡ x)))
noIdentity₁ (e , snd) =
let disjToConj = deMorgan₂ snd in
let ¬¬condition = proj₂ disjToConj in
let ¬¬elimination = ¬¬elim ¬¬condition in
let ¬e≡θ = proj₁ disjToConj in
let identityIsZero = sym $ trans (sym (alternation e)) (¬¬elimination e) in
¬e≡θ identityIsZero
We unfold the negation in the signature and get this type:
noIdentity₁ : Σ R λ e → ¬ ((e ≡ θ) ⊎ ¬ ((x : R) → x · e ≡ x)) → ⊥
i.e., we should prove ⊥
using pair e , snd
, where e : R
and snd : ¬ ((e ≡ θ) ⊎ ¬ ((x : R) → x · e ≡ x))
.
By de Morgan’s law deMorgan₂
,
deMorgan₂ snd : ¬ (e ≡ θ) × ¬ ¬ ((x : R) → x · e ≡ x))
We will denote deMorgan₂ snd
as disjToConj
for brevity.
Thus proj₁ disjToConj : ¬ (e ≡ θ)
and proj₁ disjToConj : ¬ ¬ ((x : R) → x · e ≡ x))
. We will denote these terms as ¬e≡θ
and ¬¬condition
correspondingly. Hence, ¬¬elim ¬¬condition : (x : R) → x · e ≡ x)
.
After that, we produce the same reasoning as in the constructive counterpart of this proposition, but we use the fact that $\neg (A \vee \neg B)$ classically implies $\neg A \wedge B$, where $A, B$ are arbitrary statements. Intuitionistically, $\neg (A \vee \neg B)$ implies $\neg A \wedge \neg \neg B$, but $\neg \neg B$ is not equivalent to $B$, and we use the double negation elimination to obtain $B$ from its double negation.
In this series, we first discussed the logical background with a look on classical and constructive logic calculi. After that, we introduced the reader to theorem proving and dependently typed programming in Agda and played with (non)constructive negation. It is quite clear that our introduction is not that exhaustive. If these topics interest you, we would like to propose you the following books for further study:
The basic introduction to mathematical logic, foundations of mathematics, and computability theory is “Introduction to metamathematics” by Stephen Cole Kleene, one of founders of modern mathematical logic.
To learn more about proof theory and its connection with type theory, we recommend the wonderful textbook called “Basic proof theory” by Anne Sjerp Troelstra and Helmut Schwichtenberg.
You might read more systematic and detailed introduction to Agda by Aaron Stump. For more indepth Agda learning, we advise the paper called “Dependently typed metaprogramming” by Conor McBride.
As an extra reading, we would like to recommend you several more books:
You might consider the topological and algebraic approach to classical and intuitionistic logic. If so, it would be useful to read “The mathematics of metamathematics” by Helena Rasiowa and Roman Sikorski for this purpose.
Also, we wrote about a Lie ring and related algebraic systems. To learn about algebraic systems in more detail, see the widely known work on universal algebra and model theory called “Algebraic systems” by famous Soviet mathematician and logician Anatolij I. Mal’tsev.
The classical monograph by Boris A. Kushner presents real analysis and calculus based on constructive logical principles. This work is a good example of development of mathematics via constructive logic and computability theory as its foundations.
You also may read about philosophical aspects of constructive mathematics and logic in “Elements of intuitionism” by English philosopher and logician Michael Dummett.
To read more posts like these, follow Serokell on Twitter and Facebook!
I would like to thank Gints Dreimanis, Jonn Mostovoy and Vlad Zavialov for reviews, critique, and useful advice.
]]>The main goal is to search for a partner, not a vendor. Outsourcing is not about getting the cheapest quote on your order, but about building a useful relationship that will serve you in the future.
How to find the right partner then? Here are 4 tips for making the fateful choice:
Proper initiation can make or break a project.
Once you have a breakthrough idea, it is easy to zero in on a few features, technologies, solutions. If everybody else is doing a simple blockchain in Java, or, god forbid, Python or Ruby, you might think you need one as well.
Furthermore, if you get unlucky with your software development team, they will just nod their heads in unison and agree.
We believe it is important to look at the big picture and question the jobtobedone of your solution. If you communicate your highlevel needs to partners, you enable them to research and find individual solutions in their fields of expertise that fit your needs better than “the next best thing”.
Once you have agreed with your future software team, make sure to work with them to draft comprehensive requirements that will help you both to understand each other better and communicate the work that should be done.
The cheapest option in the market usually isn’t the best choice. If you buy cheaply, you pay dearly. In software, these costs usually come as bugs, crashes, and a mismatch between your (and your customer’s) needs and the solution.
Of course, you need to choose the quality level that is suitable for your project, but every software development company you hire should use proper processes, have a dedicated QA team, and, preferably, use DevOps and guarantee maintenance. Modern business environment and users demand that things don’t break, and if they do, they should be fixed instantly.
If your project concerns handling large amounts of money (fintech, cryptocurrencies) or health of others (biotech), it is worthy to look into additional quality assurance for your software – look for software companies that use functional programming (benefits: reliable, more secure and easier maintainable code) and formal verification.
The winds of the future are uncertain, and the technologies change with every breeze. Still, it is important to sense where the tech is going and choose the correct solutions ahead of the market. Otherwise, you risk being outdated in the fastmoving, cutthroat markets of today.
For example, Rust has been voted as the most loved programming language between developers in 2016 and 2019. If you want to get the best new talent in the market, having your project in Rust is a wonderful way to attract them. (In addition to other benefits.)
If you choose the correct development partner, they should be able to communicate their programming language and solution choices to you, explain the reasons underneath and make sure the choices match your individual needs.
Choose a partner that can support your longterm growth and help you with infrastructure and maintenance of the code.
Once your project takes hold, it is most likely that it will need to scale. For this reason, you need to select a software development company that can provide for a diverse set of needs, prevent any bottlenecks, and, if necessary, extend their operations to offer personal service just for you.
Furthermore, software projects are always in a state of continuous improvement. Pick the company that won’t disappear after finishing the project because your software will need maintenance, improvements to accustom your shifting needs and those of the market.
Hopefully, these tips will help you to make the correct choice. If you want to hear more about software development and get news about the latest technologies and solutions in the field of IT, follow us on Twitter.
]]>In our previous post, we took a look on logic in itself, we considered classical and intuitionistic logical calculi. It will come in handy today when we start tackling theorem proving in Agda based on a constructive logical framework.
In this article, we will introduce the reader to dependently typed programming and theorem proving in Agda. We will compare our examples of Agda code with their Haskell counterparts. After that, we will consider an example use of Agda in the formalisation of algebra.
Agda is a dependently typed functional programming language based on a variant of MartinLöf type theory. This type theory is a constructive prooftheoretic formal system, so Agda doesn’t have the default features for writing classical logic based proofs in contrast to HOL or Isabelle.
Agda has a Haskelllike syntax and a much richer type system (but we’re working on bringing Haskell to the same level), so we may use Agda as a proofassistant based on the propositionsastypes paradigm. In other words, Agda is a programming language that allows providing formal proofs within constructive mathematics.
In this section, we will make a brief introduction to the syntax and possibilities of Agda.
See here for a more detailed introduction.
Datatypes in Agda are introduced as generalized algebraic datatypes (GADT):
Trivial example: a datatype of writers and a datatype of their works.
data Writers : Set where
Wilde Shelley Byron Sartre Camus : Writers
data Literature : Set where
DorianGrey Alastor ChildeHarold LaNausée L’Étranger : Literature
The typical example is a datatype of unary natural numbers:
data ℕ : Set where
zero : ℕ
suc : ℕ → ℕ
In Haskell we define these types as follows:
data Writers =
Wilde
 Shelley
 Byron
 Sartre
 Camus
data Literature =
DorianGrey
 Alastor
 ChildeHarold
 LaNausée
 L’Étranger
data ℕ = Zero  Suc ℕ
These datatypes are socalled simple datatypes.
See to read about parameterized and indexed datatypes in more detail.
A parameterized datatype is a datatype that depends on some parameter that should remain the same in the types of constructors. For instance, List
is a datatype parameterized over a type A
.
data List (A : Set) : Set where
Nil : List A
Cons : A → List A → List A
Datatypes also can have indexes that could differ from constructor to constructor in contrast to parameters. These datatypes are socalled datatype families. Example:
data Vec (A : Set) : ℕ → Set where
Nil : Vec A zero
Cons : {n : ℕ} → A → Vec A n → Vec A (suc n)
Vec
(vector) is a datatype that is applied to another type A
and a natural number n
. We also say that Vec
is parameterized by A
and indexed by ℕ
. Nil
is an empty vector. Cons
is a constructor adding a new element to a given vector increasing the length counter. Here n
is an implicit argument of the constructor. Cons
is also an example of a dependent function, as will be seen below.
In Haskell, we would have the following GADT via DataKinds
, PolyKinds
and GADTs
extensions:
{# LANGUAGE DataKinds, PolyKinds, GADTs #}
import Data.Kind (Type)
data Vec :: Type > ℕ > Type where
Nil :: Vec a 'Zero
Cons :: a > Vec a n > Vec a ('Succ n)
Another example: Fin
is a type that describes finite types, in other words, types of finite cardinality. Informally, Fin
is a type of finite sets:
data Fin : ℕ → Set where
zero : {n : ℕ} → Fin (suc n)
suc : {n : ℕ} → Fin n → Fin (suc n)
Fin
is a datatype indexed by a natural number. zero
reflects an empty set which is an element of any finite set. suc
is a constructor that applies the finite set i
and yields a new finite set, which is a larger than i
on one element.
In Haskell, we define Fin
using DataKinds
and KindSignatures
extensions as with vectors above:
{# LANGUAGE DataKinds, PolyKinds, GADTs #}
import Data.Kind (Type)
data Fin :: Nat > Type where
Fzero :: forall (n :: ℕ). Fin ('Succ n)
Fsucc :: forall n. Fin n > Fin ('Succ n)
Note that, datatypes Vec
and Fin
implemented in Agda are also examples of dependent types, these types depend on values (in particular, on natural numbers), not only on types.
On the other hand, their counterparts in Haskell are not dependent types in a strict sense, because forall (n :: ℕ)
in Haskell does not introduce a value, therefore Fin
does not depend on any values.
See here for an introduction to type level programming in Haskell.
Functions in Agda are arranged similarly to Haskell, using pattern matching:
bookToWriter : Literature → Writers
bookToWriter DorianGrey = Wilde
bookToWriter Alastor = Shelley
bookToWriter ChildeHarold = Byron
bookToWriter LaNausée = Sartre
bookToWriter L’Étranger = Camus
We will discuss dependent functions in more detail. A dependent function is a function that takes a term a
of type A
and returns some result of type B
, where a
may appear in B
. We consider a simple example, where a
is a type itself.
const₁ : (A B : Set) → A → B → A
const₁ A B x y = x
Here types A
and B
are explicit arguments. If we don’t need to mention these types explicitly, we may take them implicitly:
const₂ : {A B : Set} → A → B → A
const₂ x y = x
We may compare this function in Agda with a similar function implemented in Haskell with the help of the RankNTypes
extension that allows writing the universal quantifier on types explicitly:
{# LANGUAGE RankNTypes #}
const₁ :: forall a b. a > b > a
const₁ x y = x
We introduce the following datatypes to consider the next example:
data Ireland : Set where
Dublin : Ireland
data England : Set where
London : England
data France : Set where
Paris : France
After that, we construct a function that returns some country datatype for a given writer.
WriterToCountry : Writers → Set
WriterToCountry Wilde = Ireland
WriterToCountry Shelley = England
WriterToCountry Byron = England
WriterToCountry Sartre = France
WriterToCountry Camus = France
WriterToCity : (w : Writers) → WriterToCountry w
WriterToCity Wilde = Dublin
WriterToCity Shelley = London
WriterToCity Byron = London
WriterToCity Sartre = Paris
WriterToCity Camus = Paris
WriterToCity
is an example of a dependent function, because the type of the result depends on the argument. The type of a dependent function is the socalled $\Pi$type. More generally, suppose we have some function P: A → Set
, then $\Pi$type is a type of the function that assigns to every term t : A
some object of type P t
. In Agda notation, we write this type as (t : A) → P t
for some {A : Set} {P : A → Set}
in context.
Logically $\Pi$type encodes intuitionistic universal quantifier. In other words, P
is a predicate and some function of a type (t : A) → P t
is a proof that for each t : A
the property P
has established.
In Haskell, we don’t have dependent quantification at term level, but we have it at type level. Therefore, we implement comparable functions as closed type families. Similarly, we promote values of type Writers
into the type level via DataKinds
in both cases:
{# LANGUAGE TypeFamilies #}
{# LANGUAGE DataKinds #}
data Ireland = Dublin
data France = Paris
data England = London
type family WriterToCountry (a :: Writers) :: Type where
WriterToCountry 'Wilde = Ireland
WriterToCountry 'Shelley = England
WriterToCountry 'Byron = England
WriterToCountry 'Sartre = France
WriterToCountry 'Camus = France
Also, we declare WriterToCity
type family that is similar to WriterToCity
function implemented in Agda. In contrast to Agda, we should enable the language extension called TypeInType
. You may implement this function via PolyKinds
since GHC 8.6.
{# LANGUAGE TypeFamilies #}
{# LANGUAGE DataKinds #}
{# LANGUAGE TypeInType #}
type family WriterToCity (a :: Writers) :: WriterToCountry a where
WriterToCity 'Wilde = 'Dublin
WriterToCity 'Shelley = 'London
WriterToCity 'Byron = 'London
WriterToCity 'Camus = 'Paris
WriterToCity 'Sartre = 'Paris
We introduce the dependent pair type ($\Sigma$type) through a simple example.
Let us consider the following inductive predicate on two lists as a GADT:
data IsReverse {A : Set} : (xs ys : List A) → Set where
ReverseNil : IsReverse [] []
ReverseCons : (x : A) (xs ys : List A)
→ IsReverse xs ys
→ IsReverse (x ∷ xs) (ys ++ [ x ])
IsReverse xs ys
denotes that the list ys
is equal to reversed xs
. IsReverse
is a datatype parameterized over a type A
and indexed over two lists of elements of type A
.
We prove the theorem that for all list xs
there exists list ys
such that ys
is a reversed list xs
. In other words, we should produce a dependent pair, where the first projection of this pair is some list ys
and the second one is a proof that ys
is reversed list xs
.
We use the dependent pair type to prove existence:
theoremReverse₁ : {A : Set} (xs : List A) → Σ (List A) (λ ys → IsReverse xs ys)
theoremReverse₁ [] = [] , ReverseNil
theoremReverse₁ (z ∷ zs) =
let ys = proj₁ (theoremReverse₁ zs) in
let ysProof = proj₂ (theoremReverse₁ zs) in
ys ++ [ x ] , ReverseCons z zs ys ysProof
We should read the signature {A : Set} (xs : List A) → Σ (List A) (λ ys → IsReverse xs ys)
as “let A
be type and xs
be a list with elements of the type A, then there exists list ys
, such that ys
is the reversed list xs
”.
We prove this theorem by induction on xs
and build ys
for every case. If xs
is empty, then we present an empty list as the desired list ys
and ReverseNil
is a proof that ys
is equal to reversed list xs
, because both of them are empty. Thus, we have proved the base case.
The inductive step is a case when xs
is a nonempty list, i.e. xs = z :: zs
. List zs
is smaller than z :: zs
, so we can apply induction hypothesis to zs
and show that ::
preserves the desired property using the ReverseCons
constructor.
Generally, $\Sigma$type is defined in Agda as follows:
record Σ {a b} (A : Set a) (B : A → Set b) : Set (a ⊔ b) where
constructor _,_
field
fst : A
snd : B fst
This record is defined for arbitrary universes, and we consider below what universes are in more detail. The given record has two fields. The first field called fst
is an object of a type A. The second one is an application B
to the argument from the first field.
From a logical point of view, we read Σ A B
as “there exists some element of a type A
, such that this element satisfies property B
”, i.e. $\Sigma$type encodes constructive existential quantifier. As we have told before, to prove the existence of some object with desired property constructively, then we should present the particular object and prove this object satisfies this considered property.
A hierarchy of types is a quite old idea that initially belongs to the logician and philosopher Bertrand Russell. The basic types such as Nat
or Bool
have a type called Set
, but Set
is not Set
itself. Set
has a type Set₁
, etc. Otherwise, we could infer Russell’s paradox, so our system would be inconsistent in that case.
Universe polymorphism also allows defining functions that can be called on types of arbitrary levels. Let us show how universe polymorphism works on the following example. Suppose we have this function called s
:
s : {A B C : Set} → (A → B → C) → (A → B) → A → C
s f g x = f x (g x)
We implement a similar function in Haskell via RankNTypes
:
{# LANGUAGE RankNTypes #}
s :: forall a b c. (a > b > c) > (a > b) > a > c
s f g x = f x (g x)
We can indicate that types A
, B
, C
are types of arbitrary level a
:
s₁ : {a : Level} {A B C : Set a} → (A → B → C) → (A → B) → A → C
s₁ f g x = f x (g x)
We generalize s
further, because A
, B
and C
in s₁
belong to the same level, but these types may generally belong to different levels :
s₂ : {a b c : Level} {A : Set a} {B : Set b} {C : Set c}
→ (A → B → C) → (A → B) → A → C
s₂ f g x = f x (g x)
We would implement something similar as closed type family in Haskell:
{# LANGUAGE TypeFamilies #}
{# LANGUAGE PolyKinds #}
type family S₂ (f :: a > b > c) (g :: a > b) (x :: a) :: c where
S₂ f g x = f x (g x)
By the way, the most general version of s
in Agda is an universe polymorphic dependent function:
S : {a b c : Level} {A : Set a} {B : A → Set b} {C : (x : A) → B x → Set c}
→ (f : (x : A) → (y : B x) → C x y) → (g : (x : A) → B x)
→ (x : A) → C x (g x)
S f g x = f x (g x)
Records in Agda are arranged similarly to Haskell. This record implemented in Haskell
data Person = Person {
name :: String
, country :: String
, age :: Int
}
may be translated to Agda as follows via keyword record
:
record Person : Set where
field
name : String
country : String
age : Int
Typeclasses are introduced in Agda as records. For example, we implement typeclass called Functor
:
record Functor {a} (F : Set a → Set a) : Set (suc a) where
field
fmap : ∀ {A B} → (A → B) → F A → F B
Instances may be declared either by constructing a record explicitly or by using copatterns:
instance
ListFunctor₁ : Functor List
ListFunctor₁ = record { fmap = map }
instance
ListFunctor₂ : Functor List
fmap {{ListFunctor₂}} = map
The identity type is a datatype that reflects equality of terms in predicate logic. This type is defined as follows:
data _≡_ {a} {A : Set a} (x : A) : A → Set a where
refl : x ≡ x
We consider a few examples where propositional equality is used. The first one is a proof of distributivity of natural number multiplication over addition.
*+distr : ∀ a b c → (b + c) * a ≡ b * a + c * a
*+distr a zero c = refl
*+distr a (suc b) c =
begin
(suc b + c) * a ≡⟨ refl ⟩
a + (b + c) * a ≡⟨ cong (_+_ a) (*+distr a b c) ⟩
a + (b * a + c * a) ≡⟨ sym (+assoc a (b * a) (c * a)) ⟩
suc b * a + c * a
∎
This property is proved by induction on b
. When b
equals zero, the equality holds trivially. Otherwise, if b
is a nonzero number, then equality is proved using the induction hypothesis.
See the relevant part of the Haskell Prelude documentation.
We have propositional equality in Haskell as well:
data a :~: b where
Refl :: a :~: a
Unlike in Agda, this datatype is defined on types of arbitrary kinds, not on terms. In other words, (:~:) :: k > k > *
, where k
is an arbitrary kind.
We consider a few more examples in Agda to better understand its possibilities. Remember the abstract algebra, especially ring theory. We provide some algebraic definitions below:
Definition 3 A ring is an algebraic system $\langle R, +, \cdot, , 0 \rangle$, where $R$ is a nonempty set, $0 \in R$, $+$, $\cdot$ are binary operations on $R$ and $$ is a unary operation on $R$ such that:
We define a ring in Agda similarly by declaring a record called Ring
. Ring
is a type R
with additional operations:
record Ring (R : Set) : Set₁ where
constructor mkRing
infixr 7 _·_
infixr 6 _+_
field
θ : R
_ : R → R
_+_ : R → R → R
_·_ : R → R → R
After that, we formulate the axioms of a ring having declared operations above:
+assoc : (a b c : R) → (a + b) + c ≡ a + (b + c)
+commute : (a b : R) → a + b ≡ b + a
+inv₁ : (a : R) →  a + a ≡ θ
+θ : (a : R) → a + θ ≡ a
·distrright : (a b c : R) → (a + b) · c ≡ (a · c) + (b · c)
·distrleft : (a b c : R) → a · (b + c) ≡ (a · b) + (a · c)
open Ring {{...}} public
It is easy to see that the definition of a ring in Agda is similar to the informal definition of a ring proposed above.
Suppose we need to extend a ring with additional axioms. Consider a Lie ring that generalizes Lie algebra, which is a wellknown construction in linear algebra widely applied in mathematical physics and quantum mechanics. Initially, we suggest the following informal definition:
Definition 4 A Lie ring is a ring $\langle R, +, \cdot, , 0 \rangle$ with two additional axioms:
We extend a ring to a Lie ring in Agda by declaring a new record, but we need to tell somehow that a given type R
is already a ring. We pass an additional instance argument in a signature of a record called LieRing
for this purpose:
record LieRing (R : Set){{ring : Ring R}} : Set₁ where
Then we add the special axioms of a Lie ring as usual:
constructor mkLeeRing
field
alternation : (a : R) → a · a ≡ θ
jacobiIdentity : (a b c : R) → (a · b) · c + b · (c · a) + c · (a · b) ≡ θ
Lie ring is anticommutative, in other words:
Lemma 1 Let $R$ be Lie ring, then forall $a, b \in R$, $a \cdot b + b \cdot a = 0$.
Proof Firstly, we prove this fact informally. For convenience, we prove this useful proposition.
Proposition 1 If $R$ is a Lie ring, then forall $a, b \in R$, $(a + b) \cdot (a + b) = (a \cdot b) + (b \cdot a)$.
$\begin{array}{lll} & (a + b) \cdot (a + b) = & \\ & \quad\quad\quad\quad\quad\quad\quad \text{Right distribution} & \\ & a \cdot (a + b) + b \cdot (a + b) = & \\ & \quad\quad\quad\quad\quad\quad\quad \text{Left distribution} & \\ & (a \cdot a + a \cdot b) + (b \cdot a + b \cdot b) = & \\ & \quad\quad\quad\quad\quad\quad\quad \text{Alternation}& \\ & (a \cdot a + a \cdot b) + (b \cdot a + 0) = & \\ & \quad\quad\quad\quad\quad\quad\quad \text{Identity axiom}& \\ & (a \cdot a + a \cdot b) + b \cdot a = & \\ & \quad\quad\quad\quad\quad\quad\quad \text{Alternation}& \\ & (0 + a \cdot b) + b \cdot a = & \\ & \quad\quad\quad\quad\quad\quad\quad \text{Identity axiom}& \\ & a \cdot b + b \cdot a& \end{array}$ $\Box$
Thus $a \cdot b + b \cdot a = (a + b) \cdot (a + b) = 0$ by proposition above and alternation.
$\Box$
Formalisation of this proof completely preserves the reasoning above. The first lemma is the sequence of equalities applied sequentially via transitivity of propositional equality:
lemma : (a b : R) → (a + b) · (a + b) ≡ (a · b) + (b · a)
lemma a b =
begin
((a + b) · (a + b)
≡⟨ ·distrright a b (a + b) ⟩
a · (a + b) + b · (a + b)
≡⟨ cong₂ _+_ (·distrleft a a b) (·distrleft b a b) ⟩
(a · a + a · b) + (b · a + b · b)
≡⟨ cong (_+_ (a · a + a · b)) (cong₂ _+_ refl (alternation b)) ⟩
(a · a + a · b) + (b · a + θ)
≡⟨ cong (_+_ (a · a + a · b)) (+θ (b · a)) ⟩
(a · a + a · b) + (b · a)
≡⟨ cong₂ _+_
(cong₂ _+_ (alternation a) refl)
refl ⟩
(θ + a · b) + (b · a)
≡⟨ cong₂ _+_ (trans (+commute θ (a · b)) (+θ (a · b))) refl ⟩
(a · b + b · a)
∎)
Anticommutativity is proved in the same way using the obtained lemma:
anticommutativity : (a b : R) → (a · b + b · a) ≡ θ
anticommutativity a b = (a · b + b · a) ≡⟨ sym $ lemma a b ⟩ (alternation (a + b))
In this post, we have briefly described basic concepts of dependently typed programming and theorem proving in Agda via examples. In the next post, we will introduce negation with classical postulates and play with nonconstructive proofs in Agda.
]]>I would like to tell you about constructive and nonconstructive proofs in a proof assistant and functional programming language called Agda. I’m currently working on a paper on a generalized model of data processing in a blockchainsystem together with my teammates George Agapov and Kirill Briantsev. We use Agda to prove interesting properties of readonly state access computation, transaction validation, and block processing. Also, I’m writing a PhD thesis at Moscow State University on modalities in constructive and linear logic and their connection with computer science and mathematical linguistics.
We’ll split this article into several parts:
The first two parts are needed to introduce the reader to the necessary background. In the first part, we will give some theoretical background in mathematical logic to know what formal proof and related concepts are, regardless of functional programming context. After that, we will talk about constructive and nonconstructive proofs in mathematical logic and discuss a difference between them. Mathematical logic is the theoretical foundation of formal verification in dependently typed languages, so we need to discuss these basic technical and philosophical aspects to see the close connection between proof theory and dependently typed programming.
In the second part, we will introduce the reader to programming and theorem proving in Agda. We will compare the syntax and basic concepts of Agda and Haskell, and discuss the difference.
In the third part, we will prove some theorems in Agda in two ways: constructively and nonconstructively. We will see the difference between constructive and nonconstructive proofs in Agda through those examples.
As we will see later, Agda is a constructive formal system, i.e. we have no way to write nonconstructive proofs without some special postulates. We will discuss what exactly we obtain if we make Agda formal system classical by adding nonconstructive postulates.
Classical logic is the oldest branch of mathematical logic that has its roots in Aristotle’s works [1]. Classical logic took its shape in G. Boole’s [2] and G. Frege’s [3] works in the second half of the 19th century. The main motivation is a necessity to define whether some statement is true or false regardless of its content. In other words, we would like to establish the truth of a given statement from its form. By form, we mean a result of forming complex statements from atomic statements via special parts of speech by abstracting from specific meanings that can vary from context to context. In classical logic, atomic statements are propositional variables that can take a value from the twoelement set $\{ false, true \}$ and special parts of speech are logical connectives: conjunction (a counterpart of “and”), disjunction (a counterpart of “or”), implication (a counterpart of “if … then …”), and negation (a counterpart of “not”).
Thus classical proof is a syntactical way to establish the truth of a proposition from a twovalued point of view. We propose this syntactical way by this way. We define axioms (or axiom schemas, in other words, not only primitive formulas but all their special cases too) and inference rules that allows obtaining new theorems from formulas that are already proved yet. We use the single inference rule called Modus Ponens. Modus Ponens claims that if implication ($A \to B$) and assumption ($A$) are true, then the conclusion ($B$) is true.
We define the language of a classical propositional calculus:
Definition 1 Let $V = \{ p_0, p_1, \dots, \}$ be set of propositional variables. Thus:
Definition 2 (Classical propositional calculus) The classical propositional calculus (CPC) is defined by the following list of axiom schemes and inference rules:
Equivalently, we may define classical propositional logic as the smallest set $\mathfrak{L}$ that consists of all special cases of these schemas and is closed under Modus Ponens rule, i.e. if $A \in \mathfrak{L}$ and $A \to B \in \mathfrak{L}$, then $B \in \mathfrak{L}$.
Definition 3 (Formal proof) A (classical propositional) proof is a finite sequence of formulas, each of which is an axiom, or follows from the previous formulas by Modus Ponens rule.
Let us prove the formula $A \to A$ as an example: $\begin{array}{lll} (1) &(A \to ((A \to A) \to A)) \to (((A \to (A \to A)) \to (A \to A))& \\ &\:\:\:\: \text{Axiom schema}& \\ (2) &A \to ((A \to A) \to A)& \\ &\:\:\:\: \text{Axiom schema}& \\ (3) &((A \to (A \to A)) \to (A \to A)&\\ &\:\:\:\: \text{1, 2, Modus Ponens}& \\ (4) &A \to (A \to A)&\\ &\:\:\:\: \text{Axiom schema}& \\ (5) &A \to A&\\ &\:\:\:\: \text{1, 2, Modus Ponens}& \\ \end{array}$
The law of excluded middle (in Latin, tertium non datur) is a law of classical logic initially formulated in Aristotle’s works [1]. This law claims that only one statement from $A$ and $\neg A$ is necessarily true and the second one is necessarily false. In other words, a statement may be either true or false, and there is no third option. Formally, $A \vee \neg A$. We leave as an exercise to prove the law of excluded middle in CPC.
The equivalent formulation of the law of excluded middle is a law of double negation elimination, which says that any statement is equivalent to its double negation. That is, if we know that it’s false that $A$ is false, then $A$ is true.
We have told above about classical propositional logic that has quite weak expressive possibilities. The language of classical propositional logic doesn’t include the structure of propositions, but the structure of a statement often plays a huge role in establishing the truth of this statement. For example,
“A sequence of real numbers $x_1, x_2, \dots $ is a Cauchy sequence, if for all $\varepsilon > 0$ there exists a natural number $N$, such that for all $i, j > N$, $ x_i  x_j  < \varepsilon$”.
We have the sentence “for all $\varepsilon > 0$ …”, but we have no way to establish whether this sentence is true or false only looking on connectives in this statement because we have to pay particular attention to the internal structure.
Firstorder logic (FOL) is a much richer formal system than (classical) propositional logic that allows expressing the internal structure of basic propositions in more detail.
The language of FOL extends the language of classical propositional logic. In addition to logical connectives, we have:
Variables range over some domain. Constants denote the special elements of the considered domain. Predicate symbols are symbols that represent relations. Function symbols are signs that denote operations. Note that any propositional variable is $0$ary relation symbol and any constant is $0$ary function symbol. In other words, we don’t need to define propositional variables and constants in the firstorder language explicitly.
We build the firstorder formulas as follows:
A firstorder signature is a pair $\Omega = \langle Fn, Pr \rangle$, where $Fn$ is a set of function symbols and $Pr$ is a set of relation symbols.
Definition 4 (Terms)
Definition 5 (Formulas)
Let us write down the definition of a Cauchy sequence as the firstorder formula: $\forall \varepsilon : \exists N : \forall i : \forall j : ((i > N \land j > N) \to ( x_i  x_j  < \varepsilon))$ where $>$ (“greater than”) is a binary relation symbol, $$ (“subtraction”) is a binary function symbol, $$ (“absolute value”) is an unary function symbol.
More briefly: $\forall \varepsilon : \exists N : \forall i, j > N : ( x_i  x_j  < \varepsilon)$. where $\forall i, j > N $ is a short form for $\forall i \: \forall j \: ((i > N \land j > N) \to \dots)$.
We define firstorder logic axiomatically as firstorder predicate calculus:
Definition 6 (Classical firstorder predicate calculus)
Here, a proof is a finite sequence of formulas, each of which is an axiom, or follows from the previous formulas by inference rules (Modus Ponens and Bernays’ rules).
Note that $\exists x \: A$ is equivalent to $\neg (\forall x \: \neg A)$ and $\forall x \: A$ is equivalent to $\neg (\exists x \: \neg A)$. Thus, quantifiers are mutually expressible in classical firstorder logic.
Constructive (or intuitionistic) mathematics is a field of mathematical logic that arose at the beginning of the 20th century. This direction was founded by Dutch mathematician L. E. J. Brouwer to provide an answer to the paradoxes of naive set theory such as Russell’s paradox [4]. Brouwer claimed that obtained paradoxes are the evidence of the fact that classical mathematics and its foundation are unsafe.
Brouwer and his followers expressed misgivings about ways of reasoning on mathematical objects and their introduction [5]. For instance, intuitionists widely discussed the nature of existence [6]. Mathematics is full of examples of socalled pure existence theorems, i.e. theorems claiming the existence of an object with some desired property but proved without any explicit presentation of this object. We consider the simplest example:
Theorem 1
There exist irrational numbers $a$ and $b$ such that $a^b$ is a rational number.
Proof Let us consider the number $\sqrt{2}^{\sqrt{2}}$. If $\sqrt{2}^{\sqrt{2}}$ is a rational, then $a = b = \sqrt{2}$. If $\sqrt{2}^{\sqrt{2}}$ is an irrational number, let $a = \sqrt{2}^{\sqrt{2}}$ and $b = \sqrt{2}$. Thus $a^b = (\sqrt{2}^{\sqrt{2}})^{\sqrt{2}} = \sqrt{2}^{\sqrt{2} \cdot \sqrt{2}} = \sqrt{2}^2 =2$, which is a rational number. $\Box$
Such reasoning is unacceptable from an intuitionistic point of view because we did not find those numbers. We just established two alternatives and had no reason to choose one of them. This proof is a proof of existence without any provision of clear evidence for the specific property. Such proofs are often based on the law of excluded middle ($A \vee \neg A$) as in the example above. But this proof is classically valid since any statement is either true or false.
This critique has led to the rejection of the law of excluded middle. Moreover, logical connectives and quantifiers have become to be understood quite differently. A statement is proved if we have some explicit construction that solves some desired mathematical problem. Logical connectives are understood as follows. We will use Haskell notation:
(a, b)
is an ordered pair (x, y)
, where x :: a
and y :: b
. In other words, if we need to prove both statements, then we need to prove each of them;Either a b
is either Left x
or Right y
, where x :: a
and y :: b
. If we are looking for proof of some disjunction, it means that we must prove at least one of members of this disjunction. ;a > b
is a function f
such that for all x :: a
, f x :: b
. A proof of this implication means that we have a general method that reduces any proof of b
to the proof of a
;¬ a
is a proof of a > Void
, where Void
is an empty type.Logically, Void
denotes the absurdity, the statement that has no proof, for instance, $0 = 1$. In other words, if we need to prove the negation of a
, it means that we should show that any proof of a
leads to the contradiction. For example, $4 = 5 \to 0 = 1$ is equivalent to $\neg (4 = 5)$.
Typetheoretically, Void
is a type of contradiction and has no values as far as the contradiction is not provable (if our system is consistent). Thus, a > Void
may be considered as a type of function with an empty range of values.
In Haskell, nontermination and exceptions inhabit all types including Void (e.g. loop = loop :: Void
or exc = undefined :: Void
), but we’ll be working in a subset of Haskell without these problematic constructs.
Such way of the interpretation of logical constants is called BrouwerHeytingKolmogorov semantics (BHKsemantics) [7].
As you could see the proof in the example above is not valid within the context of BHKsemantics. Firstly, we did not propose any concrete irrational numbers $a$ and $b$ such that $a^b$ is rational. Secondly, we have used the law of excluded middle $A \vee \neg A$. By the definition, a proof $A \vee \neg A$ is either proof of $A$ or proof of $\neg A$, but classically $A \vee \neg A$ is true without any proof of $A$ or $\neg A$ (it is easy to check that $A \vee \neg A$ is classical tautology via truth tables). The law of excluded middle was rejected by intuitionists for this reason.
We define intuitionistic propositional logic axiomatically as follows. Propositional language and formal proof are defined similarly as above:
Definition 7 (Intuitionistic propositional logic)
In other words, we replaced the last axioms of classical propositional logic $\neg \neg A \to A$ to weaker axiom $A \to (\neg A \to B)$ and obtained intuitionistic propositional logic.
Moreover, there is the following theorem: Theorem 2 (Disjunction property, Gödel [1932], Gentzen [1934], Kleene [1945]) [8] [9] [10]
$A \vee B$ is provable intuitionistically if and only if either $A$ is provable intuitionistically or $B$ is provable intuitionistically.
By the way, the unprovability of the law of excluded middle may be considered to be the consequence of the disjunction property: $A \vee \neg A$ cannot be provable generally, because we have no possibility to establish the provability of this disjunction knowing nothing about $A$. Note that the disjunction property doesn’t work in classical logic, where $A \vee \neg A$ is provable and true regardless of what $A$ is.
Intuitionistic propositional logic may be extended to intuitionistic firstorder logic as follows:
Definition 8 (Intuitionistic firstorder predicate calculus)
Note that, quantifiers are not mutually expressible in contrast to classical firstorder logic.
There is the following theorem about intuitionistic firstorder logic which is wrong for classical firstorder logic:
Theorem 3 (Existence property [9])
If $\exists x \: A(x)$ is provable in intuitionistic firstorder logic with signature $\Omega$. Then there exists some term $t$, such that $A(t)$ is provable.
Existence property theorem is closely connected with the philosophical motivation of intuitionism, so far as we have told before that existence should be proved explicitly from an intuitionistic point of view.
See [11] to read more about philosophical distinctions between classical and intuitionistic logic in more detail.
Also, we note that the statement formulated in Theorem 1 has a constructive proof. Firstly, we propose some definitions and formulate the following theorem that solves Hilbert’s seventh problem:
Definition 9 An algerbaic number is a real number (generally, complex number) that is a root of some nonzero polynomial with rational coefficients. Simple example: $\pm \sqrt{2}$ are roots of polynomial $f(x) = x^2  2$, because $f(\sqrt{2}) = f( \sqrt{2}) = 0$.
Definition 10 A transcendental number is a real number $a$ that is not a root of a polynomial with rational coefficients. The standard examples of transcendental numbers are $\pi$ and $e$.
Theorem 4 (Gelfond–Schneider theorem [1934]) [12] Let $a,b$ be algebraic numbers such that $a \neq 1$, $a \neq 0$ and $b$ is an irrational number. Then $a^b$ is a transcendental number.
Thus we may easily prove the previous theorem without any nonconstructive steps in the reasoning:
Theorem 5
There exist irrational numbers $a$ and $b$ such that $a^b$ is a rational number.
Proof By GelfondSchneider theorem, $\sqrt{2}^{\sqrt{2}}$ is a transcendental number, since $\sqrt{2}$ is an algebraic number. Hence $\sqrt{2}^{\sqrt{2}}$ is an irrational number. But $(\sqrt{2}^{\sqrt{2}})^{\sqrt{2}} = \sqrt{2}^{\sqrt{2} \cdot \sqrt{2}} = \sqrt{2}^2$ is a rational number. Then we take $a = \sqrt{2}^{\sqrt{2}}$ and $b = \sqrt{2}$. $\Box$
In this post, we have made a brief introduction to the logical background to understand better the concepts that will be described in the next parts. We have seen the difference between constructive and nonconstructive proofs considered mathematically, within in a context of mathematical logic.
In the next post, we will introduce the reader to Agda and compare its concepts and syntax with Haskell. Moreover, we will study theorem proving in Agda and understand the implementation of mathematical reasoning in dependently typed programming languages. If you want to stay updated, follow Serokell on Twitter and Facebook!
[1] Smith, R. (tr. & comm.). Aristotle’s Prior Analytics, Indianapolis: Hackett, 1989. [2] Boole, G. An Investigation of the Laws of Thought. London: Walton & Maberly, 1854. [3] Frege, G. Begriffsschrift, eine der arithmetischen nachgebildete Formelsprache des reinen Denkens. Halle, 1879. [4] Russell, B. The principles of mathematics. London, 1903. [5] Brouwer, L.E.J… Collected works I, A. Heyting (ed.). Amsterdam: NorthHolland, 1975. [6] Heyting, A. Intuitionism, an introduction. Amsterdam: NorthHolland, 1956. [7] Troelstra, A.S. Constructivism and Proof Theory. Illc, University of Amsterdam, 2003. [8] Gödel, K. Zum intuitionistischen Aussagenkalkül, Anzeiger der Akademie der Wissenschaftischen in Wien, v. 69, 1932. [9] Gentzen, G. Untersuchungen über das logische Schließen. I, Mathematische Zeitschrift v. 39 n. 2, 1934. [10] Kleene S.C. On the interpretation of intuitionistic number theory, Journal of Symbolic Logic, vol. 10, 1945. [11] Dummett, M. Elements of Intuitionism. Oxford University Press, 1977. [12] Gelfond, A. Sur le septième Problème de Hilbert, Bulletin de l’Académie des Sciences de l’URSS. Classe des sciences mathématiques et na. VII (4), 1934.
]]>One of the main activities at our laboratory is research on passing a laser beam through the atmosphere. How can we use it for transferring data? How will it be changed by air fluctuations?
For data transfer, we use a narrow ray (“data beam”). We also send a probing, wideaperture beam periodically, and by changes in its internal structure (which is really complex) we can recognize the state of the atmosphere along the path. Using that information, we are able to predict distortions of “data beam” and compensate for them by adjusting it.
The main goal of all this is to increase the possible speed of data transferring via an optical beam, which is really low now.
Personally, I develop software for processing the signal coming from a video camera. This signal is generated by a wideaperture laser beam that passes through a long atmospheric path before reaching the camera lens.
The task of the software is to determine the characteristics of the incoming beam (the moments of the image, up to the 4th), as well as the timefrequency characteristics of these quantities. To determine the frequency components more accurately, we use quadratic timefrequency distributions (QTFD), such as the WignerVille distribution.
One of the important issues is that the time for processing the signal is limited. At the same time, the quadratic timefrequency transforms are nonlocal, which means that to process a certain period of the signal it is necessary to completely obtain all samples from this period, which makes realtime processing impossible.
In this case, it is necessary to strive to have time to process the signal while the samples for the next measurement period are being accumulated, which means that the processing time should not be longer than the duration of the measurement (let’s call it a “quasirealtime” task). At the same time, this task is easily separable into many threads.
In addition to the type safety that minimizes, to a certain extent, the number of errors at runtime, Haskell makes it easy and quick to parallelize the calculations. Furthermore, there’s a Haskell library called Accelerate
that makes it easy and convenient to use the resources of the GPU.
An important drawback of Haskell for latencysensitive tasks is the garbage collector, which periodically completely stops the program. This is partially offset by the fact that there are tools to minimize the delays associated with it, in particular  the use of “compact regions”. With it you are able to specify to the garbage collector that a certain set of data should not be deleted while at least one element of the set is used. This allows you to significantly reduce the time spent by the garbage collector to check the data set.
In addition, the laziness of the language proves to be advantageous. This may seem strange: laziness often provokes criticism because of the problems it creates (memory leaks due to uncalculated thunks), but in this case, it is beneficial due to the possibility of dividing the description of the algorithm and its execution. This helps to ease the parallelization of the task, because often there is almost no need to make any changes to the already prepared algorithm, because after describing the calculations, we did not prescribe how or when they should be performed. In case of imperative languages, scheduling computations falls entirely on the shoulders of the programmer  they determine when and what actions will be performed. In Haskell, this is done by the runtime system, but we can easily specify what should be done and how.
In this task, the ffmpeglight
and JuicyPixels
libraries were used for data processing.
The task can be decomposed into several steps: obtaining images, determining the moments of the image, and obtaining statistics on these moments throughout the video file.
data Moment =
ZeroM { m00 :: {# UNPACK #}!Int }
 FirstM { m10 :: {# UNPACK #}!Int, m01 :: {# UNPACK #}!Int }
 SecondM { m20 :: {# UNPACK #}!Double, m02 :: {# UNPACK #}!Double
, m11 :: {# UNPACK #}!Double }
 ThirdM { m30 :: {# UNPACK #}!Double, m21 :: {# UNPACK #}!Double
, m12 :: {# UNPACK #}!Double, m03 :: {# UNPACK #}!Double }
 FourthM { m40 :: {# UNPACK #}!Double, m31 :: {# UNPACK #}!Double
, m22 :: {# UNPACK #}!Double, m13 :: {# UNPACK #}!Double
, m04 :: {# UNPACK #}!Double }
The video image was converted to a list of images of Image Pixel8
type that were collapsed using the appropriate functions. Parallel processing is performed as follows:
processFrames :: PixelF > PixelF > [Image Pixel8] > V.Vector TempData
processFrames cutoff factor imgs =
foldl' (\acc frameData > (acc V.++ frameData)) (V.empty) tempdata
where tempdata = P.parMap P.rdeepseq (processFrame cutoff factor) imgs
This function takes 2 auxiliary parameters (background value and scaling factor), a list of images and returns a vector of values of type TempData
(it stores moments and some other values, which will later be analyzed statistically).
Processing each image is done in parallel, using the parMap function. The tasks are resourceintensive, so the costs of creating separate threads pay off.
Example from another program:
sumsFrames :: (V.Vector (Image Pixel8), Params) > V.Vector (V.Vector Double, V.Vector Double)
sumsFrames (imgs, params) =
(V.map (sumsFrame params) imgs) `VS.using` (VS.parVector 4)
In this case, the result of the calculation is started using the using
function:
using :: a > Strategy a > a
which starts the computation of a given value of type a
using the appropriate strategy, in this case  4 elements per thread, because the calculation of one element is too quick and the creation of a separate thread for each element would cause a high overhead.
As you can see from the previous examples, parallel execution almost does not change the code itself: in the second example, you can just remove VS.using (VS.parVector 4)
and the final result will not change at all, calculations will simply take longer.
After processing the video recording with laser beam oscillations, we get a set of values, for some of which it is necessary to build a timefrequency distribution. A common method is spectrogram construction, but it gives a relatively low resolution — and the higher we make it in frequency, the worse it will be in time and vice versa. You can work around this restriction by using Cohen class transforms:
\[C_x(t, \omega;f) = \iiint_{\infty} e^{i\phi(st)} f(\phi,\tau) x(s+\tau/2)x^{*}(s\tau/2) e^{i\omega\tau}d{\phi}dsd{\tau}\]
Here x (t) is the initial signal and f (φ, τ) is the parameterization function, by means of which a specific transformation is specified. The simplest example is the WignerVille transform:
\[WV (t,\omega) = 2 \int_{\infty}^{\infty}x(t+n)x^{*}(tn)e^{2i{\omega}n}dn\]
A comparison of the spectrogram and the WignerVille distribution is given below.
The second graph is more detailed (especially in the lowfrequency region), but there are interference components in it. To combat them, various antialiasing methods or other transformations of the Cohen class are used. The calculation takes place in several stages:
An example of an algorithm for calculating a more complex transformation of the Cohen class (ChoiWilliams) can be found here.
To calculate these transformations, the Accelerate
library was used. This library makes it possible to use both the central processor (with effective distribution of the load on the cores) and the graphics processor at the request of the user (even without rebuilding the program).
The library uses its own DSL, which is compiled into LLVM code at runtime, which can then be compiled into both the code for the CPU and the code for the GPU (CUDA). The computational cores formed for the graphics processor are efficiently cached, preventing recompilation in runtime unnecessarily. Thus, when one performs a large number of the same tasks, the code compilation in runtime occurs only once.
The DSL of Accelerate
is similar to the DSL of REPA
(Regular Parallel Arrays library) and rather intuitive, the values of the type Acc a
are in reality code that is generated in the compiletime (That is why, when trying to output to GHCi a value of the type, for example, Acc (Array DIM1 Int)
, it will output not an array of integers but some code to calculate it). This code is compiled and run by separate functions, such as:
run :: Arrays a => Acc a > a
or
runN :: Afunction f => f > AfunctionR f
These functions compile the code (written in acelerate’s DSL) with applying various optimizations, such as fusion
.
In addition, the latest version has the ability to run the compilation of expressions (using the runQ function) at Haskell compile time.
Example of the WignerWilly transformation calculation function:
...
import Data.Array.Accelerate as A
import qualified Data.Array.Accelerate.Math.FFT as AMF
...
  Wignerville distribution.
 It takes 1D array of complex floating numbers and returns 2D array of real numbers.
 Columns of result array represents time and rows — frequency. Frequency range is from 0 to n/4,
 where n is a sampling frequency.
wignerVille
:: (A.RealFloat e, A.IsFloating e, A.FromIntegral Int e, Elt e, Elt (ADC.Complex e), AMF.Numeric e)
=> Acc (Array DIM1 (ADC.Complex e))  ^ Data array
> Acc (Array DIM2 e)
wignerVille arr =
let times = A.enumFromN (A.index1 leng) 0 :: Acc (Array DIM1 Int)
leng = A.length arr
taumx = taumaxs times
lims = limits taumx
in A.map ADC.real $ A.transpose $ AMF.fft AMF.Forward $ createMatrix arr taumx lims
Thus, we are able to use the resources of the GPU without a detailed study of CUDA, and we obtain type safety that helps us cut off a large number of potential errors.
After that, the resulting matrix is loaded from the video memory into RAM for further processing.
In this case, even on a relatively weak video card (NVidia GT510) with passive cooling, calculations take place 1.52 times faster than on a CPU. In our case, the WignerWilly transformation for 10,000 points occurs faster than in 30 seconds, which fits perfectly into the notion of quasirealtime (and this value can be much smaller on more powerful GPUs). Based on this information, the original laser beam will be corrected to compensate for atmospheric pulsations.
In addition, results of this timefrequency distributions are permanently saved to disk in text form for later analysis. Here I was faced with the fact that the doubleconversion library converts numbers directly to Text or Bytestring, which creates a large load in the case of huge data sets (10 million values or more), so I wrote a patch that adds conversion to Builder values (for Bytestring and Text), and this accelerated the speed of storing text data by 10 times.
In the case of a large number of small pieces of data, they are moved to the compact region if it is possible. This increases memory consumption, but significantly reduces delays associated with the operation of the garbage collector. In addition, the garbage collector is usually started in a single thread, because the parallel garbage collector often introduces large delays.
Methods of accurate timefrequency analysis are very useful in many areas, like medicine (EEG analysis), geology (where nonlinear timefrequency analysis is used for a long time), acoustic signal processing. I even found an article (and it was really useful) about using quadratic timefrequency distribution to analyze the vibrations of an engine.
Problem of timefrequency distribution has not been solved for now, and all methods have their own advantages and disadvantages. Fourier transform (STFT) with sliding window shows you real amplitudes, but has a low resolution, WignerVille distribution has an excellent resolution, but it shows only quasiamplitude  value, that can show where the amplitude is higher and where is lower and has high level of interference crossterm. Many QTFDs are trying both to combat the interferences and not lose resolution (a difficult task).
Regarding the Accelerate
library, I can say that it was hard at the beginning to avoid nested parallelism, which is not allowed (and unfortunately is not detected at compile time).
I published the first version of this software as windowvilleaccelerate
package. Now I’ve added ChoiWilliams and BornJordan distributions. In the future, I want to add some more distributions and publish it as qtfdaccelerate
package.
I am pleased to welcome you to the first article of our blog. Cue the fanfarade, roll out the carpets, while I’ll tell you about Serokell, how we conceived the idea to create our blog, and what articles you might read there in the future.
3 years ago, Serokell was created by Arseniy Seroka and Jonn Mostovoy, two guys who met in #nixos IRC channel.
Since then, Serokell has grown into a vibrant 50person remote company, gone through the launch of Cardano SL, participated in several other blockchain projects, built an exchange, and we’re not even close to stopping. The education and recruiting project we have been working on, Disciplina, has gone to alpha recently, and it features our first venture into Haskell desktop applications, the Ariadne wallet.
For all this time, the uniting value of the company has been education. We have budget allocated for research and content creation specifically, and, what is more, I am frequently astonished by the amount of work my colleagues put in to help others. When I decided to get into Haskell and cryptography, I was showered with resources until I couldn’t get up.
The gains from putting education first are significant. As a company we now possess a wide, everexpanding range of knowledge in topics like functional programming, computer science, math, and, as I have seen from our Slack, lowcarb diets (but we won’t write a lot about those). Properly packaged, these resources could be helpful to large audiences. After realizing that, the creation of a blog felt like an obvious next step.
Our blog will be about everything we come in contact in our work life: our projects (Disciplina, Ariadne, and those to come), DevOps, Nix, and, of course, FP. We have benefited significantly from using FP both in production systems and small opensource projects; our next goal is to prepare the grounds for functional programming to dominate the world.
The first post is from Rinat Stryngis, one of our new Ariadne developers. It’s about signal processing, and involves lasers, physics, and of course, Haskell:
If you want to stay informed about our next blog posts, follow us on Twitter – we plan to publish a post every week from now on. See ya!
]]>