novonine-Note-33398-1

Token ID: 1

ERC-721 1 Transfers

Metadata

{
  "title": "The Blurry Line in Monad",
  "tags": [
    "post",
    "haskell",
    "monad"
  ],
  "summary": "There's a common joke that the rite of passage for every Haskell programmer is to write a \"monad tutorial\" blog post once they think they finally understand with how they work.",
  "sources": [
    "xlog"
  ],
  "external_urls": [
    "https://logs.novonine.space/monad-confusion"
  ],
  "date_published": "2022-10-04T01:01:06.917Z",
  "content": "There's a common joke that the rite of passage for every Haskell programmer is to write a \"monad tutorial\" blog post once they think they finally understand with how they work. There are enough of those posts out there, though, so I don't intend for this to be yet another monad tutorial. However, based on my learning experience, I do have some thoughts on _why_ people seem to struggle so much with monads, and as a result, why so many of those tutorials exist.\n\nAt a high level, the intuition for monads are that they are an abstraction of sequencing in programming. Any computation that involves \"do this, and then do that using the previous result\" can be considered monadic.\n\nFor some types that implement `Monad`, what I think of as \"computational monads\" like `IO` or `State`, this intuition make sense. We tend to think of these types as computations since they represent verbs like \"performing input/output operations\" or \"computing a value while tracking state.\" In this sense, we interpret their monad instances as describing how two of these computations can be sequentially composed together.\n\nI think many Haskell newcomers struggle to understand how monads work comes when trying to apply this intuition to a group of seemingly unrelated types that I'll call \"data monads.\" These are ones like lists and `Maybe`, which we typically think of as representing nouns, namely \"a list of values\" or \"an optional value\". The problem is this: how can these data types be sequenced together? How are they monads?\n\nFundamentally, I think this issue stems from the fact that monads blur the line between _data_ and _computation_, even though we usually think of these two to be distinct concepts. I argue that the trick to understanding data monads is to interpret them instead as computations: as verbs rather than as nouns.\n\nThis blurry line between data and computation, however, is not unique to monads. In fact, it's exactly that blurry line that lies at the core of functional programming: first-class functions.\n\n## Computation as Data\nFirst-class functions, or the ability to represent functions as ordinary values, are a core aspect of the functional programming paradigm. A language with first-class functions allows any function to return another function or to take a function as an argument.\n\nThey also precisely represent the blurriness between data and computation. We normally think of a function as a computation: something that takes some input and computes an output. But with first-class functions, we can instead use functions as data — as objects that may be passed around like a string or an integer.\n\nBut functions are not the only type of computation, or at least, we often work with more complicated kinds of computation like `IO` and `State`. These are types that compute a value while simultaneously tracking other effects outside of the normal input and output of a function.\n\nPart of what makes Haskell's type system so powerful is its ability to encode these other types of computation as data. In fact, if we look at a basic definition of `State`, we see it's really just a generic wrapper over a specific function signature:\n\n```haskell\nnewtype State s a = State (s -> (a, s))\n```\n\nThat is, the type `State s a` is a function that takes an initial state as input, and returns an updated value and state. By wrapping this function in a data type, we can more easily work with the stateful computation as data. That is, we can use it as input or output to a function, or we can wrap it in some other computation, which is what the more practical `StateT` monad transformer does.\n\nIn general, the technique of treating computations as data types is pervasive in Haskell code. It allows one to scope and isolate side-effecting code like `State` or `IO` from pure code and to compose multiple effectful computations together.\n\nSo what _is_ a monad? All monads represent some computation, i.e. a verb or action, that can be sequentially composed together and may carry with it some side effects. This is sometimes unclear, however, since we often work with these actions as if they were ordinary data types.\n\n## Data as Computation\nSo what about those \"data monads\" like lists and `Maybe`? They can be interpreted as computations too, and their `Monad` instances allow us to tap into a rich set of generic tools to work with them as such.\n\nI think an example will help explain this idea better, so let's look at representing data as computation with the `Maybe` monad. As I said earlier, we traditionally think about `Maybe a` as a generic data type that represents either the presence of that data (with `Just a`), or the absence of that data (with `Nothing`).\n\nBut the monad instance of `Maybe` exploits a different interpretation of the same type. Instead, `Maybe a` can be seen as a computation with a side-effect, namely that in the process of computing `a`, the computation may return nothing. If it does, it returns `Nothing`, and otherwise it returns `Just a`.\n\nIf you consider `Maybe` as a computation, then the monad instance for the type may make more sense. Remember that the implementation for the monadic bind operator (`>>=`) tells us how to sequentially execute two computations while threading the inner value along. We can implement monad for `Maybe` like this:\n\n```haskell\ninstance Monad Maybe where\n  m1 >>= m2 = case m1 of\n    Just x  -> m2 x\n    Nothing -> Nothing\n```\n\nThis instance tells us how to string together two `Maybe` computations, `m1` and `m2`: If the first computation returns a value (`Just x`), then the sequence returns the result of the next computation wrapped around that value (`m2 x`). Otherwise, if the first computation returns `Nothing`, then the sequence also returns `Nothing`.\n\nEssentially, this implementation means that a sequence of `Maybe` computations passes the inner value along the chain unless an intermediary computation returns `Nothing`, in which case the whole sequence computes `Nothing`. So while it may be difficult to understand how a piece of data like `Maybe` can be sequenced, if we instead consider `Maybe` as a computation, the question of how to sequence it becomes more intuitive.\n\nThis is of course just one example, and the implementation of bind for `Maybe` is a particularly simple one, but I think it illustrates an important point: when trying to understand the monad instance of a type, first try to understand what kind of computation that type represents, and then tackle how two of those computations may be sequenced.\n\nA similar data-computation parallel exist for the list monad, where we consider lists as representing a non-deterministic computation, or one that explores all possible paths of execution and collapses the values from each. This parallel interpretation of lists may better help understand how to implement its monad instance.\n\n## Conclusion\nIn summary, Haskell allows for and encourages the interplay between data and computation. It's type system enshrines all computations as first-class values, enabling rich representations of computation as data. Meanwhile, monads provide a generic set of tools for working with data types as computations that may be sequenced and chained together.\n\nGrasping these two concepts has been crucial to my understanding of how to work with Haskell's monads, so maybe this can help you as well.",
  "attributes": [
    {
      "value": "monad-confusion",
      "trait_type": "xlog_slug"
    }
  ]
}