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 the recent times, functional programming has invaded code bases 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 a 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 non trivial 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 having 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 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.
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 object oriented 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 procesing 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.
The first is this to define a 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.
]]>The first place and $28,750 was taken by this submission. If you have been following our blog, it might look familiar.
Our team of five of the Serokell’s finest submitted two smart contracts for Telegram Open Network:
You can read more about the technical details of the solutions in this blog post by one of the participants, our CTO Kirill Elagin.
The participants from Serokell’s side were: our CTO, one of our teamleads, and three experienced blockchain developers wellversed with Haskell and other functional programming languages.
All in all, we tracked 380 hours on this project including meetings, getting to know documentation, and development.
There were a lot of impressive submissions from other teams as well. TON is one of the more promising projects in the blockchain space, and it is wonderful to see many great minds working on it already.
At the end, we would like to thank Telegram for the experience. In the words of Kirill:
]]>This spirit of a hackathon, close teamwork, the need to quickly dive into a new technology – I think all engineers know how exciting it is.
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.
]]>Telegram Open Network is a relatively new smartcontracts platform developed by the team behind the Telegram messenger. It was announced in late 2017 and first source code was published in September this year. Five weeks ago, they started a competition. In it, developers were asked to either implement a smartcontract or contribute to the platform in one way or another.
After giving it a little consideration, we decided to participate as a company and implement two smartcontracts from the list of organisers’ suggestions. For one of them we chose to use the development tools provided with the TON distribution, for the other one we decided to do what we like doing the most: implement it in a new language built specifically for TON and embedded into Haskell with its incredibly rich type system.
We believe the plan worked out exceptionally well, so we would like to showcase our entries and our approach to smartcontracts and embedded languages. The competition was incredibly fun and engaging and, hopefully, so will be this blog post.
(Update: We have won the largest prize!)
We always try to keep on top of recent developments in the areas that we work in, blockchain being one of them, thus we were already familiar with the ideas from the TON white paper. However, we hadn’t looked at the technical documentation and the actual source code of the platform before, so this was an obvious first step. If you are interested too, you can find the official documentation at https://test.ton.org and in the source repository.
Since the code had been published for quite a while by that time, we also tried searching for guides or summaries produced by users, but, unfortunately, all we could find were tutorials showing how to build the platform on Ubuntu, which was not relevant to us anyway (you will see why in a minute).
The documentation turned out to be very thorough but sometimes hard to read as it was often jumping back and forth between high level explanations of abstract ideas and low level details of their specific implementations. We thought it would be a huge improvement to extract the implementation details into a separate document, as it would not only make the specifications more approachable, but also reduce their sizes. Developers creating smartcontracts for the TON platform do not need to know how the virtual machine represents its stack internally.
Here at Serokell we are huge fans of Nix: all our servers run on NixOS, we build our projects with Nix and deploy using NixOps. It helps us make sure are builds are reproducible and will work on any OS supported by Nix without us worrying about OS or distribution specific aspects of the build.
Therefore, we started by creating a Nix overlay with a build expression for TON. You can find more details if you follow the link, but, long story short, with this overlay compiling TON is as simple as:
$ cd ~/.config/nixpkgs/overlays && git clone https://github.com/serokell/ton.nix
$ cd /path/to/ton/repo && nixshell
[nixshell]$ cmakeConfigurePhase && make
Note that you don’t have to worry about installing the build dependencies, Nix will magically handle everything for you whether you are running NixOS, Ubuntu, or macOS on a MacBook.
Everyone should be using Nix for all their building needs!
The code of smartcontracts deployed to the TON Network is executed by a virtual machine called the TON Virtual Machine (TVM). More complex than most virtual machines, this one provides some quite unconvential capabilities, such as native support for continuations and data references.
TON developers created three (!) new programming languages:
A “payment channel” is a smartcontract that allows two users to send payments to each other offchain, thus saving money (transaction fees) and time (they don’t have to wait for a block to be issued). This way, the payments can be as small and frequent as needed, and the users still do not need to trust each other, as the final settlement is guaranteed by the smartcontract.
After thinking about it for a couple of days, we realised that there was a pretty simple solution to the problem: the two parties can exchange signed messages where each message will, essentially, carry two numbers – the total amounts paid by each of them so far. These two numbers will work as Vector clock in traditional distributed systems and thus will induce a “happenedbefore“ order on the payment transactions, which will allow the contract to resolve any possible conflicts in the end. We played a little with the idea and realised that just one number is enough, however we still decided to keep both for UX purposes; we also decided to include the payment amount in every message merely for UX as well. Without it, if some of the messages get lost on their way, while the total sums and thus the final settlement will be correct, the users wouldn’t notice that something was lost.
In order to verify our idea, we did a quick search. To our great surprise, we did not find a lot of mentions of this simple and elegant payment channel protocol. In fact, we found only two:
Somewhat puzzled we drafted our own specification of this protocol, trying to make it very detailed and focusing on the explanation of its correctness. After a number of iterations it was ready and you are welcome to have a look at it. With the specification at hand, we set off to write the code.
We implemented the contract in FunC and, following the recommendations of the organisers, the commandline tool for interacting with our contract was entirely in Fift. We could have chosen any other language for our CLI, but we thought it would be interesting to try Fift and see how it works for us in this case.
In retrospect, we can say that we don’t really see any good reasons to prefer Fift to other wellestablished and supported languages with good libraries and tooling. Programming in a stackbased language is unnecessarily hard and is especially bad due to Fift’s lack of static types – keeping track of your stack layout requires a lot of effort.
Because of the above, the only justification for the existence of Fift seems to be its role as a host language for Fift Assembler. “But wouldn’t it be a better idea to embed the TVM Assembler into some other language, instead of inventing a new one for this sole purpose?” – you might wonder. Well, we’re glad you asked!
We also decided to implement a multisignature wallet, but we thought that writing another FunC contract would be not that interesting, so we added a twist: our own assembler language for TVM. Just as Fift Assembler, our new language was embedded into another language but we chose Haskell as the host. This gave us access to all the power of Haskell’s static types, and we are firm believers of static typing, especially when working with smartcontracts – an area where the cost of a small mistake can be very high.
To give you an idea of what TVM assembler embedded into Haskell feels like, we have reimplemented the standard wallet contract in it. Before you take a look at the code, here are a couple of important things to keep in mind:
stacktype
annotations here and there in the code. In the original
wallet contract these were just comments, but in our eDSL they are actually
part of the code and are checked at compile time. These can serve as documentation
and as assertions that can help the developer find an issue in case they make
a change in the code and something doesn’t compile. Of course, they do not have
any effect on performance at run time as they do not result in any TVM code
being generated.And now, here is a full reimplementation of the simple wallet in our eDSL:
main :: IO ()
main = putText $ pretty $ declProgram procedures methods
where
procedures =
[ ("recv_external", decl recvExternal)
, ("recv_internal", decl recvInternal)
]
methods =
[ ("seqno", declMethod getSeqno)
]
data Storage = Storage
{ sCnt :: Word32
, sPubKey :: PublicKey
}
instance DecodeSlice Storage where
type DecodeSliceFields Storage = [PublicKey, Word32]
decodeFromSliceImpl = do
decodeFromSliceImpl @Word32
decodeFromSliceImpl @PublicKey
instance EncodeBuilder Storage where
encodeToBuilder = do
encodeToBuilder @Word32
encodeToBuilder @PublicKey
data WalletError
= SeqNoMismatch
 SignatureMismatch
deriving (Eq, Ord, Show, Generic)
instance Exception WalletError
instance Enum WalletError where
toEnum 33 = SeqNoMismatch
toEnum 34 = SignatureMismatch
toEnum _ = error "Uknown MultiSigError id"
fromEnum SeqNoMismatch = 33
fromEnum SignatureMismatch = 34
recvInternal :: '[Slice] :> '[]
recvInternal = drop
recvExternal :: '[Slice] :> '[]
recvExternal = do
decodeFromSlice @Signature
dup
preloadFromSlice @Word32
stacktype @[Word32, Slice, Signature]
 cnt cs sign
pushRoot
decodeFromCell @Storage
stacktype @[PublicKey, Word32, Word32, Slice, Signature]
 pk cnt' cnt cs sign
xcpu @1 @2
stacktype @[Word32, Word32, PublicKey, Word32, Slice, Signature]
 cnt cnt' pk cnt cs sign
equalInt >> throwIfNot SeqNoMismatch
push @2
sliceHash
stacktype @[Hash Slice, PublicKey, Word32, Slice, Signature]
 hash pk cnt cs sign
xc2pu @0 @4 @4
stacktype @[PublicKey, Signature, Hash Slice, Word32, Slice, PublicKey]
 pubk sign hash cnt cs pubk
chkSignU
stacktype @[Bool, Word32, Slice, PublicKey]
 ? cnt cs pubk
throwIfNot SignatureMismatch
accept
swap
decodeFromSlice @Word32
nip
dup
srefs @Word8
pushInt 0
if IsEq
then ignore
else do
decodeFromSlice @Word8
decodeFromSlice @(Cell MessageObject)
stacktype @[Slice, Cell MessageObject, Word8, Word32, PublicKey]
xchg @2
sendRawMsg
stacktype @[Slice, Word32, PublicKey]
endS
inc
encodeToCell @Storage
popRoot
getSeqno :: '[] :> '[Word32]
getSeqno = do
pushRoot
cToS
preloadFromSlice @Word32
You can see the full source code of our eDSL and the multisig contract in this repository. If you got interested in typed eDSLs, you will most certainly like this blog post by one of my brilliant colleagues that goes into way greater depths than I ever could.
First of all, we enjoyed the competition a lot! It gave us an unexpected break from our daily responsibilities (not that we don’t enjoy doing what we do daily, but nevertheless). This spirit of a hackathon, close team work, the need to quickly dive into a new technology – I think all engineers know how exciting it is.
We were impressed by the amount of work done by the TON team. They managed to build a pretty complex and at the same time beautiful system. And, most importantly, it works! However, we are not convinced all of this work was strictly necessary. Being engineers, we can definitely relate to the idea of Fift, a brandnew stack based (that is, in some sense, somewhat esoteric) programming language, but we believe that realworld applications more complex than simple CLI prototypes are beyond its capabilities, and what became Fift Assembler could have as easily been embedded into some other language (like Haskell!). Could it be that the developers had some other applications in mind that would justify the creation of Fift? It is possible, and if they did, we can’t wait to find out more.
The same can be said about FunC. Implementing a new highlevel language from the ground up (they even have their own parser!) is certainly Fun, but we can’t really C the need for it. As a shortterm strategy, the team could have taken an existing smartcontract language and adapted it to emit code for TVM; while in the long run we feel that having an LLVM backend for TVM would be great, as it would allow for a wide variety of source languages.
The above is especially true, given that it is clear that TVM has been designed with very highlevel source languages (Haskell!) in mind. This makes us think that FunC is not meant to be used for actual production code, but is merely a demo, a prototype of a TVMcompatible highlevel programming language, and if this is the case, then it does not make sense to put a lot of effort into it.
Overall, TON feels like a great platform and it surely has potential. There is a lot to be done to make the TON ecosystem flourish, both in terms of using it to implement solutions that require a blockchain infrastructure, and improving the tooling used to implement such solutions; and we would be proud to be part of this endeavour. So, if you think about relying on TON to solve your problem, let us know, we will definitely be able to help.
]]>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 our followers 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. – The source.
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. – The source.
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 are happy to inform you that one of the results of our fruitful cooperation with ITMO is the opening of a machine learning laboratory.
The competition in the field of technological solutions is constantly growing. In order to maintain a confident position in the market for software R&D services, it is always necessary to keep up with the times.
Pursuing the goal of improving our technology products, together with our strategic partner  National Research University  ITMO we have opened a machine learning laboratory.
We are very pleased to apply recent computer science inventions to realcase business, as well as to expand our expertise in machine learning solutions.
In the closest future, we will tell more about the goals and objectives of our activities, but for now, we suggest you subscribe to our Twitter, new updates will come shortly.
]]>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!
]]>