• Kache@lemm.ee
      link
      fedilink
      arrow-up
      4
      ·
      edit-2
      3 days ago

      It’s a container with certain behaviors and guarantees making them easy and reliable to manipulate and compose. A practical example is a generic List, that behaves like:

      • List[1, 2, 3], i.e. (“new”, “unit”, “wrap”) to create, containing obj(s)
      • map(func) to transform objs inside, List[A] -> List[B]
      • first(), i.e. (“unwrap”, “value”) to get back the obj
      • flat_map(func), i.e. (“bind”) to un-nest one level when func(a) itself produces another List, e.g. [3, 4].flat_map(get_divisors) == flatten_once([[1, 3], [1, 2, 4]]) == [1, 3, 1, 2, 4]

      Consider the code to do these things using for loops – the “business logic” func() would be embedded and interlaced with flow control.

      The same is true of Maybe, a monad to represent something or nothing, i.e. a “list” of at most one, i.e. a way to avoid “null”.

      Consider how quickly things get messy when there are multiple functions and multiple edge cases like empty lists or "null"s to deal with. In those cases, monads like List and Maybe really help clean things up.

      IMO the composability really can’t be understated. “Composing” ten for loops via interlacing and if checks and nesting sounds like a nightmare, whereas a few LazyList and Maybe monads will be much cleaner.

      Also, the distinction monads make with what’s “inside” and what’s “outside” make it useful to represent and compartmentalize scope and lifetimes, which makes it useful for monads like IO and Async.

    • Pencilnoob@lemmy.world
      link
      fedilink
      English
      arrow-up
      50
      ·
      edit-2
      5 days ago

      In practical terms, it’s most commonly a code pattern where any function that interacts with something outside your code (database, filesystem, external API) is “given permission” so all the external interactions are accounted for. You have to pass around something like a permission to allow a function to interact with anything external. Kind of like dependency injection on steroids.

      This allows the compiler to enhance the code in ways it otherwise couldn’t. It also prevents many kinds of bugs. However, it’s quite a bit of extra hassle, so it’s frustrating if you’re not used to it. The way you pass around the “permission” is unusual, so it gives a lot of people a headache at first.

      This is also used for internal permissions like grabbing the first element of an array. You only get permission if the array has at least one thing inside. If it’s empty, you can’t get permission. As such there’s a lot of code around checking for permission. Languages like Haskell or Unison have a lot of tricks that make it much easier than you’d think, but you still have to account for it. That’s where you see all the weird functions in Haskell like fmap and >>=. It’s helpers to make it easier to pass around those “permissions”.

      What’s the point you ask? There’s all kinds of powerful performance optimizations when you know a certain block of code never touches the outside world. You can split execution between different CPU cores, etc. This is still in it’s infancy, but new languages like Unison are breaking incredible ground here. As this is developed further it will be much easier to build software that uses up multiple cores or even multiple machines in distributed swarms without having to build microservice hell. It’ll all just be one program, but it runs across as many machines as needed. Monads are just one of the first features that needed to exist to allow these later features.

      There’s a whole math background to it, but I’m much more a “get things done” engineer than a “show me the original math that inspired this language feature” engineer, so I think if it more practically. Same way I explain functions as a way to group a bunch of related actions, and not as an implementation of a lambda calculus. I think people who start talking about burritos and endofunctors are just hazing.

        • Pencilnoob@lemmy.world
          link
          fedilink
          English
          arrow-up
          16
          ·
          5 days ago

          I’m sure someone will be like “um akchuly” to my explanation. But for me it’s good enough to think if it that way.

          I’ve worked in Haskell and F# for a decade, and added some of the original code to the Unison compiler, so I’m at least passingly familiar with the subject. Enough that I’ve had to explain it to new hires a bunch of times to get them to to speed. I find it easier to learn something when I’m given a practical use for it and how it solves that problem.

          • harryprayiv@infosec.pub
            link
            fedilink
            English
            arrow-up
            4
            ·
            edit-2
            5 days ago

            Lovely response! Very cool to see Unison mentioned. Haskell and Purescript are my daily drivers but I have a huge crush on it even though it intimidates me.

            Ps. Unison doesn’t have monads. They are replaced by “abilities”.

        • CanadaPlus@lemmy.sdf.org
          link
          fedilink
          arrow-up
          3
          ·
          edit-2
          5 days ago

          It is, although I’m not sure it’s complete. A list is one kind of monad, despite working like non-mutable linked lists would in any other language. They just happen to behave monadically, providing an obvious and legal interpretation of the monad functions. Going off of OP you might think monads are all Maybe.

          I will say that the concept is overhyped in at this point, at least in Haskell, and there’s a lot of monads available that do what plain functional code could but worse.

      • someacnt@sh.itjust.works
        link
        fedilink
        English
        arrow-up
        2
        ·
        5 days ago

        Great explanation! Though I prefer to regard monads as semicolon simulators. Monads combine actions separated by semicolons together. The combination can be exceptional, logging, multi-output, or whatever.

      • CanadaPlus@lemmy.sdf.org
        link
        fedilink
        arrow-up
        1
        ·
        5 days ago

        That’s a good run down of the “why”. The thing is, there’s way more things that are monads than things that have to be looked at as monads. AFAIK it only comes up directly when you’re using something like IO or State where the monad functions are irreversible.

        From the compiler end, are there optimisations that make use of the monadic structure of, say, a list?

    • CanadaPlus@lemmy.sdf.org
      link
      fedilink
      arrow-up
      8
      ·
      edit-2
      5 days ago

      Whatever Haskell programmers decide to call a monad today. It’s wandered pretty far away from whatever mathematical definition, despite insistences to the contrary.

      (Technically, the requirement is to implement a few functions)