4 ms·
> 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
by sparkie 2y ago
> 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.
Lost me at this part. Of course reading is a side-effect. A function which reads from a file/console cannot be pure. Purity implies referential transparency - a function given the same arguments will always return the same result.
If we consider the example given:
pub fn read_guess() -> int {
return io.read_int("Take a guess (0-100): ");
}
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`.
> 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.
Creating a local variable isn't a side-effect, but allocating 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.
- fwlr 2y agoLater on the author makes the point that everything changes some various register deep in the guts of the CPU so by definition everything is always effect-full, but since we invented programming languages to get away from writing assembly, we shouldn’t consider that an effect when we’re not writing assembly. We invented many programming languages to get away from writing allocations and pointers into buffers too.
- empath75 2y agoYeah, but you're losing one of the things that you get from pure functional languages, which is determinism. The same function run multiple times may return different results.
- chuckadams 2y agoIt’s usually implied that “effect” pertains to observable effects. Anything that doesn’t affect referential transparency gets handwaved away. A memoized function might use a private stateful cache, but as long as it doesn’t affect determinism, you can keep its existence secret.
- _nalply 2y agoA thought stroke me. Security struggles with timing leaks. That programming language would need something like a timing effect to control timing leaks. In such a programming language a memoized function is effectful on time.
- deleted 2y ago[deleted]
- jahewson 2y agoIf you print to stdout but nobody is around to read it, does it count as an effect?
- sparkie 2y agoPerfectly reasonable question. Basically, you can consider it a NOP if there's no observable side-effect. Obviously, stdout has a particular purpose and you wouldn't just disable the side-effect for one use case, but you could imagine having some pseudo console for the purpose of emitting debug messages which is considered a non-effect in your language (provided you can't read from it and reify the text back into values). In .NET for example, you can use `Debug.Write` and `Trace.Write` for emitting debug messages, and these lines are erased from the release version of the binary. You can mark any void-returning function with the `[Conditional("DEBUG")]` attribute and have calls to it erased because the DEBUG symbol is not defined in release versions. IMO, this should be standard in all languages - in languages with effects even moreso because we don't want to pass around an effect if our only use for it is debug or trace messages.
- shirogane86x 2y agoHaskell also has something like this in Debug.Trace.trace: even though it's _technically_ impure (cause it outputs to stdout), it's typed as a pure function because it's just for debugging (I think internally it uses an escape hatch to just hide the IO)
- Groxx 2y agoSeems like you could solve that by endlessly buffering stdin? Then you'd have to keep passing higher and higher offsets to read from, and old offsets would simply return their original, old values.
- mitt_romney_12 2y agoWhat happens if you do: x = read_guess(); y = io.read_int("what's your name: "); z = read_guess(); How would your system ensure x and z are the same?
- Groxx 2y agoYou don't have that API. You offer x = read_guess(0) y = read_name("what's your name: ", len(x)) z = read_guess(len(x)+len(y)) If you read(0) twice, you get the same value both times.
- mitt_romney_12 2y agoBut now every function that reads from stdin needs to pass around an offset of where to read from which is very unergonomical. It also isn't really pure since offset is now state that is changing whenever a function is called. fn do_stuff(offset: int) -> (string, int) { x = read_guess(offset) y = read_name("what's your name: ", offset + len(x)) z = read_guess(offset+len(x)+len(y)) new_offset = offset+len(x)+len(y)+len(z) return ("name is: {y}, guess is: {x}", new_offset) }
- Groxx 2y agoErgonomics isn't really the point of a hypothetical extreme system. And no, you can compute offset as a result of all inputs to the system, like I did in my example - just keep passing a "previous state" value around, and compute the next state and return it. Like any other functional system already does 99% of the time.
- sparkie 2y agoIt doesn't solve the referential transparency problem. To be referentially transparent, the buffer would need to be fixed in size, and have all of its data input prior to being used the first time. Essentially, it would akin to passing a read-only file as a command line argument to `main`.
- swiftcoder 2y agoThe author has an entire paragraph dedicated to explaining the difference between "pure" in an effect system vs "pure" in a functional language. It might help to read the rest of the article
- zsyllepsis 2y agoI think the author’s choice of function to demonstrate purity made this harder to grok as a reader. Asking the reader to “…exclude the I/O interactions…” when considering functional purity makes the analogy much harder to follow. My interpretation of the thesis of that paragraph is that localized mutation does not violate referential transparency, but getting there required some gracious reading. By the end of the section, we’re given an _example_ of an effectfully pure function, but no standalone definition. Based on that, I agree that this is a weak point in the overall piece.