[HN Gopher] Monads and Macros
___________________________________________________________________
 
Monads and Macros
 
Author : ColinWright
Score  : 96 points
Date   : 2021-10-01 21:40 UTC (2 days ago)
 
web link (www.johndcook.com)
w3m dump (www.johndcook.com)
 
| AzzieElbab wrote:
| Macros ! Monads !! Monads via macros !!!
 
| btilly wrote:
| For those who wonder why monads would be like burritos, read
| https://blog.plover.com/prog/burritos.html.
| 
| Though a large burrito as a joke to represent both monads and
| macros is kind of catering to a niche audience.
 
| Ericson2314 wrote:
| Honestly it's posts like this that make me think the HN lisp bump
| is out of control.
| 
| I have no idea what the hell the post is trying to stay, and as
| someone that cares enough about both to repeatedly complain that
| Template Haskell needs to be more like Racket with proper phase
| separation, I didn't learn anything and just see vague oft-
| repeated aphorisms juxtaposed.
 
  | lopatin wrote:
  | https://www.youtube.com/watch?v=ADqLBc1vFwI
 
  | chowells wrote:
  | Does the proposal to allow imports that only apply during
  | template haskell evaluation improve things?
 
    | Ericson2314 wrote:
    | See my comments to https://github.com/ghc-proposals/ghc-
    | proposals/pull/412, where I invoke Racket frequently.
    | 
    | It's the right sort of goal, but we have to go all the way to
    | full phase separation to make it _mean_ something.
 
| dustingetz wrote:
| macros are compilers, monads are interpreters
 
  | celeritascelery wrote:
  | Trying to wrap my head around this. How are monads like
  | interpreters?
 
    | dustingetz wrote:
    | google "monadic interpreter"
    | 
    | One thing monad/bind can do that macros cannot is make
    | arbitrary runtime decisions about what to do next, with
    | runtime information. A macro is constrained by what is known
    | statically in the AST. This is why compiled languages are far
    | easier to optimize (it is merely algebraic rewrites of the
    | AST) whereas dynamic languages have runtime eval.
 
    | chalst wrote:
    | If you look at each as languages for staging computations,
    | what you stage with macros happens before "normal"
    | computation, so likely happens at compile time, while what
    | you stage with monads happens after, so must be deferred to
    | run time.
 
    | Tyr42 wrote:
    | Lets get concrete and make a simple example State monad.
    | 
    | You have access to an extra variable S, with getS and setS,
    | and each of those return values of our StateMonad.
    | 
    | Using these (and >>= aka bind) you can write something which
    | increments the number in the state by one, yes?
    | increment = do s <- getS                        setS (s+1)
    | 
    | and this increment is a value of our StateMonad.
    | 
    | This doesn't run on it's own though, it needs the
    | "interpreter" to run the state monad, and put in a initial
    | value of the state, and maybe take it out at the end.
    | 
    | That's the way you can think of Monads as interpreters in a
    | very rough sense.
    | 
    | Now, you can do fancier things, where you can set up a "free
    | monad", which is basically going to record everything into a
    | syntax tree, and then you really have an interpreter.
    | 
    | http://blog.sigfpe.com/2006/08/you-could-have-invented-
    | monad...
 
| WastingMyTime89 wrote:
| > Few people are excited about both monads and macros; only one
| person that I know comes to mind.
| 
| I don't understand what the article is trying to convey. Everyone
| in the PL community know about monads and macros but they are
| tools and people are rarely excited about tools. The article
| seems to have a strong Haskell user bias.
| 
| For example, both using macro and monads is very common in Ocaml.
| JaneStreet, the company which uses Ocaml the most, uses both
| extensively. Ocaml provides advanced macro functionalities
| (called ppx rewriters) and let-binding overloading to make
| writing monadic code easier. There is no opposition between these
| two functionalities.
 
  | isaacimagine wrote:
  | As a compiler engineer, I get excited about tools. I think it's
  | important to use mathematical foundations in a way that is both
  | intuitive and transparent to the end user.
  | 
  | Aside from monads and macros, OCaml remains a great example in
  | this area because of its support for type inference and type
  | annotations; on the surface, the code you write is mostly
  | 'untyped,' save for at function boundaries and data
  | definitions. Much how like monads and macros ultimately
  | compile, so will correct typed and untyped code.
  | 
  | I'm not sure how clear I'm being, but in essence: tools should
  | be as lispy on the surface as possible (for ease of
  | prototyping), while being built on haskelly* foundations (for
  | correctness). There is no one silver bullet, it's about finding
  | the right balance for the issue at hand: and as a tool, you
  | should leave that decision to your end-users.
  | 
  | *I use lisp and haskell to mirror the article, but this is true
  | of any tool, imperative or functional, language or otherwise.
 
    | Zababa wrote:
    | Speaking about OCaml too, OCaml throws away the types after
    | the typechecking, so in a way you could even argue that the
    | types themselves are a form of macros over the untyped lambda
    | form, which sounds pretty close to Lisp. The dichotomy of
    | Lisp and Haskell is a false one.
    | 
    | Maybe the dichotomy would be better as "static" vs "dynamic",
    | but about the language/runtime, not the typing. For example,
    | in Lisp, you have REPL-driven development, where you know
    | errors will happen but you can easily correct them, while in
    | Haskell you try to minimize the errors with the help of the
    | compiler and once the system is done you usually have to
    | recompile it.
 
      | isaacimagine wrote:
      | To that end, there are type systems in some languages
      | (turnstile in racket comes to mind) that are implemented
      | entirely using macros.
      | 
      | Turnstile isn't enough to model certain type systems (for
      | reasons too complex to reasonably cover here), but it's
      | interesting to see that types-as-macros-over-the-untyped-
      | lambda-form has been done to a reasonable degree of
      | success.
 
  | avgcorrection wrote:
  | > I don't understand what the article is trying to convey.
  | 
  | Lispers are from Mars and Haskellers are from Venus. That's it.
 
  | Zababa wrote:
  | > Everyone in the PL community know about monads and macros but
  | they are tools and people are rarely excited about tools.
  | 
  | I don't think that's true at all. Considering how much we hear
  | about monads and macros compared to how much they are actually
  | used, people are excited about them. People are excited about
  | their tools, as they should be. There's this narrative that
  | some people push on Hacker News that we are all some kind of
  | cool rational engineers and that we don't act like other
  | humans. This is absolutely wrong. People get emotional about
  | tools all the time. This is this excitement that drives
  | progress. Someone got excited about error messages, and now
  | there is an industry-wide movement to improve them. This could
  | have been done decades ago, but it wasn't because people just
  | learned to live with their shitty tools.
  | 
  | While in the day to day life of a project you need people
  | capable of doing the less-exciting stuff, usually at the
  | beginning of something you need people excited to give birth to
  | it.
 
    | WastingMyTime89 wrote:
    | > Considering how much we hear about monads and macros
    | compared to how much they are actually used
    | 
    | That's users. That's not what I meant by the PL community. I
    | was talking about people actually working on PL.
    | 
    | Monads and macros are both old news and not particularly
    | exciting. I know they regularly crop up on HN. I find that a
    | bit sad because actually exciting things happening in PL like
    | effect systems rarely do.
 
      | auggierose wrote:
      | I've been hearing about effect systems for a long time
      | (decades ?) as well. I don't find them exciting at all. You
      | kind of counter your own argument ...
 
        | Ericson2314 wrote:
        | Agreed I'm completely sick of effect systems too. It's
        | entirely the wrong framing of the problem.
 
        | WastingMyTime89 wrote:
        | I would actually be glade if you could enlighten us about
        | why effect systems are the wrong framing and what you
        | would consider to be the right one.
 
        | Ericson2314 wrote:
        | Sure. So I've spent a lot of time working with FRP
        | things, e.g. with
        | https://hackage.haskell.org/package/reflex with the
        | github.com/obsidiansystems/obelisk/ web framework. While
        | FRP was sort of deemed to hard / a dead end in the early
        | 2020s, it works well for me, and I still consider it
        | viable.
        | 
        | I dislike effects because superficially they are about
        | similar problems ("My whole-program needs side effects")
        | but no amount of effects system stuff is going to ever
        | get you to anything like FRP. In particular, the eeffects
        | mind seems to be pretty narrow, something like:
        | 
        | "I have a bunch of regular-seeming code that does some
        | 'extra stuff', and I need to manage those extra
        | capabilities in a modular way that respects the principle
        | of least authority."
        | 
        | By contrast, the FRP approach rejects the premise that:
        | 
        | - The programs basically look more or less "normal", but
        | with (good) restrictions on what you can do
        | 
        | - The effects are something "side" or "extra".
        | 
        | It's hard to say exactly what it _is_ about instead, but
        | some guidelines might be:
        | 
        | - Don't just decompose a program into terminating parts
        | (like terminating callbacks + magic event look). Embrase
        | non-termination (well, co-termination), and think of new
        | ways to be modular that don't carry over from terminating
        | whole programs.
        | 
        | - Think in terms of "flows", be like a plumber. Try to
        | directory program the steady state in terms of the large
        | scale architecture, and not a regular functional--
        | imperative program that "carries out" out the flows as a
        | spec.
        | 
        | - - Actor-model-type stuff is wrong for focusing on the
        | "nodes not edges". We should have simple components but
        | rich networks.
        | 
        | - Try to find structure not in the effects themselves,
        | but how you intended to use them. If the effects form
        | sinks/sources, then have them be pipeline end pieces that
        | compose just like the the pure middle pieces.
        | 
        | - Calculus _is_ good for programming, after all. Time
        | derivatives  / time integrals are at very least good
        | metaphors. We should all go learn more Control Theory for
        | more things than "memory pressure".
        | 
        | I hope this helps.
 
        | Ericson2314 wrote:
        | Reflex in fact has a bunch of MTL-style classes, and
        | while I recognize the ways which that workflow isn't
        | ideal, it simply has not been a large problem for me in
        | practice. Effect systems are not useless, just low
        | priority to me -- they become more important if one turns
        | all their problems into questions of side effects -- but
        | per the above I don't think that is a fruitful way to
        | reduce all ones problems to one.
 
        | WastingMyTime89 wrote:
        | Research started more than twenty years ago but actual
        | applications in programming languages you might use are
        | relatively recent. If you find monads exciting but effect
        | systems boring there is little I can do for you.
 
        | zozbot234 wrote:
        | Lawvere theories have been around since the 1960s, so a
        | lot older than 20 years.
 
      | isaacimagine wrote:
      | I've done a lot of work with effect systems, continuations,
      | and concurrency. I guess I'd like to rant about it a bit.
      | 
      | An effect signals an interruption of the call stack to do
      | some work. Like an exception, it 'unwinds' to the nearest
      | handler (think catch block), and executes the effect; after
      | this point, the effect can either resume, or continue from
      | the catching handler.
      | 
      | This ability to resume is exactly a continuation. In fact,
      | a lot of effectful languages (Koka, Effekt) model it as
      | exactly that. It's important that this continuation is only
      | valid within the scope of the handler; this is equivalent
      | to a delimited continuation.
      | 
      | Full, as opposed to delimited continuations, on the other
      | hand, can be invoked from any scope. At the least, this
      | requires a copy of the stack, or that plus a subset of the
      | heap. (Functional languages with immutable datatypes kinda
      | get a free pass on this one, but only barely.)
      | 
      | Because delimited continuations can only be invoked in
      | nondestructive contexts, they do not require making a copy
      | of the stack (in fact, we don't even need to unwind the
      | stack at all.)
      | 
      | Except in most cases, we only resume once, and this--a
      | single-shot delimited continuation--is exactly a coroutine,
      | a feature common in many languages.
      | 
      | But there's more: remember that to execute an effect, a
      | handler has to be located. This handler is found by
      | searching backward through the stack. If this sounds
      | familiar, it's because this type of name resolution is
      | known as dynamic scoping (as opposed to lexical scoping).
      | 
      | The ties here run deep. We all know that closures are a
      | poor man's objects. That's easy too wrap your head around.
      | But I think we'll soon realize that effects are just a poor
      | man's coroutines are just a poor man's dynamic scoping are
      | just a poor man's delimited continuations are just a poor
      | man's resumable exceptions, and I'm not sure where the
      | strange loop ends.
      | 
      | But there's a light at the end of the tunnel. What do
      | effect systems have to offer above these other approaches?
      | I'd say there are 3 things:
      | 
      | 1. Static typing. Unlike coroutines, resumable exceptions,
      | etc. The type of effects used in a function can be
      | automatically inferred. A lot of these other systems
      | operate under a dynamic assumption, or require an explicit
      | annotation of types at some point. With algebraic effects,
      | the row of used effects can be statically determined with
      | no additional annotations by the programmer.
      | 
      | 2. Row-based composition. Building off our last point,
      | effects build a sort of open enumeration over the possible
      | effects raisable at a given point, a row. This row can be
      | generic over further effects, which means that effectful
      | higher-order functions can be composed. This full row can
      | be known at compile time, so that the programmer can know
      | the full set of potential effects in scope at each point in
      | the program. Because these row-based constructions are
      | usually built around a single monad (i.e. the free monad),
      | different effects can be composed without running into
      | traditional monadic composition issues.
      | 
      | 3. System Injection. What happens when an effect does not
      | have a handler in scope? This could just be an error at
      | compile time, but this opens up another possibility.
      | Instead of raising an error, unhandled effects are handed
      | off to the host runtime for evaluation. Quite sensibly,
      | effect systems are a really neat way not only to model
      | concurrency, but actual honest-to-goodness side effects. An
      | effect-based virtual machine is really just a glorified
      | effect generator. The host runtime can also expose
      | additional APIs, like an FFI, IO, or access to threads.
      | Most importantly, because these 'syscalls,' so to speak,
      | are just effects, they can be overridden. You could create
      | a handler that rolls native threading requests into single
      | threads, or redirects output to stdout to log files or the
      | network.
      | 
      | This has been quite the rambly rant. I've just been
      | thinking about this a lot recently and need to get it all
      | out of my system
 
      | Ericson2314 wrote:
      | People continue to study metaprogramming / representations
      | of syntax especially with binding, and category theory
      | applications to programming.
      | 
      | Yes, the blog post is bad and just rehashing old aphorisms,
      | but that doesn't mean the broader research agenda stalled
      | out.
 
      | Zababa wrote:
      | That's fair. I've seen a few mentions of Koka and OCaml 5.0
      | is going to have effects, though both are relatively niche.
      | I was expecting to hear more about linear types too, I feel
      | like one or two years ago (relatively) lots of people were
      | talking about them, but I haven't seen much coming out of
      | it.
 
  | maxiepoo wrote:
  | Exactly, Haskell's "do" notation is essentially defined as a
  | built-in macro, so if you ever want to use "do" notation in a
  | language that doesn't have it built-in you will need to use
  | macros. (Let-binding overloading is essentially do notation in
  | case that's not clear)
 
    | whateveracct wrote:
    | "do" is also no longer hard-coded. There are multiple
    | language extensions that allow you to change what it desugars
    | to, which is really cool.
 
    | marcosdumay wrote:
    | Macros are way more powerful than binding, for both the best
    | and the worst.
    | 
    | Yes, you can use macros to add do-notation to a language. But
    | you can also use macros to add things like type checks and
    | new conditional statements. You can't add those with do-
    | notation.
 
    | tromp wrote:
    | do is more syntactic sugar for >>= (monadic bind) than macro.
    | E.g.                   do           s <- readLine "Enter
    | name:"           putStr $ "Hello, " ++ s
    | 
    | reduces to                   readLine "Enter name:" >>= (\s
    | -> putStr $ "Hello, " ++ s)
    | 
    | But I agree you can use macros to implement syntactic sugar.
 
| bjoli wrote:
| Alexis King had a talk about Hackett, a sadly abandoned,
| metaprogrammable haskell: https://youtu.be/5QQdI3P7MdY
 
___________________________________________________________________
(page generated 2021-10-03 23:01 UTC)