3 ms·
This is one reason I go out of my way to divide functions between those that return something and those that do something. In JS I use class methods as a shorth
by brundolf 6y ago
This is one reason I go out of my way to divide functions between those that return something and those that do something. In JS I use class methods as a shorthand for the latter: "this will probably mutate something". Plain functions, then, are relatively safe to treat as pure/remove if their return value isn't used.
- Blikkentrekker 6y agoIt seems very bad design to begin with to have a function both return a value and be non-transparent in any other way than what it needs in order to return said value. But in some cases, this is needed for efficiency.
- tsimionescu 6y agoI think about it the other way. If you are doing something effectful, how often is it useful to also return some information? To me, the answer is 'extremely often', so I don't see the value of what GP is proposing. It's nice to have pure functions and some kind of separation, but it's not worth it to give up returning values from effectful procedures.
- Blikkentrekker 6y agoOnly to return information about the effect's success, which would again make it easy to see that the function must have some effect, if all it returns be true or false or another trivial value that could be constructed from æther.
- tsimionescu 6y agoI think a general case is concurrent mutation of a data structure. If you are adding some item to a list in parallel with others, you will usually need to return some token (e.g. index) that will allow your caller to retrieve that item later. You MAY be able to get away with making this a pure function by returning a shallow copy, but I believe that depends on how central the sharing aspect is.
- brundolf 6y agoYeah, so the problem here is that the value creation logic is non-deterministic; not only does it depend on hidden state, it doesn't even do so predictably because you don't know the order in which other threads might interplay. I established three categories of imperative-logic-return-values above: > The return value is either going to pertain to the state before the side-effect, the state after the side-effect, or the side-effect itself. The first two could easily be broken out into pure functions. The third, again, mostly comes down to error reporting. So this definitely falls into category three, which makes the coupling legitimate. It's possible I was wrong in my statement that that category "mostly comes down to error reporting"; it was just based on my own past experience.
- brundolf 6y agoIt probably varies by domain - most of my deep experience is on the front-end - though even outside of that, I can't think of many cases outside of errors where I fundamentally need to return something in tandem with a side-effect. The return value is either going to pertain to the state before the side-effect, the state after the side-effect, or the side-effect itself. The first two could easily be broken out into pure functions. The third, again, mostly comes down to error reporting. I guess things get more complicated when you consider I/O. A function that gets a file handle or makes a GET request is technically impure and mutates the state of the macro-system, and yet is primarily concerned with what it returns. I tend to lump these into the "plain functions" bucket, but it's a gray area.
- magicalhippo 6y agoWe're in the procedural world, but a typical case we have is a variation on this theme: A function takes some declaration id, generates one or several records based on the declaration data, commits them to the database and returns the record id's so they can be used in the UI or similar. Either the record id's are displayed to the user directly, they're used for filtering in a grid, used for generating response files to external system, or something else. Often this would get called several times for the same declaration id, usually generating new records, so it's easier to have the function return the id's rather than trying to reconstruct what it did. Could be there's an easy way to map this to pure functions, I haven't thought about it. Would be interested to know though.
- brundolf 6y agoWhere my mind initially goes is to have a pure function going from id -> records, and then pass those records off to a separate procedure that persists them to the database. The same records can then also be passed around to the UI or whatever else. > Often this would get called several times for the same declaration id, usually generating new records, so it's easier to have the function return the id's rather than trying to reconstruct what it did. Are the "new" records meaningfully different in some way, or are they just new instances in memory of what is fundamentally the same value? In the former case, there's state somewhere that's causing each one to be different (there is not a pure relationship from id -> record set). In the latter case, you don't actually need multiple instances (unless you expect them to be mutated by other code down the line and are guarding against side-effects, in which case you could defensively clone the pure function's result as needed instead of calling it multiple times). Edit: it's possible I misunderstood, and that what's happening is you're inserting data into a database which has its own auto-incrementing IDs that it then returns to you. If so, the coupling between value creation and state/mutation exists in the database's API itself, and so is outside of your control. There's not much you can do in that case because you don't own that code.
- jfmengels1 6y agoIn pure functional languages (at least in Elm), what happens is that you have data that describes what should be done, instead of doing it directly. is talk called "Effects as Data" describing that idea: https://www.youtube.com/watch?v=6EdXaWfoslc https://www.youtube.com/watch?v=6EdXaWfoslc
- brundolf 6y agoI'm aware; I'm not sold on this most extreme version of pure-functionalism. I think it's unintuitive, syntactically clunky, and hard to reason about, despite the benefits of purity. I'm a believer in the functional core/imperative shell approach.
- Latty 6y agoI thought similarly until I committed to working on an Elm project. I developed an open source game in Elm, and I've been very impressed with the benefits of the pure functional approach. It takes some getting used to, for sure, but it makes things really simple to reason about once you get used to it. There are definitely rough edges still: Elm in particular is just missing a lot of stuff, but I was surprised how much value the pure functional approach gave me.
- closeparen 6y agoThis is exactly how I understand functional core/imperative shell. The functional core returns a structure describing what should be done and the imperative shell goes out and does it.
- nerpderp82 6y agoI often use a pattern where I do a batch and group a bunch of operations baked into a tuple that describes the computation. Instead of do_thing(on_data), the intermediate pass would emit a data structure full of list([("operation_do_a_thing", on_data), ... ] and then a command executor can do the actual scheduling of the operations, serial, parallel, batched, try/except with backpressure, etc. Separate the thing you want done from the doing it, because when you intermingle the doing-the-thing it greatly complications the scheduling logic. Think of it as a working programmers io monad.