[HN Gopher] Effing-mad, an effect library for Rust
       ___________________________________________________________________
        
       Effing-mad, an effect library for Rust
        
       Author : agluszak
       Score  : 169 points
       Date   : 2023-03-29 15:06 UTC (7 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | gpderetta wrote:
       | Aside from this stuff being quite cool & interesting, love the
       | tone of the readme, especially the list of Cool Things :D
        
         | stcroixx wrote:
         | It does really stand out from how documentation on a low level
         | library has traditionally been presented.
        
       | epilys wrote:
       | Less jarring than the keywords blog post to be honest. This
       | doesn't skew away from existing Rust syntax at all, whereas the
       | keywords proposal introduce more syntactic noise.
        
         | insanitybit wrote:
         | Well it introduces `yield` and multiple macros. Presumably
         | these would turn into keywords/ syntax at some point.
        
           | shepmaster wrote:
           | Pedantically, `yield` is already a keyword [1]. As the
           | project's README states, generators [2] are an unstable part
           | of Rust, and the `yield` keyword goes with them.
           | 
           | [1]: https://doc.rust-
           | lang.org/reference/keywords.html?highlight=...
           | 
           | [2]: https://doc.rust-lang.org/std/ops/trait.Generator.html
        
       | akavi wrote:
       | Has anyone gone from not "getting" the value of algebraic effects
       | to getting it? And if so, what was the tutorial/process by which
       | you achieved enlightenment?
        
         | ithkuil wrote:
         | To each their own, ymmv. This was my gateway drug:
         | https://arxiv.org/abs/1611.09259
        
           | firstlink wrote:
           | Something I don't understand yet (maybe I should just read
           | your linked paper, but maybe it doesn't answer this either):
           | are algebraic effects _necessarily_ founded on delimited
           | continuations (of the reset-shift0 variety) or are there
           | other kinds of algebraic effects?
        
             | bitwalker wrote:
             | I think that's largely an implementation detail, so not a
             | requirement as far as I know. That said, delimited
             | continuations certainly make them easier to implement, as I
             | understand it.
        
               | firstlink wrote:
               | I took a look at the paper and it too shows that the
               | effects are just delimited continuations with named,
               | algebraic-typed prompts. It even goes into the difference
               | between shift0 and shift, but not by that name. (It
               | claims that Koka and others are using shift, which isn't
               | quite what I remember but okay.)
               | 
               | I guess it's a good marketing move because "multi-prompt
               | delimited continuations" is scary. It also makes the
               | types sane; I once worked out that expressions with
               | delimited continuations of the reset-shift0 variety are
               | typed by binary trees of ambient types, with a subtyping
               | relation s.t. leaf nodes can be expanded.
        
       | satvikpendem wrote:
       | For those of you following along with keyword generics in Rust,
       | the current proposal is this [0]:                   trait ?const
       | ?async Read {             ?const ?async fn read(&mut self, buf:
       | &mut [u8]) -> Result<usize>;             ?const ?async fn
       | read_to_string(&mut self, buf: &mut String) -> Result<usize> { ..
       | }         }                  /// Read from a reader into a
       | string.         ?const ?async fn read_to_string(reader: &mut impl
       | ?const ?async Read) -> io::Result<String> {             let mut
       | string = String::new();             reader.read_to_string(&mut
       | string).await?;             Ok(string)         }
       | 
       | which was rightfully disliked [1]. There have been some other
       | proposals however, such as this one I wrote about [2]
       | fn foo<F, T>(closure: F) -> Option<T>         where
       | F: FnMut(&T) -> bool,         effect              const if F:
       | const,             ?async,         { /* ... */ }
       | 
       | for which someone made a more detailed issue [3]. It is similar
       | to a `where` clause in that the `effect` clause comes afterwards
       | and defines the effects that a function can have.
       | 
       | [0] https://blog.rust-lang.org/inside-rust/2023/02/23/keyword-
       | ge...
       | 
       | [1]
       | https://old.reddit.com/r/rust/comments/119y8ex/keyword_gener...
       | 
       | [2] https://github.com/rust-lang/keyword-generics-
       | initiative/iss...
       | 
       | [3] https://github.com/rust-lang/keyword-generics-
       | initiative/iss...
        
         | u320 wrote:
         | If Rust had algebraic effects, your example would be:
         | fn foo<T,E>(closure: impl FnMut(&T) -> bool | E) -> Option<T> |
         | E
         | 
         | All that's needed syntax-wise would be a way to attach one or
         | more type parameters to a function type. (I used "|" here but
         | it could be anything)
        
         | [deleted]
        
         | yoshuaw wrote:
         | What you're highlighting here is something we explicitly call
         | out in the blog post as what we _don't_ want people to have to
         | write. We agree the syntax is noisy and would be bad to work
         | with. We instead would prefer it if people could write
         | something like this instead, which would have the same
         | semantics as `?const ?async fn`:                   effect fn
         | read_to_string<R>(reader: &mut R) -> io::Result<String>
         | where             R: effect Read         {             let mut
         | string = String::new();             reader.read_to_string(&mut
         | string).do;  // note the .do here             string         }
         | 
         | The exact syntax of the design is something we're far less
         | committed to than the semantics of it. But we didn't do a good
         | enough job at communicating that in our last post, so that's on
         | us.
        
           | satvikpendem wrote:
           | Why would you want the function to run over all effects
           | instead of explicitly outlining what effects you want the
           | function to run on? It seems like the latter is more verbose
           | but more explicit.
        
         | lalaithion wrote:
         | And the way you'd do it with this library is
         | // very complex and powerful API         effing_mad::effects! {
         | Read {                 fn read_to_string(bug: &mut String) ->
         | Result<usize>;             }         }
         | #[effectful(Read)]         fn read_to_string() ->
         | io::Result<String> {             let mut string =
         | String::new();             (yield read_to_string(&mut
         | string))?;             Ok(string)         }
         | 
         | and then to call it, you would do                   let handler
         | = handler! {             Read {                 read() =>
         | reader.read(),             }         };              let action
         | = handle_group(read_to_string(), handler);         let result =
         | run(action);
        
         | pjmlp wrote:
         | I really hope the ??? vomit isn't accepted.
        
           | 0x457 wrote:
           | You have to read the post related to this proposal (first
           | link). It shows why it makes sense more than whatever is
           | proposed in this thread.
           | 
           | I agree that it looks like trash, though.
        
             | pjmlp wrote:
             | No it doesn't, not in the form it is proposed.
        
       | agluszak wrote:
       | For context: https://blog.rust-lang.org/inside-
       | rust/2022/07/27/keyword-ge...
        
       | airstrike wrote:
       | This sent me down a rabbit hole... I didn't know the "function
       | coloring problem" was a thing, but this was a hilarious read
       | https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
        
       | dagipflihax0r wrote:
       | It may help to know that "[computational] effect" is supposed to
       | mean these things together: - a particular class of computation,
       | in a programming context where we want to define the class and/or
       | take advantage of computations being defined in this way - to
       | this end computations are wrapped in a type constructor
       | (typically with one argument e.g. State<T>, IO<T>, Maybe<T>).
       | 
       | The jargon is of course terrible and unhelpful (but widely used
       | and used somewhat consistently). It is not necessarily about
       | side-effects, it is not necessarily about interacting with
       | environment. Calling it "algebraic effects" makes it a bit
       | mysterious the various supported methods/callbacks are more or
       | less like an "algebra", since they involve the type parameter T.
       | To top it off, this use of "effect" is quite different from, say
       | "type and effect system", and Moggi's 1991 never used "effect" in
       | "Notions of computation and monads."
        
       | puddingforears wrote:
       | This readme is great!
       | 
       | > This means you have to use monad transformers. I don't really
       | understand monad transformers, therefore they are bad.
       | 
       | LOL we all start here.
        
       | grumpyprole wrote:
       | > Long story short, this library solves the function colouring
       | problem
       | 
       | For a great example of this, see OCaml 5 support for effect
       | handlers. It's basically a generalisation of exceptions, very
       | lightweight and elegant. I can't help but think this approach
       | would have been a much better fit for Rust than the current async
       | effort. Monads like Async and Result are a slippery slope into
       | the world of FP, something Rust will never excel at. Effect
       | handlers are an alternative which arguably better align with
       | systems programming.
        
       | handwarmers wrote:
       | This project has the least cringey Rust vibes vibes I have seen
       | so far. I love it.
        
         | okamiueru wrote:
         | Some over-the-top rust advocates (not naming names) almost made
         | me ditch wanting to learn rust. I'm glad I stuck with it, as it
         | has some lovely features, and after some weeks of frustration,
         | it's fun to program.
        
       | firstlink wrote:
       | Erdos thought God had a book of proofs. I think he was slightly
       | off the mark: God has a book of _abstractions_ , and the good
       | proofs are the ones which use abstractions from the book most
       | judiciously. Anyhow, algebraic effects are in The Book.
        
       | [deleted]
        
       | simplify wrote:
       | Algebraic effects is a fantastic type system feature that needs
       | to break into mainstream ASAP.
        
         | u320 wrote:
         | I agree but Rust is extremely unlikely to go in this direction,
         | even though it is a pretty good fit.
        
           | satvikpendem wrote:
           | They're already working on keyword generics which is a
           | limited version of this.
        
             | pjmlp wrote:
             | ?with ?lots ?of ?interesting ?syntax.
        
             | u320 wrote:
             | I don't think anyone would describe their proposal as a
             | limited version of algebraic effects. It's missing the
             | algebraic part for one thing.
        
               | satvikpendem wrote:
               | That's how the authors describe it, in general terms at
               | least: https://blog.rust-lang.org/inside-
               | rust/2022/07/27/keyword-ge...
        
               | yoshuaw wrote:
               | When I wrote that I didn't really understand the
               | difference in terminology between "effects"/"effect
               | types" (e.g. keyword-based modifiers on functions and
               | types) and "effect handlers" (e.g. typed co-routines).
               | 
               | Many effectful languages have both, so teasing them apart
               | can be confusing. I think I can now more comfortably say
               | that what we're working on is an extension to Rust's
               | effect system. This has nothing to do with effect
               | handlers.
        
       | KingOfCoders wrote:
       | I was deep into effect languages (Koka) and did some effects on
       | my own in Scala for some years.
       | 
       | After a year with trivial Go I just wonder why.
        
         | satvikpendem wrote:
         | Could you elaborate? What did you find unnecessary or bad about
         | effects?
        
           | jerf wrote:
           | I've dug deep into Haskell, and my day-to-day job is Go, so I
           | can probably sort of answer this at least for myself.
           | 
           | A lot of time, heavy-duty functional programming advocates
           | will use as their foil what you might call either the worst
           | of imperative programming messes, or if you want to be more
           | generous, an "average" imperative programming mess, where I
           | include OO in the "imperative" category for the purposes of
           | this conversation. Against this they will present their world
           | of pure programming and recursion-scheme-based programming
           | and super strong, complicated type systems, and claim it is
           | better.
           | 
           | I agree, for the most part. I mean, I could elaborate and add
           | half-a-book's worth of nuance to that, but for now, I agree.
           | 
           | However, when I, personally, sit down in front of Go and
           | program with it, I am not some abstract "imperative
           | programmer". I am someone who is writing Go having been
           | informed by the lessons from Haskell and Erlang. My code is
           | not perfectly functional, because that is not the optimum,
           | but you don't see global variables flying everywhere in my
           | code. You don't see my code being written where everything
           | takes a dozen locks simultaneously to do anything. You see
           | actors constraining things. IO may not be rigidly separated
           | by a type system, but in a lot of my code you can see that I
           | isolate IO behind an interface.
           | 
           | If you are feeling a bit generous and squint a bit, I write a
           | lot of my Go code as a free monad with an interface being
           | used as an interpreter, which allows me to use a "pure"
           | implementation of that interface, if you also squint and
           | allow the initial construction of the value to count as a
           | "pure" call and the output of the test implementation to be
           | considered as a pure output, as a test driver. This turns
           | what superficially looks like imperative, highly stateful
           | code that may even have extensive dependencies onto external
           | state into pure code when used with its pure interpreter,
           | which is great for testing. Then I can also write very
           | impure, but highly focused, integration testing on to the
           | "real" free monad interpreter to be sure it works as I
           | expect, with its interface being minimized to just what the
           | interpreter needs which makes the testing easier.
           | 
           | So when I sit down, in real life, with a real engineering
           | problem, the choice I face is not "Write Haskell/Rust and be
           | pure and wear the hair shirt" (as the saying goes) and "write
           | imperative code that will bring the world down around my
           | ears". It's between the first, and "write in an imperative
           | language with guardrails inspired by functional and stronger
           | languages that actually do a pretty decent job." As a result,
           | the choice I face is not so much day versus night, but day
           | versus "overcast but warm day". I don't deny there's still a
           | difference and a value in the harder, stronger, more rigid
           | languages, and there are absolutely still tasks I might reach
           | for them for. But the value proposition the harder, stronger
           | languages bring me are much more _muted_ than they might be
           | for someone else.
           | 
           | Effects are cool, I hope people continue to research them.
           | I'd love to play with them sometime. Thumbs up to this Rust
           | experiment too. But the simple truth is, they don't solve a
           | problem I _actually have_ right now. The  "effects system" I
           | implicitly get from already being careful with IO and my
           | separation between IO-using code and the IO drivers solves my
           | real problems, and nobody else on my team is reading my code
           | and going WTF, because the value proposition is obvious after
           | just spending a minute or two reading the test code.
           | 
           | Again, I want to emphasize, I'm not saying my solution is
           | perfect and therefore anyone who uses any harder, stronger
           | languages are wrong. I'm very explicitly saying the opposite.
           | I'm just saying that when you include choices other than a
           | binary "use the strongest, hardest language possible" and "be
           | cast into the outermost darkness of pure imperative
           | programming, where there is much wailing and gnashing of
           | global variables", you may find that the ideal engineering
           | balance isn't either extreme.
           | 
           | (Though I would say it's _closer_ to the former than the
           | latter. Undisciplined imperative programming is every bit the
           | nightmare the functional advocates say it is, and what
           | happens if you try to multithread without discipline hardly
           | bears thinking about.)
           | 
           | All that said, I _highly, highly_ recommend learning
           | something like Rust or Haskell and becoming _good_ at it. You
           | can kinda sorta pick these principles up in other looser
           | languages, but there is a _ton_ of value in working in an
           | environment where the compiler will rigidly enforce these
           | practices. If those are still too strong, even Erlang /Elixir
           | will give you a lot of practice, with a bit less rigidity. It
           | is so much faster to learn in the stricter languages than in
           | the looser languages. And it's a valuable skill that you may
           | then someday deploy when you encounter a task where you need
           | the full strength they offer.
        
         | ezy wrote:
         | I didn't want to put down the effort of the OP, because even
         | they admit it's an interesting experiment, not something they
         | recommend that anyone use, and I do think it's an interesting
         | experiment. But I have exactly this feeling in general.
         | 
         | I like the idea of Rust, but I feel it has, unfortunately, been
         | taken over by the seductive idea that abstraction is the
         | purpose of a programming language (e.g the C++ crowd, among
         | others). I can say from long experience, that while it probably
         | won't impede its popularity, this isn't real progress in
         | programming language design. These abstractions just create
         | their own problems to solve on top of solving the original
         | problem you wanted to solve by writing a program in the first
         | place.
         | 
         | Inevitably, that means that you end up having to limit yourself
         | to some particular "idiom" or subset of the language in
         | production, so that you can get anything constructive done
         | without the code being inscrutable or unmaintainable or just
         | overblown for the task that's being performed.
         | 
         | I knew it was over when they started debating adding more and
         | more meta-language type system features, and then added
         | async/await -- which is the very definition of creating a
         | problem to solve a problem.
         | 
         | So, as much as I appreciate Rust, I am looking forward a newer
         | systems language with more discipline in its design and
         | direction.
         | 
         | Go isn't perfect, but it definitely trends to the right flavor
         | of simplicity and design discipline.
        
       | boredumb wrote:
       | I legitimately laughed out loud reading through the readme a few
       | times. Also reminds me I should probably get around to really
       | digging into Pin/Unpin.
        
         | theossuary wrote:
         | Jon Gjengset's stream on pin/unpin is invaluable for
         | understanding Pin semantics: https://youtu.be/DkMwYxfSYNQ
         | 
         | But the 10000 ft view is: Pin lets you tell Rust to never ever
         | move a piece of memory. This is almost always done because the
         | piece of memory has pointers into itself (it's self
         | referencial) which would be come invalid if moved.
        
       | margorczynski wrote:
       | Good effort but still this looks pretty awkward compared to
       | Haskell and Scala cause of the type system limitations.
        
         | proto_lambda wrote:
         | I mean, it's a library that implements a language feature. It's
         | surprising it's possible at all, even if really ugly.
        
       | Georgelemental wrote:
       | This function signature is a work of art: https://docs.rs/effing-
       | mad/0.1.0/effing_mad/fn.transform.htm...
        
         | ilovecaching wrote:
         | I've saved most of this thread so that I can link it when
         | someone asks me why I don't use Rust for systems development
         | yet. In some ways Rust is worse than C++ with these completely
         | out of orbit contrived math problems in the type system.
         | 
         | C is a simple language, but it's still difficult for beginners
         | to understand systems software because it's complex. Adding
         | something like this were people are inventing complexity in the
         | language itself out of boredom is a recipe for disaster and is
         | much worse than the terrible things than can happen in C.
         | 
         | Please just learn to write simple code and solve hard problems,
         | rather than writing complex code to solve contrived problems.
        
           | Georgelemental wrote:
           | Production Rust code doesn't look like this.
        
           | reitanuki wrote:
           | you do realise this is essentially a joke right?
           | 
           | I'm sure every language has examples of code where it was
           | written to be crazy on purpose, This is one of those.
           | 
           | Like when you write a raytracer in CMake, or Meson build, or
           | C++ templates. Etc etc. Or when you compile your program to
           | only mov instructions, because mov instructions are turing
           | complete (should this be a reason we shouldn't use x86?). I'm
           | sure there are plenty more examples.
           | 
           | Nobody is suggesting you actually use this stuff. Therefore
           | it seems a bit silly to point at it and say 'don't use this
           | language because people do silly things for fun in
           | it!!!!11!!1'
        
           | insanitybit wrote:
           | > Adding something like this were people are inventing
           | complexity in the language itself out of boredom is a recipe
           | for disaster
           | 
           | Not really. This is a fun project to show what an effects
           | system would look like. The consequences of this are pretty
           | much strictly positive.
           | 
           | > and is much worse than the terrible things than can happen
           | in C.
           | 
           | No. "Ugly code no one will use but that demonstrates a
           | concept" is definitely not worse than "attacker has full
           | control over the computer".
        
             | ilovecaching wrote:
             | Complexity is where bugs hide. Do you really think Rust
             | protects you from every class of bugs? It doesn't even
             | protect you from all memory bugs.
        
         | akiselev wrote:
         | I don't see any GATs. Color me unimpressed. (/s)
        
         | echelon wrote:
         | What I worry the "Rust is hard" commentary and criticism makes
         | people think of Rust.
         | 
         | 99.9% of the time you're never going to touch voodoo like this.
         | Unless you choose that path.
        
           | [deleted]
        
           | VWWHFSfQ wrote:
           | I struggled mightily with the borrow checker and lifetimes
           | when I first got started until I got some good advice to just
           | use the heap. String, Vec, .clone(), etc. Just write the code
           | you want and don't worry about allocations. You can fix that
           | all up later when you're more comfortable. It was kind of a
           | cognitive game-changer for me. And now I love Rust and I've
           | gotten better at it.
        
             | echelon wrote:
             | This is the best Rust onboarding advice ever.
        
               | MuffinFlavored wrote:
               | I don't think it is. I ran into the same pitfall. Instead
               | of taking String and passing around &str, I did .clone()
               | everywhere. Kind of gross? The better advice I think is
               | "lean on passing around borrowed parameters as much as
               | possible, otherwise you're going to run into Copy+Clone
               | hell. I would be curious to learn from somebody "even
               | better" at Rust if that is bad advice.
               | 
               | That and, I just wrap everything "hard" in some
               | combination of lazy_static / Arc+Mutex.
        
               | echelon wrote:
               | Great way to learn your bearings. Then once you get the
               | lay of the land you recognize that's not the way to do
               | things.
               | 
               | It'd be neat if the language had a "beginner mode" flag
               | where the compiler could recommend simple things like
               | abuse of the heap. All you need is a day or two with it,
               | then you can take off the training wheels.
               | 
               | Write one to throw away. Learning is messy and hands-on,
               | not rote and academic.
        
             | rhn_mk1 wrote:
             | And then you decide to try nostd :)
        
           | donio wrote:
           | Or if you use a library that chose that path and you end up
           | having to debug it.
        
         | colonwqbang wrote:
         | Enough to make even a seasoned Haskell programmer blush.
        
           | elcritch wrote:
           | Now the real question is blush in envy or embarrassment? ;)
        
       ___________________________________________________________________
       (page generated 2023-03-29 23:02 UTC)