|
| 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) |