First, there were obligatory stack effect declarations on each word. Want to refactor your program? Sure, rewrite your words, together with stack effect declarations. That part was extremely annoying when it came to exploratory programming. For the uninitiated: stack effect declarations are akin to function prototypes in C, only lacking type information. IMO, once you have them, and they're obligatory, you get all the drawbacks of postfix languages, and no benefits.
Second, the language and standard library rely heavily on stack combinators. Reading the standard library code requires intimate familiarity of what words like bi, bi@ and bi* do (among a whole lot of others).
Third, I find Factor's documentation extremely confusing. For example, there are "Vocabulary index" and "Libraries" sections, with little or no overlap between them. But vocabulary is a library (or package, whatever, same thing), so WTF?!
Then there are important and powerful features like generic words, but if you click on the "Language reference" link on the docs homepage, you get a menu with no mention of generics, and you have little clue in which section to look for them. (It's under objects.) Then you eventually find out (sorry, I was unable to dig up reference to the docs) that generics support only single dispatch, and only "math" generics (plus, minus, etc.) support double dispatch.
In short, the manual is a maze of cross-references with no head or tail.
Fourth, I dislike writing control structures in postfix. This is the part that, IMO, RPL on HP's calculators got right. Instead of forcing you to write something like
"Less than 10"
"Greater than 10"
[ < ] 2dip if ! 2dip is a stack combinator, guess what it does!
IF 10 < THEN "Less than 10" ELSE "Greater than 10" END
Last but not least, it supports only cooperative threads.
10 < [ "less than 10" ] [ "greater than 10" ] if
The unusual names lke 'dip', 'bi', etc are just common names that once learned make code easier to read. 'dip' is from Joy IIRC.
I do agree that combinator heaviness can make things difficult for a beginner. Especially if code uses a combinator that isn't common it requires time to look up and see what it does. You really need to immerse in Factor development for a while to get familiar with them.
Stack effect declarations exist to make refactoring safer. Prior to having them enforced by the compiler it was common to change a words stack effect only to find it broke random code using it elsewhere. Making the check enforced provided a safety check that you hadn't forgotten to change word usage elsewhere.
The refactoring problem could have been solved in a more practical way (though more difficult implementation-wise) by implicit versioning of symbol definitions. Plus, I find that stack effect declarations read like line noise (see for example the declaration for if; IIRC from browsing the docs it's one of the moderately bad ones.)
The postfix notation even for conditionals is really not an issue when you dive into the language. However, with most systems you should be able to translate IF into 'nop', THEN into IF, and END into THEN and it should do the trick. But then there are DO WHILE loops. etc. But everyone prefers to preserve the postfix logic at the end of the day. It's like infix notation systems in Lisp and Scheme that never caught up (and, I believe, never will).
About your second point: most of the library code, in any language, looks like gibberish and like a mess of arbitrary idioms. One simply cannot hope to inspect and understand non trivial library code just just by casually reading it. With Forth the situation is indeed even worse, because implementers generally cannot resort to spaghetti code too much, whose continuous nature, at least, helps the casual reader.
On the topic of cooperative multithreading, I think that the trend in recent programming languages in general is that, nobody wants to deal with pre-emptive threads anymore. It hurts kitties, it is dirty. It is like manual dynamic memory management. Cooperative threads (if I'm not mistaken, they're also sometimes called "lightweight threads", "green threads" or "fibers"), avoid most of the problems of preemptive threads (because the programmer controls when execution can switch to another thread and when it should not very easily), while retaining some benefits (background execution of low priority tasks). For a "modern" Forth system, I think it could be interesting to keep cooperative threads at user level (really hardcore implementers may just remove them completely, because it is expected that the programmer may prefer to implement some sort of cooperative tasking fitted to his needs), and use preemptive/OS threads in library code (typically implemented in C).
Now that's the point of view of a 'hardcore' kind of hobbyist Forth user (and implementor). There is another category that finds brutal simplicity simply not practical and I can understand that. But really for educational/curiosity purposes I would recommend the hardcore way. Like, abandon all the bloat you're addicted to, shut the up, try hard and sincerely. I tried Forth this way because I was very sceptical when reading Moore (inventor of Forth) and Fox (one of his collaborator and friend; Moore isn't very good at communication, so Fox [RIP] was sort of his Plato) that their approach could work. These experiments in extreme simplification brought me a lot when I was a young programmer.
That would be because Factor is OO.
: names ( -- seq )
"names.txt" ascii file-contents [ quotable? ] filter "," split ;
: score ( str -- n ) [ 64 - ] map sum ;
: solution-22 ( -- n )
names natural-sort [ 1 + swap score * ] map-index sum ;
"hello world" print
I see no semantical difference between
if(expr1) expr2 else expr3;
expr1 ? expr2 : expr3;
If-Then-Else is a statement. It doesn't return a value. It's just a branching operation between the two blocks.
The conditional operator is an expression. It returns a value hence it has a type. And that's where things start to be messy. Typing rules for the conditional operator are, well, not trivial and varie from language to language. Let's use an exemple from Java Puzzlers (Joshua Bloch and Neal Gafter) :
char x = 'X';
int i = 0;
System.out.print(true ? x : 0);
System.out.print(false ? i : x);
will print X88 but the same thing in C++
char x = 'X';
int i = 0;
cout << (true ? x : 0);
cout << (false ? i : x);
And I am not even talking about the priority mess when you try to use a conditional operator in a conditional operator.
You could expose the same semantic difference your example relies on with overload resolution, without needing to refer to the ternary operator at all. But I hope you wouldn't take that to mean that overloads are also a bad idea.
I also believe that the ternary operator is particularly nice when used in combination:
x = a ? foo :
b ? bar :
c ? baz :
As for the priority mess, I spend my brain cycles elsewhere and use parentheses, which also helps the next guy as well as me.
I never ever remotely said that. You are putting words in my mouth and I don't like it. I just rightfully pointed that there actually is a semantic difference between the two mentioned lines. I never implied using any of them was wrong. I didn't even emit a judgement of value on either construct.
Now, I don't have problem per see against the conditional operator. It's just not as simple as some people like to pretend and it makes your code depends on some non obvious and not broadly known language feature for what is usually a minimal gain. So, yes, I do tend to avoid it in anything which is going to end in the hand of others (in the same way I try to avoid any un-obvious language idiom as much as possible). It's like nested comprehensions in Python, the slightly shorter code produced is just not worth the added complexity.
"hello world" uppercase print
print(uppercase("hello" + "world"))
λ>putStrLn . reverse . map toUpper $ "hello world"
I'm sure it's just a matter of what you're used to.
Next, there's the idea of the stack. Every word is executed, one after the other, and the words operate on the stack. So the four-word sequence 3 4 + . puts a 3 on the stack, then puts a 4 on the stack, then pops the top two numbers from the stack and puts their sum on the stack, then prints the top number on the stack. So the result, naturally, is 11. (Well, it is if you're operating in base 6 at the time. Didn't want to make it TOO easy for you!)
Oh, and there's also the concept of interpreting vs compiling. The colon word, which is spelled :, reads the very next word from the input and begins the definition of that word. Every word it encounters up to the next semi-colon is compiled, not executed. So if I did this
3 4 + : FOO SWAP DROP ; .
So the short form is: begin reading at the top left, continue rightward and downward until you reach the bottom. There's no syntax as such, like Perl's die "Can't open file" unless $fileopen; because Forth-like languages don't read ahead to the end of the line.
[Edited: HN doesn't do Markdown. WTF? Also, I don't know my left and right.]
And it's a very coherent idea. It's meant to respect processor architecture while maintaining a good level of comprehensibility to the human. And it keeps a MUCH closer correspondence between "word" (essentially a routine) and actual instruction. That got WAY lost with C, enter the beast-compiler.
I think of it as a concept layer above assembly, that has features far beyond just mnemonic value. It's like Lisp to me- a language whose "syntax" is dictated by the functional necessity of a computing methodology, not the decisions of language designers. For me at least, the approach leads to much higher productivity for a variety of reasons.
There's also a very high probability that Forth is the first thing to be run when you turn on the computer or cellphone you're reading this from.
Now as to Factor... see my post above and help me out!
Thinking about Python, looks wise it is kind of the opposite of any of the stack languages. I get the feeling that you can like one or the other.
1) various 8-bit BASICs, 6502 ASM, Modula-2, 370 ASM, C, Ada, foxbase, SQL, Postscript, Forth, Objective-C (weird listing it out)
Firing up the Factor GUI environment you can access help via F1 where two options are the cookbook and the "Your First Program" tutorial.
This same documentation available in the environment is also available online.
I could never get over the syntax though - even though the #factor folks say its a matter of getting used to and theoretically not "harder" than Lisp, Lisp feels very natural to me, but Factor always required a big overhead.
I have always admired the engineering work on Factor, but had thought the project would die when Slava Pestov took his job at Google. Great to see it continue to improve.
On ARM it would be interesting- or if it had a highly modular structure that would allow it to be "easily" ported to other microprocessors.
But on x86? What niche is it trying to serve? Is it a language-lover's-language? I guess what I'm asking is, why would I use a "high-level" Forth on x86? There has to be a reason, I'm just missing it.
I used to run a video sharing website written in Factor , sadly the server crashed a few weeks ago and I haven't recovered the data from the disk yet.
Factor feels more like a Scheme than a Forth to me when programming.
Currently working on an ARM forth-idea with some scheme-ideas. There's this nagging intuition I have that there's a symbiosis between Lisp atom/Forth word and S-expression/Forth dictionary.
I have to get something going pretty quickly so I can't get too pure about the idea, as I don't want to get bogged down in the necessary GC yet.
Thanks for your reply, it already made me more interested in Factor from an implementation standpoint, even if I wouldn't use it on x86.
Theoretically you would have the HLL incredibleness of a lisp with the low level genius of Forth. I asked some old timers if the idea made sense and they were silent in specifics but not discouraging. Which would indicate to me they think it is a huge learning opportunity for me one way or the other. Have you done any deeper analysis?
This was very useful in the early days of Factor when language and base library changes caused bitrot in contributions. If the contributed library wasn't updated it was moved to an 'unmaintained' directory for later correction.
"hello world" print
EDIT: I'll plug my own blog for this too, as I started writing a Beginning Factor series of posts a long time ago , but never got beyond a couple entries.
From your second article introducing Factor
"Next time we’ll stick with a theme of “flow” and discuss control flow for your words and after that, the typical work flow when developing code with Factor. Hope you enjoyed this installment!"
I am still waiting for that article. Your introductions to Factor are awesome and more would be delightful.
Personally, I've fallen in love with it a few years ago. Even though I don't use Factor for anything practical, the language itself (as well as Forth) has taught me a completely new way of thinking about problem solving. Something that I benefit from in other languages I use.
So if there is one reason you should at least check it out, it is that it will make you a better programmer. And I think anyone who's been writing code long enough, will know that there is always room for self-improvement.
"10:10:24 <RodgerTheGreat> another HN gem: "I must say, that the syntax and semantics are not intuitive at all. I've spent half an hour trying to learn how does this thing work, and have not been able to.""
It's horrible to watch when a language community has this sort of a elitistic attitude, especially when it's doubtful that the language has any future.