└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Haskell 2 | 3 | Hey, everyone! Thank you for taking the time to read my blog post. 4 | I hope you will learn something today and have a happy Christmas! 5 | 6 | I just want to begin by saying a few things about what inspired me to write this post. 7 | I have always been curious to understand the meaning of things, and I always find myself 8 | noticing a lot of patterns and connections to mathematics in the computer engineering/science world. 9 | Being a passionate functional programmer, I was able to experience this evidence more clearly, 10 | but I feel like most people do not respect nor value these patterns. 11 | 12 | I became increasingly concerned about program correctness and specification, 13 | and what tools/methods were there to help one construct reliable/correct software. 14 | Among several, the most recent one I contacted was Denotational Design (DD), which is precisely the 15 | main theme of this blog post. When I first learned about DD, even though 16 | I saw a huge potential in it, I didn't quite understand it, and I took a lot of time and reading 17 | to process it all. Conal Elliott, the author of this method, was very kind and patient, and 18 | answered a lot of my questions, which I have included in this article. Having said that, 19 | I think it's hard for people to understand why you should care about things that Denotational 20 | Design cares about, and even if you do understand, I find it hard to absorb all that this method has to offer. 21 | 22 | There are not a lot of Denotational Design resources available, from which to read and learn on your own, 23 | [and it may make you wonder if the method is really that relevant](https://www.quora.com/unanswered/Could-anyone-explain-the-process-of-Denotational-Design-that-was-invented-by-Conal-Elliott). However, a lot of people I admire, 24 | respect and look up to, understand its importance and are, in certain respects, influenced by its ideas. Apart 25 | from the [original paper](http://conal.net/papers/type-class-morphisms/), one of the few resources is Sandy Maguire's latest book, which I highly recommend if you enjoy this blog post. As you will see, in the end of this 26 | post I present a collection of several other resources about DD, so stick until the end! 27 | 28 | In view of this, in the hope of reaching more people on one of the topics that I consider 29 | very important for every computer engineer/scientist to bear in mind, I decided to write this blog post, 30 | which gives a very simple introduction to Denotational Design in a kind of Q&A format. 31 | Let's hope you enjoy it as much as I do! 32 | 33 | # Introduction 34 | 35 | Denotational Design, developed by Conal Elliott, is an abstract and rigorous design method, that 36 | forces the programmer to _really_ understand the nature of his 37 | problem domain, stepping back and design the _meaning_ of abstraction before implementing it. 38 | If the programmer is not able to correctly describe his abstraction to the machine, 39 | he will introduce bugs, i.e. a leaky abstraction. 40 | Denotational Design therefore gives us the ability to look at the designs and clearly ask whether 41 | or not they are correct. 42 | 43 | There are a lot of different programming languages available, each unique to its paradigm. 44 | While it is possible to apply this method when using whatever language you choose, 45 | purely functional programming languages are those that will be more suitable. "Why?", you may wonder. 46 | Because, as you will see, if you want to be rigorous, meaningful and correct, you need to go through math, 47 | and math is pure and functional. So, by creating abstractions using a purely functional programming language, 48 | you can be very close to the actual specification. Finally, if you have a compiler that can guide you through 49 | the design, pointing out the correct path of what you're trying to build, you'll be far more confident, 50 | productive and successful. 51 | 52 | With that being said, this post will be a very simple introduction to the Denotational Design method, 53 | using Haskell. Hopefully, you can understand the motivation for this kind of methodologies, 54 | as well as why it is important to care about the _meaning_ of programs in order to build non-leaky abstractions. 55 | 56 | # Motivation 57 | 58 | Abstraction leaks are at the very heart of a wrong decision and design. What's an 59 | abstraction leak? 60 | 61 | **A:** Let's use an example outside Computer Science: 62 | 63 | For drivers, cars have abstractions. In its purest form, there is the steering wheel, 64 | the accelerator and the brake. This abstraction hides a lot of information about what's beneath the hood: 65 | engine, cams, timing belt, spark plugs, radiator, etc. The good thing about this abstraction is that we 66 | can substitute parts of the implementation with better parts without retraining the user. 67 | These adjustments improve performance, but the user keeps steering with the steering wheel and uses the pedals to 68 | start and stop. But there are leaks: in an automatic transmission, you can feel that the car loses power for 69 | a moment when the gears are switched; if the engine block is too cold, the car may not start or it may have 70 | poor performance, etc. 71 | 72 | A useful abstraction is one that gives us an understanding of the world that we accept as real. 73 | An outstanding abstraction is one that never reminds you of its falsehood. 74 | A useful abstraction profoundly changes the way you think and behave. 75 | 76 | So, an **abstraction leaks** when you still have to deal with details that are not part of the 77 | abstraction. That can arise when the implementation can't really conform to abstraction, 78 | or when the abstraction exposes too little details and you still have to work with the implementation 79 | specific details to make your program work. 80 | 81 | In software having leaky abstractions is so common that in 2002 Joel Spolsky coined 82 | the ["Law of Leaky Abstractions"](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/) 83 | that states: "All non-trivial abstractions, to some degree, are leaky." In his article 84 | Joel says that the law 85 | 86 | > means that whenever somebody comes up with a wizzy new code-generation 87 | tool that is supposed to make us all ever-so-efficient, you hear a lot of people saying 88 | "learn how to do it manually first, then use the wizzy tool to save time." Code generation 89 | tools which pretend to abstract out something, like all abstractions, **leak**, and the only 90 | way to deal with the leaks competently is to learn about how the abstractions work and what 91 | they are abstracting. **So the abstractions save us time working, but they don’t save us time learning.** 92 | 93 | This last sentence summarizes what to learn about this law and helps 94 | understanding the scope of this presentation. Paradoxically, although we have higher 95 | and higher level programming tools with increasingly better abstractions, becoming a 96 | proficient programmer is getting harder and harder. Producing non-leaky abstractions 97 | is possible if we are rigorous and our starting point is not by itself leaky. Math is 98 | all about abstraction, and it sure isn't leaky. This post aims to show how one is 99 | capable of producing non-leaky abstractions in software, by finding their meaning in 100 | math and then formulating (a) a representation that focuses on performance and (b) operations 101 | on that representation specified (not implemented) by a denotation function that requires that 102 | function to be _homomorphic_ over the designed API. 103 | 104 | # Introduction 105 | 106 | Data types have a central role in structuring our programs, whether checked statically or 107 | dynamically. Adding data abstraction gives us a clean separation between a type's interface and 108 | its implementation. Therefore, the **ideal abstraction** is as _simple as possible_, revealing everything the user 109 | needs while hiding everything else (such as implementation details). 110 | Purely functional programming languages allow the programmer to reason about code in a much more 111 | straightforward (yet correct) way than do imperative programming languages, shifting the focus from _how_ 112 | to _what_ to do. Through purity and referential transparency, 113 | the programmer is able to derive and calculate proofs and efficient programs. If aided by strong static 114 | type systems, the programmer, can discharge a lot of correctness verifications to the compiler. 115 | These are the main strengths of using a functional programming language, like Haskell, when 116 | writing software, i.e., abstraction. If you are familiar with functional programming and 117 | understand its value, you also understand that even with all these features it is hard to 118 | design an interface that suits both the user and implementor without some experience or, 119 | at least, notions of best practice. In the upcoming sections we will see how the Denotational Design 120 | method ultimately leads to data abstraction/interface that connects implementors and users, while 121 | still serving the distinct needs of each. 122 | 123 | # Homomorphism Principle 124 | 125 | Type classes provide a mechanism for varied implementations of standard interfaces. 126 | Many of these interfaces are founded in mathematical tradition, thus having regularity not only of 127 | types but also of properties (laws) that must hold. Type classes in Haskell 128 | are known for having certain (implicit) laws that need to hold, such as the `Functor` or `Monad` type 129 | classes. Haskellers rely on instances of these classes to abide by such laws in order 130 | to reason and write code. 131 | 132 | The main value of laws in algebraic abstractions (“classes” in Haskell) is that they enable correct, 133 | modular reasoning, in the sense that one can state, prove, 134 | and assume properties of many different types at once. On another note, dependently typed 135 | functional programming languages take these laws more seriously and hence achieve better dependability. 136 | This is something that you don't see often in other languages or communities, and it may be the 137 | reason why functional programmers are thought of producing more reliable abstractions. 138 | 139 | The Denotational Design paper, advocates the Type Class Morphisms (TCM) principle, which 140 | we will call the Homomorphism Principle (HP) during this article. 141 | The idea is basically that, for a given type class, _the instances meaning follows the meaning's instance_. 142 | This principle determines the required meaning of each type class instance, 143 | thus defining the correctness of the implementation 144 | 145 | # Denotational Design 146 | 147 | On the previous section I said that the Denotational Design method ultimately leads to 148 | an interface that is able to connect implementors and users, while still serving the distinct 149 | needs of each. 150 | 151 | - What kind of thing is an interface that can connect implementors and users while 152 | still serving the distinct needs of each? 153 | 154 | **A:** Part of the answer is something that we can call "form" (which we can essentially 155 | understand as an API) which consists in the collection of data type names and operations 156 | that work on them. For example the interface of a finite map can be the following: 157 | 158 | ```haskell 159 | abstract type Map :: * -> * -> * 160 | 161 | empty :: (...) => Map k v 162 | insert :: (...) => k -> v -> Map k v -> Map k v 163 | lookup :: (Eq k, ...) => Map k v -> k -> Maybe v 164 | ``` 165 | 166 | By itself the interface fails to serve the needs of the implementor and end-user. 167 | Although it hides implementation details it fails to _reveal_ a suitable substitute. 168 | More concretely: 169 | 170 | - Implementations reveal too much information to the user. Signatures ("forms") reveal too 171 | little; 172 | - An interface is _form without essence_; 173 | - The end-users **care** about the meaning of the names of the operations. 174 | 175 | In the example of the Map, nothing distinguishes that interface from another, besides 176 | syntactically: 177 | 178 | ```haskell 179 | abstract type Shoe :: * -> * -> * 180 | 181 | shoe :: (...) => Shoe a b 182 | littleShoe :: (...) => a -> b -> Shoe a b -> Shoe a b 183 | bigShoe :: (Eq a, ...) => Shoe a b -> a -> Maybe b 184 | ``` 185 | 186 | - What do we mean by "mean"? How can we give meaning/essence to our "form"? 187 | 188 | **A:** Denotational Semantics is an answer. The meaning of a data type is a mathematical 189 | object (Set, function, number, etc.). And the meaning of each operation is defined as 190 | the function that takes the meaning of its inputs to the meaning of its outputs. 191 | 192 | For our Map example, its meaning could be a _partial function_ from `k` to `v`. In this 193 | model, `empty` is the completely undefined function, `insert` extends the partial 194 | function and `lookup` is just function application. 195 | 196 | If we give the same meaning to the `Shoe` data type we can understand that the two 197 | distinct "forms" have the same essence, so we could replace one with the other. 198 | 199 | In HP, type classes are meant to provide not only the "form" but also _part of the 200 | essence_ of an interface's instance. This means that, for example, the `Monoid` type class, defines the 201 | `mempty` and `mappend` operations and those operations need to satisfy the `Monoid` type 202 | class laws. So, what about the **rest** of the meaning of a type class instance? 203 | 204 | **A:** The **rest** of the meaning can be achieved by following the principle "the 205 | instance's meaning follows the meaning's instance". In other words, the denotation is 206 | _homomorphic_. In other words, the meaning of each operation application is given 207 | by the application of the _same_ operation to the meaning of the arguments, 208 | i.e. a type class morphism, preserving the class structure. 209 | 210 | For the `Monoid` instance of the finite map type, the HP tells us that the 211 | meaning of `mempty` on Maps must be the meaning of `mempty` for partial functions, and the 212 | meaning of `mappend` of two Maps, must be the meaning of `mappend` for the partial functions 213 | denoted by those Maps. Of course that, to use this principle, we need to know what `mempty` and `mappend` 214 | mean for partial functions. On an unfortunate note, it happens that `mappend` isn't quite compatible with the 215 | suggested denotation, which leads us to the following conclusion: 216 | 217 | - Sometimes the HP property fails, and when it does, examination of the failure leads 218 | to a simpler and more compelling design for which the principle holds. Denotational Design 219 | by itself isn't able to tell us if it is the denotation that's wrong or if it is the design/"form" that's at 220 | fault. But we should be thankfull however that we are able to notice our unfortunate choices! 221 | 222 | _Homomorphisms_ are the key insight for the HP. A _homomorphism_ is a map between 223 | two structures of the same type, that preserves the operations of the structures. Roughly, 224 | it has this shape/pattern, depending on a given map `f` and operation `opN`: 225 | 226 | ``` 227 | f (a `op1` b) = f a `op1` f b 228 | ``` 229 | 230 | ``` 231 | f (s `op2` v) = s `op2` f v 232 | ``` 233 | 234 | Type class morphisms or homomorphisms specify what correctness of implementation means. 235 | If the homomorphism properties hold, then the implementation behaves like the semantics. 236 | Therefore, users of a library can think of your implementation as if it were the semantics 237 | (even though the implementation might be quite different), i.e. no abstraction leaks. 238 | Basically, this means that if we follow homomorpism specification, the mental model used 239 | by the end-users can be expected to always hold. 240 | 241 | That's the main ingredient for the HP and adopting it might require additional up-front 242 | effort in clarity of thinking, however the reward is that the resulting designs are simple and general, 243 | and sometimes have the feel of profound _inevitability_. 244 | 245 | # Stack Example 246 | 247 | ## Notation 248 | 249 | `⟦ · ⟧` is the denotation function. So, in the light of the previous section, you can 250 | see how the denotation function relates to the `Map` data type, as well as to its "form": 251 | 252 | - `⟦ · ⟧ :: Map k v -> (k -> v)` 253 | - `⟦ empty ⟧ = ⊥` 254 | - `⟦ insert k v m ⟧ = \k' -> if k == k' then v else ⟦ m ⟧ k'` 255 | - `⟦ lookup k m ⟧ = ⟦ m ⟧ k` 256 | 257 | Denotation homomorpism (in pseudo Haskell): 258 | 259 | ```haskell 260 | -- semantic 261 | instance Monoid v => Monoid (Map k v) where 262 | ⟦ mempty ⟧ = mempty 263 | ⟦ ma `mappend` mb ⟧ = ⟦ ma ⟧ `mappend` ⟦ mb ⟧ 264 | ``` 265 | 266 | ## Stack 267 | 268 | In order to be able to write a library that allows the end-user to create and manipulate 269 | stacks, the implementor needs to fully understand what a stack is and what types of 270 | operations that work on stacks make sense. Since stacks and their operations are common 271 | knowledge amongst programmers, let's just dive write into the "form" or API: 272 | 273 | ```haskell 274 | abstract type Stack :: * -> * 275 | 276 | empty :: (...) => Stack a 277 | push :: (...) => a -> Stack a -> Stack a 278 | pop :: (...) => Stack a -> (a, Stack a) 279 | ``` 280 | 281 | Now, what is the essence of our "form"? What's the meaning of a `Stack` data type as well 282 | as its operations? One could think "A stack is a linked list!" or "A stack is a LIFO 283 | queue!", but **what is** a _list_ or a _queue_? What's the meaning of those structures, how 284 | can we capture their essence? We quickly realise that we are not sure how to answer these 285 | questions, and that we **do not** know what is the _essence_ of a stack. 286 | 287 | Note that we could argue that a list is just a programming language primitive, such as 288 | `Array`, `Vector` or `[]`, and that is fine. However, those solutions seem tainted with 289 | details that are not specific to the domain in question, namely implementation details. 290 | 291 | Understanding the problem at hand is the most important and harder part of designing a 292 | software. How can one expect to be able to come up with a correct implementation whilst, at 293 | the same time, offering a nice non-leaky abstraction to the end-user, without having full 294 | comprehension of the problem domain? 295 | Again, this is the most important and harder part of a Software Engineer's job: 296 | understanding the problems so well that you can explain them to uncomprehending computer machines. 297 | 298 | I argue that the simple essence of a `Stack` can be captured by the partial function that 299 | maps natural numbers to elements in the stack: 300 | 301 | ```haskell 302 | ⟦ . ⟧ :: Stack a -> (Nat -> a) 303 | ``` 304 | 305 | The intuition behind this is that `(Nat -> a)` only captures the essence of what I think 306 | makes a stack: a map between natural numbers (positions in the stack) and elements in the 307 | stack. Given this, we have the following denotation on the operations: 308 | 309 | - `⟦ empty ⟧ = ⊥` 310 | - `⟦ push a s ⟧ = \n -> if n == 0 then a else ⟦ s ⟧ (n - 1)` 311 | - `⟦ pop s ⟧ = (⟦ s ⟧ 0, \n -> ⟦ s ⟧ (n + 1))` 312 | 313 | Please note that there will possibly be some potential for change in this denotation, 314 | so please do not be too hung up on this suggestion. The main thing I want you to take away 315 | is that the more time you spend trying to understand the problem, the better a suitable 316 | denotation you can come up with! 317 | 318 | ### Type classes 319 | 320 | Type classes provide a handy way to package up parts of an interface via a standard 321 | vocabulary. Typically, a type class also has an associated collection of rules that 322 | must be satisfied by instances of the class. In Haskell it is convenient if you give 323 | additional operations on your data type such as the ones needed for `Functor` or `Applicative`. 324 | By doing so, you not only discover extra structure for your data type, allowing you to 325 | validate that the denotation you picked is indeed a good one, but also enrich your library 326 | with more power and expressibility. Specially in Haskell a lot of type classes encapsulate 327 | ubiquous patterns which translate to exceptionally well-studied mathematical objects, such 328 | as `Monoid`s, `Lattice`s, etc. By recognizing these universal algebras in our designs, we will 329 | end up with more powerful abstractions. 330 | 331 | #### Functor 332 | 333 | With that being said, it would be useful to make our `Stack` an instance of `Functor`, since 334 | it's a nice thing to offer to the end-user. By using the HP we can see what it 335 | means for a `Stack` to be a `Functor`, since the homomorpism properties must hold: 336 | 337 | ```haskell 338 | -- semantic 339 | instance Functor Stack where 340 | ⟦ fmap f s ⟧ = fmap f ⟦ s ⟧ 341 | ``` 342 | 343 | This means that `fmap`ping a `Stack` can be 344 | understood as `fmap`ing the partial function which denotes it. In other words 345 | `fmap f` applies `f` to every element in the stack. 346 | 347 | Now, `Functor` as 2 laws that need to hold: 348 | 349 | - Identity: `fmap id = id` 350 | - Composition: `fmap (f . g) = fmap f . fmap g` 351 | 352 | Let's see how the laws hold: 353 | 354 | ```haskell 355 | -- semantic 356 | instance Functor Stack where 357 | ⟦ fmap f s ⟧ = \n -> f (⟦ s ⟧ n) 358 | -- = f . ⟦ s ⟧ 359 | 360 | -- Laws: 361 | -- ⟦ fmap id s ⟧ = 362 | -- \n -> id (⟦ s ⟧ n) = 363 | -- \n -> ⟦ s ⟧ n = 364 | -- ⟦ s ⟧ 365 | 366 | -- ⟦ fmap (f . g) s ⟧ = 367 | -- \n -> (f . g) (⟦ s ⟧ n) = 368 | -- \n -> f (g (⟦ s ⟧ n)) = 369 | -- \n -> f ⟦ fmap g s ⟧ n = 370 | -- \n -> ⟦ fmap f (fmap g) ⟧ n = 371 | -- ⟦ fmap f . fmap g ⟧ 372 | ``` 373 | 374 | We could just give a sensible definition for `fmap` first and then see if the laws would 375 | hold. Quickly we'd realise that the `Functor` instance definition for functions is just 376 | function composition `(.)` and, indeed our `Functor` definition for `Stack` is a `Functor` 377 | homomorpism! 378 | 379 | #### Polishing 380 | 381 | Let's simplify our denotation and make the partiality explicit in the 382 | types. This way we'll avoid having error exceptions in our runnable specification, and type 383 | safety is always good to have! 384 | 385 | ```haskell 386 | abstract type Stack :: * -> * 387 | 388 | empty :: (...) => Stack a 389 | push :: (...) => a -> Stack a -> Stack a 390 | pop :: (...) => Stack a -> (Maybe a, Stack a) 391 | 392 | ⟦ . ⟧ :: Stack a -> (Nat -> Maybe a) 393 | 394 | ⟦ empty ⟧ = const Nothing 395 | ⟦ push a s ⟧ = n -> if n == 0 then Just a else ⟦ s ⟧ (n - 1) 396 | ⟦ pop s ⟧ = (⟦ s ⟧ 0, (\n -> ⟦ s ⟧ (n + 1))) 397 | ``` 398 | 399 | This change requires us to revisit our `Functor` instance: 400 | 401 | ```haskell 402 | instance Functor Stack where 403 | fmap f s = \n -> fmap f (s n) 404 | -- = fmap f . s 405 | ``` 406 | 407 | This is not a `Functor` morphism! This failure looks like bad news. Must we abandon 408 | the HP, or does the failure point us to a new, and possibly better, model 409 | for `Stack`? The rewritten semantic instances above do not make use of any properties 410 | of `Stack` other than being a composition of two functors for the `Functor` instance. 411 | So let’s generalize: 412 | 413 | ```haskell 414 | ⟦ . ⟧ :: Stack a -> (Compose ((->) Nat) Maybe a) 415 | ``` 416 | 417 | It may look like we’ve just moved complexity around, rather than eliminating it. 418 | However, type composition is a very reusable notion, which is why it was already defined, 419 | along with supporting proofs, that the `Functor` laws hold. 420 | 421 | #### Calculating an implementation 422 | 423 | ##### Deriving Type Class Instances 424 | 425 | Let's talk about efficient implementations/representations of a `Stack`. Until now all 426 | we've done was to specify the precise denotation that characterises a `Stack`, which was a 427 | partial map from the positions on the stack and the respective elements. Given this 428 | semantics, we actually coded a runnable specification of it, applying the HP 429 | along the way in order to further polish our abstraction and making sure it does not leak. 430 | 431 | There might be cases where the runnable specification suits our needs, performance wise, 432 | however there might be cases where it does not. In those cases it helps to be able to 433 | calculate a more efficient representation without accidentaly introducing an abstraction 434 | leak. For that effect, imagine we have the following type class: 435 | 436 | ```haskell 437 | class IsStack s where 438 | -- mu = ⟦ . ⟧ 439 | mu :: s a -> Stack a 440 | -- mu' = ⟦ . ⟧⁻¹ 441 | mu' :: Stack a -> s a 442 | 443 | -- mu' . mu = id 444 | ``` 445 | 446 | `mu` is our `⟦ . ⟧` semantic function and `mu'` is it's inverse. 447 | 448 | Now, consider the `Functor` morphism property: 449 | 450 | `mu (fmap f s) = fmap f (mu s)` 451 | 452 | Because `mu . mu' = id`, the property is satisfied if: 453 | 454 | `mu' (mu (fmap f s)) = mu' (fmap f (mu s))` 455 | 456 | And because `mu' . mu = id`: 457 | 458 | `fmap f s = mu' (fmap f (mu s))` 459 | 460 | And, _by construction_, `mu` is a `Functor` morphism. Assuming the class laws 461 | hold for `s`, they hold as well for `Stack`. 462 | 463 | If `mu` and `mu'` have implementations, then we can stop here. Otherwise, 464 | if we want to optimize the implementation, we can do some more work, 465 | to rewrite the synthesized definitions. 466 | 467 | ##### List example 468 | 469 | When trying to come up with the essence of a `Stack` we mentioned lists. Let's assume that 470 | for whatever reason (I didn't performed any benchmarks) using lists is more efficient. Then: 471 | 472 | ```haskell 473 | instance IsStack [] where 474 | mu [] = empty 475 | mu (h : t) = push h (mu t) 476 | 477 | mu' (S (Compose s)) = aux s 0 478 | where 479 | aux fn n = case fn n of 480 | Nothing -> [] 481 | Just a -> a : aux fn (n + 1) 482 | ``` 483 | 484 | Now, the equation: 485 | 486 | `fmap f s = mu' (fmap f (mu s))` simplifies: 487 | 488 | ```haskell 489 | -- [ a ] always "Just" values. 490 | 491 | -- fmap f s = mu' (fmap f (mu s)) 492 | -- 493 | -- mu' (fmap f (mu s)) == 494 | -- < case splitting > 495 | -- == { mu' (fmap f (mu [])) 496 | -- { mu' (fmap f (mu (h : t))) 497 | -- < def - mu > 498 | -- == { mu' (fmap f empty) } 499 | -- { mu' (fmap f (push h (mu t))) 500 | -- < def - Stack fmap x2; def - push x2 > 501 | -- == { mu' (const Nothing) } 502 | -- { mu' (push (f h) (fmap f (mu t))) } 503 | -- < always returning nothing = always returning empty list; def - mu' for Just values > 504 | -- == { [] } 505 | -- { f h : mu' (fmap f (mu t)) } 506 | -- < Stack fmap - homomorphism > 507 | -- == { [] } 508 | -- { f h : mu' (mu (fmap f t)) } 509 | -- < mu' . mu == id > 510 | -- == { [] } 511 | -- { f h : fmap f t } 512 | 513 | -- Result: 514 | -- fmap f [] = [] 515 | -- fmap f (h:t) = f h : fmap f t 516 | ``` 517 | 518 | **Exercise**: Calculate implementations for `push` and `pop`. 519 | 520 | # Author's personal insight 521 | 522 | It all comes down to knowing whatever you're going to build/design. 523 | Software developers have come a long way in writing programs without this approach, 524 | simply by having a combination of truly knowing the issue at hand and having the necessary 525 | knowledge and experience about how to communicate their understanding to the computer, 526 | through a programming language. 527 | While the HP doesn't seem to be as practical, it requires a great deal of effort up-front 528 | and it gives beautiful results. 529 | 530 | I'm not an advocate that software engineering is an art and that writing code should be thought 531 | of as being written under some sort of romantic inspiration. For me, Software engineering should 532 | be all about method, precision and correctness, just like every other engineering. 533 | However, I know that there is a lot of flexibility, style and taste required to design programs, 534 | also just like any other engineering. With software, I think we should be original and imaginative when 535 | we come up with various abstractions, and there's definitely no science to come up with a fine, 536 | simple denotation. But once we have one, concentrating on compositionality, 537 | formal properties and precision would significantly improve the quality and reliability of our product. 538 | 539 | Then again, it's all about knowing the problem in question, coming up with a meaningful denotation 540 | and relying on a rigorous method, such as DD, to guide you in the search for the 541 | essence of what you're trying to do. 542 | 543 | # Insight Conal 544 | 545 | - By cleanly separating programming interface and specification from implementation, 546 | connecting the two by semantic homomorpism, one can achieve very elegant, generic, 547 | simple and performant implementations. 548 | 549 | - A goal of the Denotational Specification is to remove all operational/implementation bias 550 | and get to the essential and elegant mathematical ideas. Then formulate a representation 551 | that focuses on performance and operations on that representation specified in a 552 | straightforward and regular way by a denotation function that requires that 553 | that function to be homomorphic over the API/vocabulary. This is what 554 | software/hardware design and implementation is all about. 555 | 556 | - **Software design is the posing of tasteful algebra problems; and software implementation 557 | is the correct solution of those problem.** 558 | 559 | - **Software developers mostly lack these fundamental principles and so cannot distinguish 560 | fundamental choices from inevitable consequences.** 561 | 562 | - The sort of tension between model simplicity and expressiveness has been a source of deep 563 | insights for me. The denotational design discipline brings these tensions to the surface 564 | for examination. While lack of this discipline allows them to continue to be left unquestioned, 565 | I count on raising this tension to help determine whether I’m right or wrong here. Most of what 566 | people accept in programming is just bad taste left unquestioned and with very expensive consequences. 567 | 568 | - If you pick a bad denotation, you’ll get bad results. A good denotation is one that captures the 569 | essence of an idea (“a problem domain” in software design lingo) simply, precisely, and generally. 570 | It clarifies our thinking about the domain, even before we’ve tried to relate it to representations 571 | and implementations. Any operational bias will interfere with these goals. By “bad results” I mean weak 572 | insight, complex implementation proofs and calculations, limited flexibility, and limited capability. 573 | 574 | - Without the denotational discipline, we don't even have the mental lens through which to notice 575 | unfortunate choices. One cannot even see the missed target and so cannot improve one's aim. 576 | 577 | # Q&A Conal 578 | 579 | - The homomorphism requirement is just so that we do not build leaky abstractions, right? 580 | 581 | **A:** I wouldn’t say “just” here. The homomorphism requirement is the specification, 582 | so it’s there to define your intent and thus to establish what correctness of implementation means 583 | (faithfulness to the specification). If homomorphism properties hold, then the implementation behaves 584 | like the semantics, so users of your library can think of your implementation as if it were the semantics 585 | (even though the implementation might be quite different), i.e., no abstraction leak. 586 | 587 | - Should we stick only to one denotational semantic or can we have other when convenient? 588 | 589 | **A:** There may be exceptions to having just a single denotation, but if we do, all conversations 590 | will have to become much more explicit about which denotation. 591 | 592 | We can, and should, however consider several representations all explained in terms of the 593 | single denotation. And then we have a rigorous, common basis for comparison. 594 | 595 | - What if we lack the knowledge about what denotation to use? Or use the "wrong" denotation. 596 | 597 | **A:** Then we cannot design a good API and know what it means to correctly implement it. 598 | Denotation is the one overwhelmingly important creative choice. It takes good taste and 599 | what my math profs refer to as “mathematical maturity”. 600 | 601 | A good denotational model is optimized for precise simplicity, stripped of any 602 | implementation/efficiency (or even computability) bias. 603 | 604 | - **How would you approach a problem for which you weren't sure what denotation would be best?** 605 | 606 | **A:** First, if I don’t know of a denotation, then I don’t understand the thing I’m trying 607 | to program. One cannot program well without understanding. It’s like engineering without 608 | science or physics without math. So, what I do is contemplate and study. Fortunately, 609 | good denotations are much simpler than good implementations and more enlightening. 610 | 611 | - What distinguishes a good denotation from a "bad" one? 612 | 613 | **A:** I compare them using some basic criteria: The denotation must be precise, and adequate 614 | for what I want to express (but usually not the way I or others have been taught to express it). 615 | 616 | - Why care about type class morphisms? 617 | 618 | **A**: I want my library’s users to think of behaviors and future values as being their semantic models. 619 | Why? Because these denotational models are simple and precise and have simple and useful formal properties. 620 | Those properties allow library users to program with confidence, and allow library providers to make radical 621 | changes in representation and implementation (even from demand-driven to data-driven) without 622 | breaking client programs. 623 | 624 | # Finishing words 625 | 626 | Thank you very much for your attention, I hope this blog post was enjoyable to you! 627 | Most importantly, I hope it described the topic of Denotational Design in a simple and 628 | intuitive manner. All feedback is welcome as I plan to keep polishing this article and write more about the intersection of denotational and formal methods. In the last section I have a collection of references and resources about the topic which I recommend having a look, if it peaked your interest! 629 | 630 | Lastly, I just want to thank to the people that are organizing Advent of Haskell for the 631 | spotlight and to the people that took their time to read and review this blog post, in 632 | particular to Conal Elliott. 633 | 634 | # References & Resources 635 | 636 | In this section I gathered a lot of resources and references that I used to write this 637 | post. I hope this can be seen as a contribution for newcomers that just heard about 638 | DD for the first time! It has a lot of discussion on wether this method 639 | is good or not so you can make your own choice! Thank you once again for your attention! 640 | 641 | - [https://stackoverflow.com/questions/3883006/meaning-of-leaky-abstraction](https://stackoverflow.com/questions/3883006/meaning-of-leaky-abstraction) 642 | - [Calculating compilers](https://github.com/conal/talk-2020-calculating-compilers-categorically#readme) 643 | - [Denotational Design](http://conal.net/papers/type-class-morphisms/) 644 | - [Algebra Driven Design](https://algebradriven.design/) 645 | - [Semantics Design](https://lukepalmer.wordpress.com/2008/07/18/semantic-design/) 646 | - [http://conal.net/blog/posts/simplifying-semantics-with-type-class-morphisms](http://conal.net/blog/posts/simplifying-semantics-with-type-class-morphisms) 647 | - [https://poddtoppen.se/podcast/694047404/the-haskell-cast/episode-9-conal-elliott-on-frp-and-denotational-design](https://poddtoppen.se/podcast/694047404/the-haskell-cast/episode-9-conal-elliott-on-frp-and-denotational-design) 648 | - [https://reasonablypolymorphic.com/blog/follow-the-denotation/](https://reasonablypolymorphic.com/blog/follow-the-denotation/) 649 | - [https://lispcast.com/why-do-i-like-denotational-design/](https://lispcast.com/why-do-i-like-denotational-design/) 650 | - [https://ro-che.info/articles/2014-12-31-denotational-design-does-not-work](https://ro-che.info/articles/2014-12-31-denotational-design-does-not-work) 651 | - [https://lukepalmer.wordpress.com/2008/07/18/semantic-design/](https://lukepalmer.wordpress.com/2008/07/18/semantic-design/) 652 | - [http://conal.net/blog/posts/denotational-design-with-type-class-morphisms](http://conal.net/blog/posts/denotational-design-with-type-class-morphisms) 653 | - [https://wadler.blogspot.com/2009/02/conal-elliot-on-type-class-morphisms.html](https://wadler.blogspot.com/2009/02/conal-elliot-on-type-class-morphisms.html) 654 | --------------------------------------------------------------------------------