Algebras and Laws
Algebras in Haskell
The importance of taking advantage of mathematical laws when programming
Monad
Monad’s `join` (flattening structure) in action
Monad as a generalization of concat, with the ability to collapse/merge structure
Difference in using `Applicative` or `Monad` functions in sequences
Aris Goudouras
This is probably the difference between using `apS` and `bind` in fp-ts.
Binding in the context of Monads
Kleisli Composition (that accounts for structure) in Monads
`mapM` as a special case of `traverse`
Parsing: A Case Study for Monads Usage

Assuming this parsing function that parses fractions from a string:

What’s the benefit of implementing it using monads?

How does this align with the fact that the Parser constructor takes just a function?

Aris Goudouras
Note that `decimal`(the first parser in the chain) is a `Parser`, which encapsulates a function that takes a `string` as input. This aligns with what the expected input type is in our case.
Monad Transformers
Leveraging the concreteness of the known monad type as the essence of Monad Transformers
A simple monad transformer: `IdentityT`
When you are trying to explain what a monad is to a Haskell programmer, how do you explain? It’s like, well, there is the Maybe Monad. Well, okay. And there’s the List Monad. Okay. And there’s the Continuation Monad. It’s like your fingers sticking out of water, right? Each finger is separate. Where is the hand? So, the way that I finally tried to explain monad is that it’s in composition. You are trying to compose things and there is this new way of composing things that is different. It’s sort of like composing functions, right? But these are not simple functions. These are functions that return some funny stuff, right? And how do you compose functions that return funny stuff? They accept normal stuff, but they return something that’s weird. […] There is something miraculous that happens with monads. The question is like, how come that all these weird effects that you can think of, they have all the explanations in terms of functions returning some functor? Why is it so? Whether it’s IO or maybe or list. Bartosz Milewski, https://haskell.foundation/podcast/30/, 32:07
Traversable
`sequenceA`: flipping layers of structure around
`mapM` as a special case of `traverse`
When to use `Traversable`
Functional Programming - Paradigm & Mental Models
Why Functional Programming
We can generalise this idea of being forced to handle the failure cases by saying that Haskell makes us write total functions rather than partial functions. This means that we have to handle the entire input domain rather than only part of the input domain, otherwise the compiler will complain at us and, sometimes, point-blank refuse to give us a programme. https://www.gtf.io/musings/why-haskell
FP and Dependency Rejection
“Dependencies are, by nature, impure. They're either non-deterministic, have side-effects, or both. Pure functions can't call impure functions (because that would make them impure as well), so pure functions can't have dependencies. Functional programming must reject the notion of dependencies. Obviously, software is only useful with impure behaviour, so instead of injecting dependencies, functional programs must be composed in impure contexts. Impure functions can call pure functions, so at the boundary, an application must gather impure data, and use it to call pure functions. This automatically leads to the ports and adapters architecture.”
“A common pattern for Haskell: First, gather data from impure sources. Second, pass pure data to pure functions. Third, take the pure output from the pure functions, and do something impure with it.”
Great article describing why dependency injection usually makes functions impure due to impure dependencies.
Aris Goudouras
Lists advantages in the end of this video. Effect polymorphism is a powerful idea.
Declarative vs Imperative / Global vs Local / Denotational vs Operational Approach in Problem Solving
The way I understand functional/reactive programming is that it treats all input as it were pre-existing. So, instead of thinking of: okay, now the user moved the mouse or clicked or pressed a key and we have to react to it and have handlers for it and so on, we think like this: okay, here’s a list of all actions that the user will do, so it sort of says “I know everything about the universe, I know all the future”, this is the list of mouse clicks from now till the program terminates, this is the list of keys that the user will press, okay, here is a list of input from the network that will come, okay? Here, go and calculate something for me. And you just go and take this list and say: okay, I will apply a function to a list like `fmap`, or I can do a fold on this list, or I can merge two lists, stuff like this - you just work on the whole list as if it existed. Of course it doesn’t exist at the moment the program starts running but it doesn’t matter. It’s like, if you tell it ‘’do something for every digit of pi”, does it have every digit of pi? […] it doesn’t have everything at the same time. […] in a lazy language like Haskell we don’t care that we don’t have the whole list. Conceptually we do have the whole list. When time comes to get the next element, well maybe we’ll block / the system will block waiting for user input, but we don’t care: we just have a list and operate on the whole list […]. Bartosz Milewski, https://youtu.be/3XTQSx1A3x8?si=VXcQVdOJ6koJ16oa&t=1904
Don’t think about how things are implemented in hardware. What computers are is just one of the many models of computation. We shouldn’t get attached to it. You can perform computations in your mind, or with pen and paper. The physical substrate is irrelevant to the idea of programming. Milewski, The Dao of Functional Programming, p. 1
[…] when we write code in what now I’m calling programming only language – okay, what we’ll call programming language, I’m now calling a programming-only language, meaning it can only talk about the surface and only talk about the implementation, so it misses the heart of the matter and the reusability. I think that the purpose of my code is to take some data and transform it into other data. That’s, of course, a functional perspective, but even when you’re doing imperative programming, you’re still doing that just in a sequential way. Start with some data, you end up with some data. But I don’t think that’s really what computation is about. I think that’s what the surface layer is about. I think that’s what the executable is about. But the whole process of why am I generating this computation and why am I happy with it, or should I be happy with it, and if so, why, that is not about transforming. The purposeful computation, or meaningful computation, is not about transforming data into other data. It’s about transforming meaningful data into other meaningful data and doing it meaningfully. And what I mean by that is that when I write code to do, say, binary addition, there’s a reason for that code. It is not just because I have a vector of bits and a vector of bits. It’s because I have a particular interpretation of that vector of bits. I have a particular way in which I’m interpreting that vector of bits as a natural number. So my job in constructing computation is not to transform data to data. It’s to transform, in a way, an interpretation. So there’s a type of data, and there’s a type of its meaning, and then there’s a function from the formative ladder, from the representations to the meanings. That’s the domain. That’s what I’m transforming. That’s what I’m taking in, is meaningful data, is the data type interpreted in a particular way. And that’s what my computation is generating. That’s the whole thing. That’s the higher-dimensional thing that has the purposefulness in it and has the ability to then have the logical proof in it as well. When I’m just reflecting, I look back, and when I’m programming, of course, I have an interpretation in mind. Everybody has an interpretation of mine, but what often happens is maybe I’m working with you and we have different interpretations in mind and our programming language cannot capture either one of those. And so it cannot notice the difference. You write your code. I write my code. They’re both correct. We put them together. It’s not correct. Why not? Because we didn’t say what we meant. We said, “I’m transferring bit vectors to bit vectors. […] So our programs miss their purpose, and the languages cannot capture that purpose, and therefore they cannot support us in fulfilling it. Especially we, who like typed languages, for instance, appreciate that the program is helping us, supporting us in fulfilling our intentions. https://haskell.foundation/podcast/62/ (0:52:06)
[…] there's a mental bottleneck which is the von Neumann paradigm. The von Neumann style in the in the title of this during award lecture is exactly this. And it forces to think small. We cannot think clearly and profoundly and deeply and gain insight within the von Neumann model of programming. The von Neumann programming is still the dominant model of programming. Operational semantics, which is mainly what PL people are taught is essentially mirrors this weakness. it's an essentially sequential and mechanistic way of thinking which is not rooted in beauty. It is sort of fundamentally sequential, whereas computation isn't. If if you think it is, it's because you've been programmed right with this deep misunderstanding. […] So anyway, this turning a word lecture for me is really about breaking out of the Von Neumann bottleneck, the spiritual, mental, cognitive, right, philosophical bottleneck, and and and getting back into this programming. https://open.spotify.com/episode/4jPTxGfaUa84IqsQceKywL (1:04:55)
There's one meta idea that applies, I think, to everything everybody should ever do. Certainly applies to everything I do, which is you should understand what you're talking about in the simplest, most elegant compositional terms. That is denotational semantics. That's what I call giving, having a denotation. So all your types have a mathematical model. It's simple and of course it has to be precise. Otherwise simplicity is an illusion. OK, so it's as simple as it can possibly be. And then every operation that you provide can be fully explained in terms of that model and that model has nothing to do with the implementation at all. OK, so so it it it it's a very severe discipline that forces you to to think in the clearest well, it encourages you to think in the clearest possible way about what you're trying to say, independent of any bias about implementation. That's very hard for people who have been conditioned to think that programming is about making machines do stuff. Because any anything. You know, if you think computation and if you've been taught operational semantics and operational thinking, and like, like linear algebra is about arrays and machine learning is about neural Nets, all these like arbitrary bad choices about particular representation, or programming is about sequences of effects, all these harmful ways to look at things. https://open.spotify.com/episode/4jPTxGfaUa84IqsQceKywL?context=spotify%3Ashow%3A1gE06aXNjm4BEcwbsow0Zu (1:56:43)
Yeah, so so I thought I got to think about 3D graphics in a a functional way, a compositional way. A way of what it means, not how it not what it does. OK. And that's the difference. The graphic systems are almost always implemented the way almost everything else is implemented. But especially graphics it seems is is if they are a graphics program is a sequence of commands, maybe structured, you know sequence of commands, the words like you might have loops and subroutines, what they used to call them, right. So procedure definition, so on. But they're essentially just ways of of organizing sequences of instructions that have side effects, right? But instead of the system memory, they're going to be the video memory, right? And the pattern that we want in memory after we're done as an emergent effect is one that looks like something to you like some geometry or something like that. All right. But that model is a terrible model if what you care about is clarity. Because it's extremely hard to reason about in exactly the way John [Backus] has said that that because of imperative programs, that is programs that are about sequences of effects and which have no nice mathematical properties, therefore they cannot give you any powerful insights. So what I did instead is: I said OK, what are graphics programs for? In other words, what are the meanings they are trying to convey? So I want to make a graphics program. It's because I want to help somebody look through a tool at something they are interested in. So I'm trying to invent a telescope for something. What is that something? And that's the important thing. And the secondary thing is how can I make a device that helps you look through it? How can I use a computer? How can I make a device you can look through to see what you want to see and then how can I make a language describe what you want to see? That's very different from the dominant design paradigm, which is how can I make a language or a library so that you can describe a sequence of effects right, that emerge that have this, you know eventually, you know that cause a billion effects whose whose overall aggregate result is to draw a picture, right? Because it because it messes up these two things. The essence of what it is that the the idea you're trying to describe and give somebody a a a opportunity to look at and then how the machine works and what are the kind of physics in a way what are the effects. So I divided those two things and instead of a language of rendering or even of affecting a data structure that would get rendered I designed a language with a library of vocabulary, typed, composable vocabulary of geometry and colors. I mean it's like essentially what Euclid did within a modern a more modern vocabulary, a more modern linguistic framework, right? So a collection of types and typed operations on three-dimensional things. […] https://open.spotify.com/episode/4jPTxGfaUa84IqsQceKywL?si=168fed48e7fe47fd (1:32:19)
Denotational Design Principles
So in denotational design, you say for most important thing is, what do I want to talk about? Ok, I'm going to talk about these linear maps. And then the second question is, what is a language? What's a vocabulary in which I can express these things? I can build them up. I combine them, build them out of nothing, all these things; it's these operations. What algebraic abstraction already exists? And we've already proven things. We've already learned it for some other reason. Then the next step is now we're going to say, OK, so then now I kind of understand the mathematical realm, the specification. That's the story I'm going to tell the user. Now what about the implementer? What I'm going to tell the implementer, which may be me, maybe somebody else. What I'm going to tell them is I want you to, I want you to find a way of implementing the first story. And there are two kinds of choices to make. One is the representation you're going to use for the things of interest. And the other is how you're going to implement the operations. Which operations? The one that the first model already wants to talk about. Now, how do you know how to implement them correctly? Well, there's two questions. What does it mean to be correct? And then how do you get there? What it means to be correct is always the same. It's a homomorphism. OK, So what homomorphism you have to talk about? How does the representation mean the things we're talking about? if you don't know what the representation means, you can't possibly address the question of how to implement operations on them because it's only the meaning that guides them. You have to know what the up the arguments mean and what the operation means.
Aris Goudouras
What is interesting here is how does it compare to Domain Driven Design. There, again, we need a shared vocabulary, but it’s closer to how the domain expert understands the domain. Here Elliott argues for a vocabulary that’s in the mathematical realm. Maybe in the cases he has in mind the language of mathematics is indeed the language of domain experts. But if not, we do have an important decision point. Do we express the problem in the language of mathematics & categories or using a vocabulary that’s closer to how domain experts communicate things?
Aris Goudouras
Milewski makes an interesting association between the declarative vs imperative way of thinking and the global vs local way of attacking problems in physics, as described by Feynman.
Aris Goudouras
Conal Elliott refers to a John Backus lecture (his Turing award lecture and the corresponding paper) where we renounces imperative (Von Neumann) style programming and argues for functional programming (or something close to it), although he received the award because of his work in Fortran - an imperative language. Elliott also says that Von Neumann’s style was mean to be a shortcut / experiment for pragmatic reasons and then we got stuck with it as the dominant paradigm. (starts around 54:28)
Aris Goudouras
Interesting discussion on programming paradigms.
Mental models and thinking in Functional Programming
There is one area in which knowing something about category theory actually changes the way you program in Haskell. I think that’s once you learn about algebras and coalgebras, because that gives you this level of abstraction in which you stop doing things using recursion. The first big problem when people learn Haskell or other functional language is recursion, right? Especially when you come from C++ or traditional languages, right? You always write loops, right? And you think in terms of loops, I have to iterate, iterate, right? How do I iterate in Haskell? Well, you do recursion, but recursion is kind of like a weird way of thinking when you have to sort of assume that you already solved a problem and then you have to do one more step to finish it. And it’s not natural. In iteration, you just do one step at a time, and then next step, and then next step, and then step. In recursion, you have to assume that you’ve done it already, right? I haven’t done it yet, but let’s assume that I’ve done it and now I need one more final step, right? So this is hard. But then when you learn about algebras and things like catamorphisms, anamorphisms, all this stuff, you suddenly think, “Oh, I don’t have to think recursively. I can think holistically.” It’s a data structure. I’m looking at this whole data structure and I’m applying a transformation to this whole data structure all at once. And this is so powerful. Bartosz Milewski, https://haskell.foundation/podcast/30/, 27:14
However, much in the same way that music is not a set of instruments, functional programming is not a set of abstractions that we need to learn and memorize. Functional programming is an approach to solving computational problems. Many of the abstractions that you do read about are ways to apply this approach to new problems, or problems that were difficult to solve without reaching for more traditional programming methods. But the essence, the core, of what functional programming is about is mostly unchanged. In the preface to the book Burge writes (emphasis mine): "The main emphasis [of this book] is placed on those parts of the language, namely expressions, that denote the end results sought from the computer, rather than on the instructions which the machine must follow in order to achieve the results. The main thesis of this book is that, in many cases, this emphasis on expressions as opposed to mechanisms simplifies and improves the task of programming.” The above will be familiar to anyone who has had the good fortune of taking a well designed functional programming course. This is the paradigm shift that sometimes makes functional programming hard to learn for those that are used to other methods of programming. Expressions over mechanisms. Jose Calderon, http://jmct.cc/intro/index.html
Even if you are using functions/methods that are usually met in the functional programming paradigm, if you think about the implementation details (e.g. mentally iterating over arrays or worrying about how map executes under the hood), you are in the imperative mindset. With functional programming and a declarative mindset, the focus should be on the intent of the operation applied.
Aris Goudouras
Importantly, it’s not that in functional programming we don’t deal with mechanisms or low-level constructs. It’s the emphasis that changes in how we attack the problem (from the instructions to the end results) that makes the difference.
Aris Goudouras
Interestingly, Burge’s and Calderon’s perspective is much different than Milewski’s, who focuses on the abstractions and category theory part.
Aris Goudouras
I think Burge (and Calderon) here endorse the declarative style manifested in the expressions. But I don’t really understand at this point why FP expressions are declarative in FP (especially with ones like Do notation, we really have the opposite).
Aris Goudouras
Haven’t read this yet but seems relevant

Appears in 2 boards

Folding & Foldable
Folding and Monoid
Folding in lists and its significance
`foldr` specifying the binary associative operation explicitly
`foldr` (right fold) and `foldl` (left fold) in lists
How to write folds
Catamorphisms as the generalization of folds
How `foldl` associates
How `foldr` associates
`foldl` implementation
`foldr` implementation
The “rest of the fold” in `foldr`
Folding as a two-stage process (traversal and folding)
Example of folding usage (Source: https://hackage.haskell.org/package/recursion-schemes-5.2.3#readme)
Pattern-Matching
Prefer non-overlapping patterns
Category Theory and Functional Programming
Objects and Elements
Category Theory and Structure
So what is category theory about? In every other aspect, either mathematics or physics or programming, we always look at the structure of things, right? In order to understand something, we want to decompose it and find what’s inside, right? It’s like you get a watch and you want to see like, how does it work? Why is it ticking, right? So you open it, you find the little parts, and then you decompose it into tiny little bits. Maybe you decompose it into atoms and then quartz, depending on how far you want to get to understand it. And category theory is very different from this. In category theory, you say I want to learn about this object, but I’m not going to look inside of this object at all. This object is black box. It’s a point. It has no structure. The structure of this object is given by how it interacts with other similar objects. So you always have a whole category of things. You want to talk about groups? Okay, what’s a category of groups? And in the category of groups, you don’t talk about what a group is like, what’s inside the group. No, you say, how is this group related to other groups, right? It’s more about understanding structure. I mean, it’s an amazing thing what category theory does, that it says, “Here’s a structure, it’s just objects and arrows. That’s it.” There’s nothing else. There’s just objects and arrows. And then you can study it and you can discover that these arrows can move in weird ways and combine in such crazy ways. And if you want to describe objects using just arrows, because arrows tell you how they interact with other objects, right? So there is a completely new way of talking about structure by saying how the arrows arrange themselves, right? And there is this stuff, for instance, called universal construction. Most of the things in category theory are defined using universal construction. A universal construction says, in order to understand this object, you have to tell me how it interacts with the whole universe, with every other object, not just a few objects, okay? No, with every object. That’s the universality of it, right? So this is so philosophical. You can’t be more philosophical than that, right? Make me whole with the universe, right? Bartosz Milewski, https://haskell.foundation/podcast/30/, 38:32
A set as a category with no structure
Composition, Application and Identity
Identity
Function application as function composition
Associativity of composition and its importance to problem-solving
Composition and problem-solving
Definition of function/morphism composition
Expression of composition in Haskell, where types and functions form a Category
Category Theory and other Mathematical Theories
Category theory as complementary to set theory
A set as a category with no structure
Category of Sets (Category of Types and Functions)
Modeling programming in the category of sets
An undercategory from the terminal object (coslice 1/C category) conveying set-theoretic types and functions
Category Theory and Thought Processes
Abstraction as discarding information in a function
Aris Goudouras
Not all functions perform abstraction (Milewski gives an example right after of injective functions / monomorphisms, which don’t abstract).
Morhpism/Function Types
Injective Functions / Monomorphisms
Surjective Functions / Epimorphisms
Bijective Functions / Isomorphisms
Isomorphism as the equivalent of object equality in Category Theory
Types
Sum Types
Bool
Enumerations
Either
Maybe
Functoriality/lifting for sum types
Introduction, elimination and computation rules
Product Types
Functoriality/lifting for products
Function Types
Function application as the elimination rule for function types
Functors
Functorial data types
Type constructors related to container & parameterized types
Important: type constructors not to be confused with arrows between objects
Functor as a kind of mapping that preserves structure
Functors must preserve identity arrows
Functors map objects but also arrows between them and therefore preserve structure
Functors must preserve composition
Endofunctors: functors that map a category to itself
Functoriality in Haskell (Functor type class and `fmap`)
Functoriality & Lifting
Type constructors: mapping types to other types
Composition and identity preservation with functors left to the programmer in Haskell
Types of Functors
Endofunctors: functors that map a category to itself
Bifunctors: functors that lift a pair of functions
Contravariant functors: functors lifting arrows that go in the opposite direction
Hom-Functor and Hom-Set
Important Categories for Programming
Category of Sets (Category of Types and Functions)
Modeling programming in the category of sets
An undercategory from the terminal object (coslice 1/C category) conveying set-theoretic types and functions
Opposite categories
Product categories of interest to programming
Endofunctors: functors that map a category to itself
Natural Transformations
Algebras
Goal: A general procedure for generating and manipulating arbitrary recursive types from simpler pluggable components
Recursion Schemes, Algebras and Coalgebras
Catamorphisms in recursion schemes
The use of functors in recursion scheme catamorphisms and anamorphisms
Anamorphisms in recursion schemes
Functors and recursion schemes
The use of functors in recursion scheme catamorphisms and anamorphisms
Idea & intuition behind recursion schemes
Functional programming is all about functions, right? Using a recursive function is the correct approach. Or is it? The more you program in Haskell, the more you realize that you get the most power by considering wholesale transformations of data structures. When creating a Map of pieces, I didn’t write a recursive function over a list — I used a fold instead. Of course, behind the scenes, fold is implemented using recursion (which, thanks to tail recursion, is usually transformed into a loop). But the idea of applying transformations to data structures is what lets us soar above the sea of details and into the higher levels of abstraction. Bartosz Milewski, https://bartoszmilewski.com/2017/12/29/stalking-a-hylomorphism-in-the-wild/
Many recursive functions share the same structure, e.g. pattern-match on the input and, depending on the data constructor, either recur on a smaller input or terminate the recursion with the base case. Another one: start with a seed value, use it to produce the first element of an infinite list, and recur on a modified seed in order to produce the rest of the list. Such a structure is called a recursion scheme. Using higher-order functions to implement those recursion schemes makes your code clearer, faster, and safer. https://hackage.haskell.org/package/recursion-schemes-5.2.3#readme
Important: Separating the how from the what using recursion schemes

Definitions & Tutorials

Discussion: What are good use cases, if any?

Implementation / Libraries