The ambiguity produced by stack effects is also paralleled in most infix procedural languages. C++ recognizes 18 levels of operator precedence[1] with two types of associativity when evaluating expressions, and any of those operators can be overloaded to produce side effects for different combinations of types. Sometimes this behavior is intuitive, other times it can be deeply confusing.
For what it's worth, my experience is that practice and good factoring can make concatenative languages much less cryptic. In Forth, the ideal size of a procedure is about a line, and procedures that take more than three arguments are considered a "code smell" that suggests a different factoring might be more appropriate. When you only care about the top few stack elements within any given definition there's a lot less to juggle mentally.
> The ambiguity produced by stack effects is also paralleled in most infix procedural languages. C++ recognizes 18 levels of operator precedence
Yes, but all of those operators are binary operators, instead of let-me-look-at-the-documentation-ary, lots of those precedence rules are drilled into people at a young age, and lots of developers compulsively wrap them in parentheses anyway, just to make their intent absolutely clear.
All of that is very different from the experience of a concatenative language.
Although I think you're right that there is less variation, they are not all binary. -, +, & and * all have unary and binary variants, while !, --, ++, ~, new, delete and delete[] are always unary, and of course the ternary operator is always ternary.
"In Forth, the ideal size of a procedure is about a line, and procedures that take more than three arguments are considered a "code smell" that suggests a different factoring might be more appropriate."
That might be the ideal, but if you take a look at real-world Forth code, most of it is much longer than the ideal.
There are a couple of other things that help readability in Forth. One is thorough commenting, and the other is the use of intermediate variables.
Unfortunately, real-world Forth code often doesn't have much of either -- intermediate variables are often frowned upon as being "un-Forthish", and thorough commenting results in a program that is so verbose that (except for the embedded domain) you might as well have written your code in a more conventional language in the first place.
For what it's worth, my experience is that practice and good factoring can make concatenative languages much less cryptic. In Forth, the ideal size of a procedure is about a line, and procedures that take more than three arguments are considered a "code smell" that suggests a different factoring might be more appropriate. When you only care about the top few stack elements within any given definition there's a lot less to juggle mentally.
[1] http://en.cppreference.com/w/cpp/language/operator_precedenc...