> 4. External resources (files, I/O): this would be a very difficult topic if not for our simple question. Only writing to a file or outputting to an I/O stream is considered an effect, as only the process of writing mutates external state. Reading data creates new state, but does not modify it, therefore reading data of various sorts is not an effect. Because of this, printing is effectful, but reading from stdin is not.<p>Lost me at this part. Of course reading is a side-effect. A function which reads from a file/console cannot be <i>pure</i>. Purity implies referential transparency - a function given the same arguments will always return the same result.<p>If we consider the example given:<p><pre><code> pub fn read_guess() -> int {
return io.read_int("Take a guess (0-100): ");
}
</code></pre>
We should take `read_int` to be effectful because it will have the effect of advancing the position to read from in the console's buffer. If it didn't, it would always read from the same position, so even if the user took a second guess, the first guess would be read again the second time `read_int` is called. So given that `read_int` is effectful, so too is `read_guess`.<p>> 1. Well, creating a variable is creating new state, but it’s not changing the state, therefore creating a variable is not an effect, but changing it is.<p>Creating a local variable isn't a side-effect, but <i>allocating</i> a variable is a side effect. Sure enough, if we also free the allocation before leaving scope, we can avoid propagating that effect, but if you return a value that contains anything allocated by a function, then the function becomes effectful.
I've been thinking a lot about effects systems recently. A few months ago I implemented an effects system based interpreter in Haskell for an embedded DSL prototype for a project at work. In our case, the effects we were concerned with were all some variation of reading data, and the effects based approach allowed us to statically ensure that data would be available, and let us perform some clever optimizations to reduce the overhead of large IO operations.<p>We've since rewritten the system and, while we still support an optional effects based style in our DSL, it's not being used very heavily. In practice, our user found the ergonomics of the effects system quite challenging, and it introduced some type inference challenges. The biggest problem was that our use-cases ended up with functions that would have hundreds of effects, and it was fairly unwieldy and the type errors were difficult to deal with. Since we've introduced the updated version, most users prefer to user our newer features that allow them to write more traditional code even though it means they don't get composable effects.<p>On the other side of the experience, I've come to believe that effects systems are a good idea, but when adding them to an existing language it's probably best to make them an opt-in feature that can be used to constrain specific small parts of a program, rather than something that should be applied globally. I also think we need a bit more research into the ergonomics before they are going to appeal to a lot of users. That said, the guarantees and optimization opportunities ours gave us were really nice, and were quite difficult to achieve without building on top of the effects system (our new system is about an order of magnitude more code, for example).
What is the actual problem that this effect-oriented approach addresses? The effects of methods that are called by other methods will be concealed, so at the calling site, you'll have no idea that the high-level API `do_stuff` is going to utilize `io`. That is, unless the effect decoration "infects" the caller like `async`, in which case every complex API method will be decorated with a huge number of effects.<p>Simply put, what does this buy us?
I can't but feel this is running head first into statements versus expressions.<p>Specifically the part that views effects as growing in size and that being contrary to desired behavior. Strikes me as worrying about similar concerns.<p>Seems more that it is the eager evaluation of each line of code that is a problem. Nothing wrong with growing the footprint of concern on code. The problem is how to annotate the concern.<p>Consider, adding 'logger.whatever(...)' is likely not a concern to the program. Being able to annotate the logger as not an effect to check makes sense. Of course, all edge cases matter. Are the arguments eagerly evaluated? What if the logger doesn't even use them, due to level?<p>With lisp, the magic wasn't only that you can treat code as data, but also that you could define code that ran at different times. And you could largely do that in "user space."
The article starts off with a motivating example of effects getting "bigger" when composed but I don't see where it does anything to contain / control that expansion (other than "proto" which does full inference)<p>Seems like complex functions could end up with effects lists of unwieldy size, similar to how Elm programs end up with a giant "Msg" type.
in the OP example, this is considered bad<p><pre><code> let x: string = "hello world";
x = 32; #type error
</code></pre>
however, a well designed <i>strong</i> typesystem should be able to compose types to allow this if desired by the coder (eg. to read in a column of numbers from a csv)<p><pre><code> my $forty-two = 42 but 'forty two';
say $forty-two+33; # OUTPUT: «75»
say $forty-two.^name; # OUTPUT: «Int+{<anon|1>}»
say $forty-two.Str; # OUTPUT: «forty two»
</code></pre>
Calling ^name shows that the variable is an Int with an anonymous object mixed in. However, that object is of type Str, so the variable, through the mixin, is endowed with a method with that name, which is what we use in the last sentence.<p><a href="https://docs.raku.org/routine/but" rel="nofollow">https://docs.raku.org/routine/but</a>
Out of curiosity is there any point to a loop that doesn’t produce some effect in its body? Obviously I mean traditional imperative looping constructs and not map functions that return a value.