can't help but chuckle at the accomplishment of 40 years of research in functional PL, ending up with almost C-like syntax once again.
can't help but chuckle at the accomplishment of 40 years of research in functional PL, ending up with almost C-like syntax once again.
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).
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.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.
> 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.
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 #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. f(g(x), h(y));
is not too unusual, and g and h could be arbitrarily complex.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).
But if they look closer, they find out it is not really imperative. Why is there a difference between let and bind? Why can't I reassign values. And they start to use more exotic and also more interesting monads and boom the imperative feel goes away.
For example with the interesting LogicT[1] monad. Or what about the probability monad[2]? Or the continuation monad[3]? Or the reverse state monad[4]? How imperative are those really? It really depends how you use it. And I think it adds more to the magic, that you think it is just imperative code at first, because suddenly you can create code that behave very different under various monads that represent computation models. I think that is a cool thing about this confusion.
I understand that if you are a professional that needs to get work done, it can be annoying.
[1]https://hackage.haskell.org/package/logict [2]https://hackage.haskell.org/package/probability [3]https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-M... [4]https://lukepalmer.wordpress.com/2008/08/10/mindfuck-the-rev...
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']I too can't help but chuckle at some of the substance less commentary at HN, where chuckling is a substitute for inability to contribute anything to a discussion.
The last time there was someone chuckling, I was way more civil.
do
x <- a
y <- b
return (f x y)
isn't basically imperative syntax ?
hint: if you can get from it to C (or pascal or python or whatever) simply by adding a couple types, semicolons and replacing operators, it's C. let x = a
y = b
in (f x y)
? Which in turn is very much like OCaml syntax or plenty of other functional syntaxes.This could be doing any one of:
- flatmapping over lists
- doing a computation with Maybe types, producing Nothing if a or b are nothing
- doing IO
- managing futures
- pretty much anything, actually
Don't confuse 'x <- a' with assignment, or 'return' with returning a value.
Another way of looking at it, (f x y) might be invoked:
- once
- never
- more than once
- later
hint: you are wrong.
It isn't "almost C-like syntax", unless you define that to mean "if I do any amount of search and replace I can make it into valid C code that has completely different semantics". "x <- a" in haskell is not at all the same as "x = a" in C.
And that isn't the reason I said your comment is wrong. Your comment is wrong because do notation is not the result of "40 years of research in functional PL", nor is it an accomplishment. It is simple syntactic sugar that didn't come from research at all.
Your comment could be accurately summarized as "I would like to express how smug I feel for not learning something".
are you kidding me ? if a 2016 paper from Simon Peyton Jones isn't research then what is ? (https://dl.acm.org/doi/pdf/10.1145/3241625.2976007)
But your chuckle has shown me that this code is just plain old C, with links to documents you haven't even read. Thank you for enlightening me, great chuckler.
Here is some basic reading material for you. Lets see if you can follow this
https://hackage.haskell.org/package/async-2.2.2/docs/Control...
do a1 <- async (getURL url1)
a2 <- async (getURL url2)
page1 <- wait a1
page2 <- wait a2
This implements async await in Haskell via a library using Monads. Hint: you cannot do this in C via any library. You need to modify the language. If you are uneducated about Monads, you might think this is similar to some kind of C code. It is not!Here is the same do syntax being used to parse source files instead of doing async/await IO. Yes this parser will also perform backtracking etc, which will not be obvious to anyone who thinks this is some kind of transliteraton of C
https://markkarpov.com/tutorial/megaparsec.html
mySequence :: Parser (Char, Char, Char) mySequence = do
a <- char 'a'
b <- char 'b'
c <- char 'c'
return (a, b, c)
Hint: If you want to do something like this in C, you use yacc/bison, a parser generator tool - there is no library. And it is fucking ugly.Hint 2: There are monads for parsing, async IO, exceptions, nondetermenism, monte carlo .........
Hint 3: The parser libraries are 25 years old.
Hint 4: Monad libraries represent less than 1% of PL research over the last 40 years.
Hint 5: C# LINQ is basically a Monad.
Hint 6: When confronted with something you don't understand, it is better to be quiet instead of opening your mouth and advertising your ignorance.
Hint 7: If your conversational style consists of chuckling and providing "Hints", be prepared for receiving a taste of the same medicine.
> This implements async await in Haskell via a library using Monads. Hint: you cannot do this in C via any library. You need to modify the language. If you are uneducated about Monads, you might think this is similar to some kind of C code. It is not!
it's still pretty much
> almost C-like syntax once again.
at no moment, not a single time, did I say in any way that I was talking about semantics but this is apparently a hill you wish to die on... so, by all means have fun !
Maybe you'll be more convinced by the exact text of the paper that introduced do-notation though ?
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.11....
let me quote it :
> Using monads gives functional programming an imperative flavour. Gofer supports a, so called, do notation which makes this imperative flavour more apparent.
or, let me rephrase that whole thing for you in a language that you seem to speak quite fluently:
class BasicallyC m where
(>>=) :: m a -> ( a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
class BasicallyC m => ImperativeFlavour mYou also failed to show how parser combinator grammars such as yacc/bison/BNF forms are basically just imperative code.
Monads model effect systems; not imperative code.
I am impressed by your ability to argue about stuff you know nothing about, and linking to papers and documents you can't even read. Have you considered running for US president?
but async / await is semantics, not syntax, thus completely irrelevant to the conversation. you want an example of building async / await into the syntax of C as a library ? tada !
void* async(...) { return NULL; }
void await(void* ptr) { }
int main(int argc, char** argv) {
void* handle = async(printf("foobar"));
await(handle);
}
or maybe you would prefer the more "modern" version, still compatible with pure C89 syntax though ? #define async
#define await (void)
int main(int argc, char** argv) {
auto x = async printf("foobar");
/* wow, you can even compose it ! */
auto y = async 2 * x;
await x;
}
please go on, this is super entertaining !This thread is silly.
I'm actually not sure what the C-like syntax you're referring to is? The fact that it reads like imperative code (even though it could be defining a parser or something not imperative)? Or the fact that it uses the word `return` (many use `pure` instead..`return` is free for anyone to bind a variable too)?
They're both equivalent so it should almost be expected that if you go far enough along the horseshoe you end back on the other side.
A bit like how Java eventually got lambdas.
But when the capitalists come up with communism, it is interesting to observe the implementation they come up with after travelling the road they have come, much like when the communists come up with capitalism...