Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> 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`.

The point is to make the caller aware of any side-effects so that the callee can't conceal them.

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

Effects are infectious, precisely because if `foo` has effects, which must be known to `bar` which call's `foo`, then `baz` which calls `bar` must also be aware of the effects of foo, else they would be hidden.

> Simply put, what does this buy us?

For one, it makes it explicit what can and cannot be done by some code, as a means to prevent obvious programmer errors. You might consider it analogous to a static versus dynamic type system. In the dynamic system we can call `(foo 123)`, even though `foo` might expect strings rather than integers - and we get a failure at runtime. In a static typing system, we can catch this error much earlier - at compile time. `foo(s : String)` makes it clear what foo expects.

Effects attempt to augment functions not only with the type of value they expect, but also with the capabilities that the function has. If a function is pure, it's not capable of going into your filesystem and deleting data, for instance.

While the effects are infective, the languages encourage you to write as much as you can using pure functions and only use effects where strictly necessary. Essentially, they follow the principle of least privilege[1].

There are ways to avoid infecting a program with the `IO` effect where only "some" IO is needed. For instance, in Clean, which uses uniqueness types instead of effects, a function which writes to a particular file can be given the privilege for just this file, eg `write_foo : *File String -> *File`. That would prevent `write_foo` from opening up another file and writing to it. The file itself must be opened using a `*Filesystem` type, which `*World` - the primordial source of uniqueness for your program implements, which is passed into the program's entry point.

The other advantage referential transparency gives us is optimization. If a function is called multiple times with the same arguments, it always produces the same result. If we can detect at compile time that a function is called more than once with the same arguments, we can cache the result of the first call, and replace the second call with the cached value. This same optimization can't be done with unknown side-effects because those side-effects may be desirable - we don't want the compiler to attempt to remove them.

Clean's uniqueness types give us another opportunity for optimization. A uniqueness type is guaranteed by the compiler to not be aliased, which means that we can mutate a value in place whilst retaining referential transparency, though only in a single-threaded manner.

In regards to threading, threads are also side-effects. If we call into some library function written by someone else, we don't want their functions to start spawning threads and potentially mutating values we give it, else it could cause any number of race conditions. If any functions which may spawn a thread have a big red flag on them, we can avert these problems earlier, rather than finding out in production and spending a lot of time debugging.

Another advantage that purity provides (but not uniqueness) is the ability to reuse parts of data structures for multiple values. For example, if we have a linked list `l`, we can write both `x = cons foo l` and `y = cons bar l`, to obtain two new lists which both have `l` as their tail, but they both refer to the same `l` in memory. It's safe to do this because we can't mutate this tail - we wouldn't want a mutation of the list `x` to also mutate `y`. In a language which doesn't prevent arbitrary side-effects, we would need to make a copy of `l` when creating `x` and `y` to prevent this from occurring. Purely functional data structures have many uses - if we want to keep a versioned record of states (like git) for example. Would strongly recommend reading Okasaki's Purely Functional Data Structures[2] for more insight.

[1]:https://en.wikipedia.org/wiki/Principle_of_least_privilege [2]:https://www.cs.tufts.edu/~nr/cs257/archive/chris-okasaki/dis...



> If we can detect at compile time that a function is called more than once with the same arguments

Just to emphasize, effect systems can let the compiler chase down whole chains of functions calling functions and libraries and so on and know that at no point is there going to be anything non deterministic happening. Something you can't do, btw, if you follow the OP's advice and allow file reads.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: