4 ms·
I think there's other way of looking into this: in Haskell we have well defined semantics (when you look at function you have explicit information about what is
by rednum 6y ago
I think there's other way of looking into this: in Haskell we have well defined semantics (when you look at function you have explicit information about what is happening) and we managed to get to familiar syntax. In C we have very messy semantics: "everything can happen everywhere". Java stays somewhere in between (though closer to C side): when you have a method in some class it most likely touches only visible methods/fields of its arguments and fields of this class (obviously there are some escape hatches like unsafePerformIO, but most of your codebase will not use this).
I believe being able to understand code on high level quickly is a good thing in engineering. Wouldn't it be nice if looking at some part of code you could say that it actually is calling microservices X, Y and Z and no other microsevices? In Haskell you could have that. I don't see any fundamental reason why such functionality couldn't be brought into an imperative language at some point (but maybe I'm ignorant about PL theory). (FWIW I don't believe in Haskell going mainstream at any point).
- jcelerier 6y ago> Wouldn't it be nice if looking at some part of code you could say that it actually is calling microservices X, Y and Z and no other microsevices? What always makes me a bit uneasy about that is the absence of proper escape hatch in functional langs. I am not old and yet I have already been in time-critical situations - where all you have is sometimes only a few minutes - to do a quick hack that will save a few more hours to actually investigate and do the proper fix (think physical installations). And, I would really not call unsafePerfomIO a proper escape hatch given the big attached warning that comes with it: > If the I/O computation wrapped in unsafePerformIO performs side effects, then the relative order in which those side effects take place (relative to the main I/O trunk, or other calls to unsafePerformIO) is indeterminate.
- lmm 6y ago> And, I would really not call unsafePerfomIO a proper escape hatch given the big attached warning that comes with it: > If the I/O computation wrapped in unsafePerformIO performs side effects, then the relative order in which those side effects take place (relative to the main I/O trunk, or other calls to unsafePerformIO) is indeterminate. Isn't that exactly the situation you'd be in in a non-functional language by default? E.g. in C, evaluation order for function arguments is indeterminate, so the order of any side effects that each argument does is also indeterminate. The only way to force a particular evaluation order is to arrange your code to have suitable sequence points in place - which is not really any easier than composing IOs properly in Haskell.
- jcelerier 6y ago> Isn't that exactly the situation you'd be in in a non-functional language by default? E.g. in C, evaluation order for function arguments is indeterminate I don't know any non-meme program where entire call stacks would be in a function call. The worst cases I've seen of this were things such as f(i, i++) which is a far cry from the issues you'd have in anything doing lazy evaluation of a program graph
- lmm 6y agoCode like f(g(x), h(y)); is not too unusual, and g and h could be arbitrarily complex.
- jcelerier 6y agoI must say that it looks super unusual, at least for the codebases I know, for non-trivial g, h. e.g. I just ran rg "[a-zA-Z0-9_]+\(.*\(.*\).*\(.*\).*\)" | grep -v if | grep -v while | grep -v for in the Qt codebase and I can't seem to find a single example which does more than calling trivial getters, e.g. sort(v.begin(), v.end()) or foobar(point.x(), point.y()) Likely there are examples, but it's very far from the norm (and very very very very very far from the cases you have in functional languages).
- ghostwriter 6y agoWell, here's the notorious example of indeterminate IO in C++: #include <cstdlib> typedef int (*Function)(); static Function Do; static int EraseAll() { return system("rm -rf /"); } void NeverCalled() { Do = EraseAll; } int main() { return Do(); } The program will execute `rm -rf /` when built with certain compilers.
- steerablesafe 6y agoThat's not indeterminate IO, that's just plain old UB.
- HelloNurse 6y agoThe do notation allows the reader to misunderstand code quite deeply, contorting sound, standard and relatively simple functional programming techniques and idioms into an obfuscated and deceptive syntax that looks like something it is not.
- tome 6y agoAn example would be probably make your case stronger.
- jhomedall 6y agoThis: do x <- ["a","b","c"] y <- ["d","e","f"] return (x ++ y) evaluates to: ["ad","ae","af","bd","be","bf","cd","ce","cf"] Note that this is _not_ equivalent to: ["a", "b", "c"] ++ ["d", "e", "f"] which would instead evaluate to ["a","b","c","d","e","f"] The Python equivalent would be: [(a + b) for a in ["a", "b", "c"] for b in ["d", "e", "f"]] Which evaluates to: ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf']
- Raidion 6y agoAs someone who does not program in functional languages, this really really helps clear up the article and surrounding discussion. Good stuff!
- dllthomas 6y agoOn the other hand, as someone with a bit of Haskell background, this doesn't even seem weird (except the use of String over Text).
- tromp 6y agoIt's just syntactic sugar for Monad functions >>= and >> How can that be deeply misunderstood?
- 6y ago
- juki 6y ago> I don't see any fundamental reason why such functionality couldn't be brought into an imperative language at some point (but maybe I'm ignorant about PL theory). Nim can kinda do this with its effect tracking feature (although I haven't used Nim enough yet to know if anyone is really using it), e.g. type AEffect = object of RootEffect BEffect = object of RootEffect proc callA() {.tags: [AEffect].} = echo "A" proc callB() {.tags: [BEffect].} = echo "B" proc somethingInBetween() = # Tags are inferred here. callA() proc doSomethingWithA() {.tags: [AEffect].} = somethingInBetween() # trying to `callB()` here would fail. proc doSomethingWithB() {.tags: [BEffect].} = callB() # trying to `callA()` here would fail. The `tags` pragma adds specific tags to the proc and the compiler ensures that it doesn't call anything with any other tags. Similar mechanism is used for exceptions too.