News for hackers. Stuff that matters.
Re:Railway-Oriented Programming (2015) by KirinDave on Tuesday June 19, 2018 @01:16PM (#17348464)
> Unless we know there are data dependencies between s1 and s2 (and we don't; we're talking very abstract here)

The entire discussion is about railway programming for early exit on error handling. So yeah, there are dependencies there.

> You're purposefully twisting words here.

No. I'm pointing out you pattern matched against a generic 2-tuple in your pseudocode. There's no words to twist, returns in functions on modern hardware are atomic over 1 value in all but the most obscure languages, and it's not a new phenomenon.

If it's valid to say, "I returned 2 values" it's equally valid to say, "I returned one value and restructured it." They represent the same thing.

> And no, that's not "the type", because I was (obviously) just using straight forward pseudocode whose semantics I assumed would be intuitively clear to everyone.

Both statements can be true. I'm simply writing a type that captures your "obviously true" statements. Therefore, both are obviously true.

> There is so much hair-pulling in here and what you say is simply wrong. I was just returning two friggin' variables.

You're the one consistently lambasting folks about paying attention to what happens at the "bare metal" (ha!) level. I encourage you to take your own medicine here.

> Why do you make it seem like it was something more complicated? You must be deliberately hair-pulling just to win a stupid argument. I'm not doing any "virtual object-oriented blah blah" or any kind of types craziness.

Either you wrote something so vague it can be anything you want (e.g., your magical streams that are also not streams). If we remove the nonsensical (or I guess by your admission, non-sequitur) streams bit then you're left with this outcome. I told you exactly what I'd do in my post. You have to recognize the type of the result of each step as either a success or an error.

You've chosen to pretend none of this is about error handling, which is pretty boring IMO. Error handling is what makes code actually hard to write.

> What's wrong with my procedural code? Fill in the error handling between the steps as soon as you have a concrete instance for my abstract example.

I admit I don't know since it seems to mean exactly what you want it to, and no one else is privy to either the viewpoint nor the problem you're actually discussing. You're just here hurling vague insults about how everything other than your "obvious" pseudocode is nonsense.

> Dude, I used a pseudo-code for loop.

Not in any example I reviewed that didn't completely ignore the actual problem to burn down a straw man about streaming constructs. Also, don't call me dude. I'm not.

> I've been there and I know to use FP-ish constructs when they make sense (very rarely. Much easier to restrict to procedural altogether).

Considering you didn't recognize the product pattern match and objected vigorously to the idea that it might be as such; and tuples something nearly every statically typed FP dialect has? I gotta confess that I don't think you know the field or the techniques very well. Fair enough, many modern FP techniques in use today are less than 25 years old. No shame there. But you should probably investigate a bit more before speaking so confidently on the subject.

You really are just advocating for doing what the F# example given was doing, without all that helpful syntax or compile-time verification of correctness & totality. I dunno why you'd want that. Given that you are accidentally reusing FP pattern matches, they can't be that complicated now can they?

Re:Railway-Oriented Programming (2015) by jstimpfle on Tuesday June 19, 2018 @01:24PM (#17348540)
> The entire discussion is about railway programming for early exit on error handling. So yeah, there are dependencies there.

"Early" does not have anything to do with "dependency". You can do a million unrelated things in an order of your choice and abort at the first error. There need not be any dependencies.

> returns in functions on modern hardware are atomic over 1 value in all but the most obscure languages, and it's not a new phenomenon.

This is true and totally unrelated to my point.

> No. I'm pointing out you pattern matched against a generic 2-tuple in your pseudocode.

Again, it's pseudo-code, so no, it need not be a "generic 2-tuple", not even needs to be a "tuple" (whatever that means in a given PL). In a real implementation the types are known precisely (Just like the types of the alternatives in an ADT are known precisely). There is no kind of "matching" (there are no conditionals) whatsoever. s1 and s2 are simply two variables (for example pointers, in C) of precisely known type. And the process_data() function returns two values that get assigned to the variables. It's the simplest kind of code.

The whole point of the discussion was whether to design the output as a single stream of ADT values, or as multiple streams of precisely known values. In this context it's totally clear why my (single) assignment to s1 and s2 is not the "matching" that you need to do on the (many) values of the single stream in the ADT version. Really I don't care how hard you want this assignment to s1 and s2 to be a matching/destructuring of a "tuple"; It doesn't change anything and is totally unrelated to my point.

Sorry to call you "dude". I had already decided to remove that word before reading your answer.

Re:Railway-Oriented Programming (2015) by KirinDave on Tuesday June 19, 2018 @02:03PM (#17348933)
> Again, it's pseudo-code, so no, it need not be a "generic 2-tuple", not even needs to be a "tuple" (whatever that means in a given PL). In a real implementation the types are known precisely (Just like the types of the alternatives in an ADT are known precisely). There is no kind of "matching" whatsoever. s1 and s2 are simply two variables (for example pointers, in C) of precisely known type. It's the simplest kind of code.

That's not different syntactically or even mechanically from pattern matching over a generic 2-tuple. So if you want to do this as a social convention, that's fine. I'm not sure why other people would adhere to such a convention.

> The whole point of the discussion was whether to design the output as a single stream of ADT values, or as multiple streams of precisely known values.

No. The whole point of the talk we're discussing is that a single stream of chained operations that have failure states is difficult to cleanly assemble without ADTs. You've backed away from the error contingency in your argument. To the uncharitable onlooker, this appears to be because ADTs solve the problem elegantly and procedural programming turns into am error prone, one-off pile of if-then-else spaghetti. See also: every complaint about Golang error handling.

Ignoring the Success-or-Failure nature of the talk is to fundamentally ignore the problem it's trying to solve. If you DID have two streams of values that had no contingency, we wouldn't write code this way in any runtime with async primitives.

> Sorry to call you "dude". I had already decided to remove that word before reading your answer.

Thank you.

Re:Railway-Oriented Programming (2015) by jstimpfle on Tuesday June 19, 2018 @02:17PM (#17349053)
> The whole point of the talk we're discussing is that a single stream of chained operations that have failure states is difficult to cleanly assemble without ADTs

And I suggested that this is overly complicated and procedural approaches are better. In procedural code we don't "assemble chained operations". We do them one-by-one and handle errors appropriately.

And we can typically arrange the data and control so that error handling is much easier. That was my point.

> one-off pile of if-then-else spaghetti.

ADTs and FP techniques provide only syntactic sugar to avoid this spaghetti in the source code. But due to that they just encourage having the worst mess of control actually happen on the machine.

On the other hand, I demonstrated how to avoid this mess by decomposing data into individually homogeneous bins/streams/arrays/whatever. My example does not have a single if statement (yes, you might argue there are if statements hidden in the for loops). I clearly stated that it's often possible to choose this approach.

Re:Railway-Oriented Programming (2015) by KirinDave on Tuesday June 19, 2018 @03:29PM (#17349612)
> And I suggested that this is overly complicated and procedural approaches are better. In procedural code we don't "assemble chained operations". We do them one-by-one and handle errors appropriately.

Go does this. It's widely and I think rightly criticized for this. It's "easy" to do but not "simple" because it's prone to human error.

> And we can typically arrange the data and control so that error handling is much easier. That was my point.

You can do more work to validate in a separate pass if we're discussing data transforms. It's harder to do this for a series of chained imperative steps. The talk details an example making a series of API queries. It's fantastic to handle a CRUD loop with the ADT method because it lets you write a simple specification and collect errors out in the end, while reserving exception handlers for truly exceptional cases like network faults. F# is incredibly good at this kind of flow.

> ADTs and FP techniques provide only syntactic sugar to avoid this spaghetti in the source code. But due to that they just encourage having the worst mess of control actually happen on the machine.

You've said this multiple times, but it's just not true. The F# code is just a function that calls functions. F#, ML and Haskell compilers all aggressively inline these calls. You pay the exact same costs for validating an error exists in the Golang style, in practice. Indeed, lots of people generate Go from liskov-style templates JUST to get ADTs to make error handling less painful. It's gaining popularity in Swift and Rust as well. Folks there aren't trying to impress people with their FP knowledge, they're cribbing a technique that works and scales well.

> My example does not have a single if statement (yes, you might argue there are if statements hidden in the for loops). I clearly stated that it's often possible to choose this approach.

Your example, by your own admission, doesn't address error handling flows. You need conditional logic to work with operations that don't always succeed. You say, "Structure it so you never have them." I think a lot of FP people would agree with this (in fact, there is a really cool functional pearl about doing exactly this at scale: https://github.com/matt-noonan/gdp-paper/releases/download/j...) but they'd think about it less as a function of assurances and "thinking very hard" and more about using methods to make inconsistent code provably false at compile time.

Very few languages can actually encode formal notions of correctness, right now. Recently languages like Idris, Agda, Coq, and TLA come up as tools for formal correctness, where you can actually have your compiler prove your invariants and their implications.