What's the future of side effects?
# thinking-together
m
What's the future of side effects?
l
modeled as a "possibly affecting" graph, and thus possible to reason about, while limiting the spread of model-precision-degradation.
💯 2
d
Define side effects. Ta 😄
m
anything that make a function not pure, interacting with the "outside", examples: write to disk, network request, read from a socket
d
I won't mess with this thread with my heresies, so don't answer this: suffice to stay, "start with side effects"!
👍 2
t
@Duncan Cragg now I really want to hear more 🙂. In what way “start with”?
d
I said don't answer! This is a functional programming thread! 🤗
The OP's question was posed with a hidden presumption that respondents have already agreed to tie their hands and feet with pure FP 😊
🙂 1
😄 2
m
nope, I'm not an FP fundamentalist, I don't even use types that much (python, js, erlang and elixir are the languages I use the most)
but I don't like to write code full of side effects, hard to test, hard to reason about, hard to know all the edge cases
I also don't like mocks and doing dependency injection everywhere to be able to switch the side effect parts is a lot of work
b
ooh oooh; good news everyone; I've actually totally solved this!!!! OK so.... that was a lie; just to put you in the correct mindset of TRUST NOTHING THAT FOLLOWs. I would be interested in exploring how "developer tooling" could get better at addressing this. I use that term rather broadly; so would put "type systems" and "interpreters that support dynamic languages" into the bucket of developer tooling. Design of these tools is the hard part. I'm currently of the hypothesis that highly opinionated tools are great; as long as they restrict users in "the right way" So one day in fantasy land our tools will tell us where our impure code is. And let us chose our poison in a boilerplate friendly way of how we wish to address testing; probably using different trade offs at different stages of various test cycles. The "lessons learned" in building type systems is likely a fertile learning ground The last 15 years in API design lessons relating to "high performance IO" must have some critical insights. If we can put all those take aways in one box; that might be the starting point for having enough info to build the end-game of effect management. (I clearly need to work on refining my thinking & communication thereof in this space)
c
If the choice is between "no side effects" (e.g. Haskell) and "no guarantees" (e.g. Python) then I think Python is better, because your assumptions about side effects are usually right. The claims such as "a + b could format your HD" are basically nonsense, whereas the impractically of a pure FP language are actually real (for me at least). It does annoy me that I can't actively mark something as pure and have it enforced.
😄 2
m
who/what claims that "a + b could format your HD"?
p
Ok huh. We're not in #C0133ED5811 , but I'd like to propose a recent functional programming innovation: Algebraic Effects. I wouldn't bother too much in understanding the name (I don't quite get why it's named that), but the essence is this: 1. Annotate your function types with the effects they can have. Haskell kind of has this with IO, for example. but IO is this behemoth that can do anything. And you can't get rid of it, once something in your call stack uses it, you have to be IO as well. In contrast, if you use e.g. the
State
monad, you can resolve it at a higher level by running it, and you'll get back a pure value. 2. All the annotated side-effects your functions can generate are accumulated. So a function that needs an effect like
readFile : FilePath -> String
will be composable with a function that needs an effect like
openSocket : () -> SocketId
. The resulting expression will contain both effects. 3. Finally, all the effects don't actually refer to actual implementations, but rather, they refer to something like datatypes. At any point you can do something similar to what try-catch does to exceptions: You can handle effects. Thus, you can e.g. always return the same (mocked) string for
readFile : FilePath -> String
. Or you can accumulate all files written with the
writeFile : FilePath -> String -> ()
effect. If you want to learn more about algebraic effects, I recommend looking at unisonweb.org. Other than that, there's the typescript-like language koka (microsoft research) and the paper for "Frank", horribly named "Do be do be do".
@Chris Knott I know some people say 'Haskell doesn't have side effects', and I know why they do: From the perspective of haskell's type system, haskell doesn't have side effects. However, in reality, you can write and read files in haskell, thus, it has side effects.
It does annoy me that I can't actively mark something as pure and have it enforced.
This is what motivated me to answer you: In haskell, if a function is annotated with
IO
, including this computation in the
main :: IO ()
computation will run its side effects. If not, it's pure. I feel like this is exactly fulfills your wish. However, writing IO functions is not very common in haskell. In fact, you try to write as much pure logic as possible, before your call stack approaches the outermost layer of your application, which is written in IO. I feel like this is one big difference between haskell and python, and it's not about functionality, it's about conventions. In terms of functionality, there's one big difference between haskell and python: Types. However, if your goal is annotating pure-ness and statically enforcing this, you'll probably need some form of type system (even if only two types exist "has side effect" and "doesn't have side effect"). So... yay types, yay haskell? 🙂
🤔 1
c
@Mariano Guerra in many languages + can have arbitrary side effects, e.g. in c++ the operator might be overloaded, in Python it might have ___add___() set, and therefore it could do anything. (People use "format your HD" as a deliberately exaggerated example of a side effect)
d
Right now, the FP community is messing with row types and algebraic effects. There is also this fun document on coeffects: http://tomasp.net/coeffects/
e
I want to see a good open source implementation of state charts (note: state charts, not state machines). I think all the robust implementations are commercial, hence harder to gain wider acceptance. I still have to keep up to Harel's new work, he's still working on this area (ex: recent paper https://arxiv.org/abs/1911.10691). I don't know what scenario-based programming is but I bet there's something good in there 🙂
a
I was literally just thinking yesterday, "dang, algebraic effects are the future". This paper from MS Research is a nice intro, especially section 2. https://www.microsoft.com/en-us/research/publication/algebraic-effects-for-functional-programming/
g
i don’t understand why we can’t treat all effects as continuation capturing points, use parsing and interpreting to order those continuations hierarchically, and also provide a “script” which is basically a list or tree of dummy inputs and outputs. but i like barely understand continuations 🤷‍♀️
a
Um, we can't? You can definitely think of effects as captures (at least in the form presented in the article I linked), but I'm not sure what you mean by ordering them hierarchically. Do you mean for the purpose of providing isolated simulations?
d
@Philipp KrĂźger said
I know some people say 'Haskell doesn't have side effects', and I know why they do: From the perspective of haskell's type system, haskell doesn't have side effects. However, in reality, you can write and read files in haskell, thus, it has side effects.
A side effect is an effect that happens on the side. I/O actions are effects. Haskell has effects, but it does not have side effects.
💯 1
I sympathize with what Mariano wrote:
I don't like to write code full of side effects, hard to test, hard to reason about, hard to know all the edge cases. I also don't like mocks and doing dependency injection everywhere to be able to switch the side effect parts is a lot of work.
One of my hobby projects is to make a practical programming language that is easy to use, easy to understand, has a low barrier to entry, and has simple semantics. In my language, all functions are pure (function calls do not have side effects), and there is no shared mutable state, because that leads to simpler semantics and less cognitive load. I think my goals are different from functional programming researchers, because there, the focus is on complex type systems. Eg, Haskell is a very complex language with a steep learning curve. Whereas my language is dynamically typed. As for "the future of side effects", I think there is a much larger design space for side-effect-free programming languages than what is currently explored by FP researchers focused on higher order type theory.
💯 1
The big advantage of Algebraic Effects seems to lie in the fact that it is a type system for old style imperative code, and functions with side effects, in which the type of a function includes its side effects. That seems like an incremental improvement to the current state of things, with backwards compatibility to the old coding style. Side effects are more visible and more controllable, giving at least some of the advantages of using a side effect free language. I'm not sure Algebraic Effects provides all the advantages of side-effect-free programming. If you've got a big web of shared mutable state, and you call a function that modifies some of that state as a side effect, AE can tell you that there are side effects but I don't see how it helps you understand what state is being modified. In side effect free programming, the connections are explicit, since you can't modify state that isn't passed as an argument.
n
I think the biggest assumption in this thread is that the Future of Coding even involves the concept of a “function” 😇. I encourage people to explore possible alternatives to “callable units of code” — my planned (general purpose) language doesn’t have it! Thus it’s not even possible to talk about side effects.
👍 1
d
@Nick Smith In my planned language, there are two kinds of "callable units of code": functions and procedures. Functions are pure mathematical functions with no side effects, and they compute a value. Procedures perform an effect, such as an I/O effect. I don't do logic programming, so correct me if I'm wrong, but in Prolog, you don't have functions. You have facts, rules, predicates, and evaluation is logical deduction. I/O is done using magic I/O predicates that perform I/O as a side effect of deduction. So Prolog models I/O using side effects, even though it doesn't have functions. How does I/O work in your language?
a
@Doug Moen in the paper I linked, Algebraic Effects are applied in a functional language (and can express a purity much stronger than Haskell's). They can be arbitrarily fine-grained, and the calling context has full control over how the effects are expressed (so yes, you can tell what state is modified ;) ). It's semantically very close to returning actions, but with friendlier control flow.
n
@Doug Moen I’ll first mention (as I do often) that I consider Prolog to be a horrible exemplar for logic programming because whilst it features deduction, it is also steeped in imperative concepts (side effects) and functional concepts (some relations have “input” and “output” parameters). It’s a big mix of several paradigms. My own plan for I/O: the state of the program is just a set of deductions. Rules can only make deductions: they have no additional effects. Relations do not have “inputs” and “outputs”: they are just a set of deductions. How does a program interact with the external world? External agents just look at the program state and execute actions accordingly. If the program wants to flip on a light switch, then it just puts a tuple into its state (“light switch”, “on”) and an external “switch-flipper agent” (pre-configured with permission to view a portion of the program state) does the flipping. These agents are managed by the operating system: they aren’t part of the programming language or its libraries. External agents can also write tuples to the program’s state, of course. I plan to use a model of time that accommodates such interactions and allows a distinction to be made between “events” (momentary tuples) and “state” (persistent tuples). All I/O must be asynchronous in order to have a clean way to define state transitions (observe event -> deduce new state).
😀 1
❤️ 1
m
For starters, I’d like to put more (“as many as possible”?) effects on the left hand side of an assignment statement, so they are distinct from non-effects. That way, it becomes easier to keep the functions/methods pure.