Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Joy (Programming Language) (wikipedia.org)
98 points by reedwolf on May 2, 2020 | hide | past | favorite | 42 comments


It's a functional programming language where every function is of type stack -> stack.

I like concatenative languages[1] and especially pipelines[2], but IMO Forth-like stack-oriented languages are in the wrong direction, b/c they force programmer to manipulate arguments on a stack, which feels hackish.

And my first real physical computer was an RPN calculator. As a kid I wrote lots of programs for it.

Also b/c of their simple implementation, Forth-s and stack-based VMs are frequently used in the wrong domains, i.e. Bitcoin's Scripts and Ethereum's EVM.

APL-family languages are better in this regard, but they also feel hackish in the general case, especially when passing arguments to nested functions. Copying RTL-style from math notation is also wrong. You are typing LTR (at least in English), but the expressions are evaluated RTL. Here Forth, RPN, and UNIX pipelines got it right.

The interesting direction would be a programming language with following attributes:

  - Purely-functional
  - Statically typed with an algebraic type system
  - Concatenative [1]
  - Tacit / Pipelines [2]
  - Homoiconic
  - top-to-bottom, LTR
[1] https://en.wikipedia.org/wiki/Concatenative_programming_lang...

[2] https://en.wikipedia.org/wiki/Tacit_programming


I think your list basically describes http://kittenlang.org/ which sadly has kind of halted in development because the sole dev working on it couldn't get anyone else on-board. Probably because it occupies a pretty rare niche of computer science interests.



It's a direct inspiration - hence the name ;)


I appreciate both Forth (and Lisp) and have both used and implemented several (mostly naive) versions of both, but IMHO with something OCaml/F#/Reason you get pretty close. Sure, it's not concatenative, but you can certainly code point-free if you like so rather than:

c(b(a(42), 2))

You can write either:

42 |> a |> b 2 |> c or a reverse pipeline: c <| b 2 <| a <| 42

with complete type-safety. Note quite as concise as:

42 a 2 b c

But I that's good for the most part. I think programming with higher-order curried functions and closures gives similar power but much more readability -- every time I've gone past the toy program stage with concatenative languages, my head ends up exploding -- but maybe I never had the appropriate epiphany.

With that said, I certainly appreciate the power of Forth and compile-time words -- amazing power for such a minimal system.


agree,

I'm using pipelines in Elm, which is almost the same syntax as in OCaml (piping into the last argument).

I dislike "<|" b/c of the flexibility syndrome. It's frequently used in Elm in order to avoid parentheses.

Same with Elixir, but it's missing currying, so it does piping into the first argument.

Clojure has both thread-first and thread-last macros.

The problem with these approaches is that they are optional. Ideally the point-free programming will be the default, not an optional style.

I agree that pipelines in Elm and Elixir are much more readable than point-free code in Forth and APL ;)

Unlike the ML-family languages, in APL/J/K/q you can omit any combination of arguments:

  f:{x-y}
  
  f[1]  same as f[1; ]
  f[;1] same as f[ ;1]
  f     same as f[ ; ]


Most Lisps (incl. clojure), Scala, and probably others also allow placed or numbered "holes", so something like:

42 |> f 3

would be the same as 42 |> f 3 _

but you could do:

42 |> f _ 3 to get at the first arg.

or even to swap args:

42,43 ||> f %1 %0

which would essentially get expanded to:

42,43 |> fun a b -> f b a

(Of course, in this case the closure should get erased by the optimizer to simply "f 43 42".)

I really miss not having this in F# (and C# for that matter)


In my opinion, the perfect programming language would have RTL evaluation like APLs but RPN is a superior input method. If I had some kind of IDE that allowed me to "build" APL expressions (and dfns) using RPN as I built algebraic expressions using my HP calculator, I would be a happy man.

Forth LTR feels good with small expressions (and many times, that's all you need), but as soon as you have something complex, stack shuffling makes difficult to keep a mental picture of what the program is doing. I have the same problem with complex tacit expressions, I need to "translate" it. But dfns (or k funcs) are trivial to read.


I’m not sure what “feels hackish” means but it sounds suspiciously like a nonspecific complaint that is used when all the actual potential issues have been exhausted.

In programming, we’re always dealing with stacks. It’s just that they’re usually invisible. As someone dealing with an unexpected user crash in a modern/traditional language, the possibility of making them visible would be pretty helpful right about now! I’d kill for a crash dump that included a data stack, too.


It's an aesthetic complaint. It has the word "feels" right in it. What do you expect?

With that said, as someone who hasn't programmed in Forth-likes but has read about them, I can guess at a more precise complaint: stack manipulation operations like dup, swap, roll, et al are pure noise relative to my business logic. They're implementation details constantly interfering with my intent. And yes, I'm sure if I find just the right factorization into tiny words I could avoid most of the explicit stack twiddling, but is that really a good use of time relative to writing in C? Or figuring out how to design a better language? Doubtful.


Correct, there are lots of abstractions under the hood it doesn't mean they need to be intertwined with our business logic. What's next exposing the microcode or physical RAM layout?

Even worse majority of ISAs are register-based, not stack-based. So this abstraction is virtual.

What I meant is that Forth wasn't designed from first principles. Instead of solving the problem "how to help developer to transform business logic into an application", it solves the problem "how to help developer to program a stack machine".

It was fitted to a simple, but useful abstraction - stack machine. Same with Lisp, APL, C, etc. In case of the C language the abstraction was PDP-11 ISA ;)

  Glorified data structures -> PL:
  
  array       -> APL
  list (cons) -> LISP
  stack       -> Forth
  relation    -> Prolog
  
  Glorified data structures -> DB:
  
  relation    -> SQL (RDBMS)
  map         -> NoSQL K/V stores
  hashpointer -> IPFS
  hashchain   -> Blockchain
  Merkle Patricia Tree -> EVM


How often do you actually do "dup, swap, roll" in a Forth program, though? The typical Forth program doesn't look like the samples you see on its Wikipedia page, just like the typical C program doesn't look like the samples you see on its Wikipedia page.

I could go the other way and say that dereferencing arbitrary pointers, maintaining a \0 at the end of every string, and declaring which variables should live in registers aren't part of my business logic, either. C sure sounds terrible! Just because a language allows low-level implementation details doesn't mean it requires them in every function.

I don't understand the logic of having a language which allows you to build your own abstractions, and deciding that it's a better use of one's time to drop it altogether and instead use a language whose only user abstractions are structs and functions.


Granted, C is pretty awful too. My personal favorite option remains "better language". Forth-likes are an under-utilized source of inspiration for sure, but I don't think we should be satisfied with stack-twiddling any more than with C strings.


Joy is great (at least better than SQL) as an API to a database. IE, every query is a joy program. If you add get, set and (de)serialize as primitives, you have stored procedures, intermediate results, galore! Want to transform your result before you send it back to the client ? no problem, just add a transformer function.


Can you add a link pointing at some examples?


there's a bunch of research related to 'stack based query languages' google for SBQL.

Anyway, in the specific case of joy is more difficult to dig up. Here's an example (I know it, because I implemented it): https://github.com/openvstorage/arakoon/blob/lsl/src/system/...

    "script1_" true 
    "script2_" false
    0
    [POP; 1; ADD] 
    FOLD_RANGE
The above program has basically calculates the number of keys between "script1_" inclusive and "script2_" exclusive. It's really compact flexible and powerful.

Now, before you run off and think about implementing this yourself. First have a look at kitten: https://kittenlang.org/


Example from C2[0]:

  (* x y percent approxEqual *)
  DEFINE approxEqual == 100.0 / rotate [0 =] [pop] [dup rolldown -   
  swap /] ifte > 
[0] https://wiki.c2.com/?JoyLanguage


"In Joy, everything is a function that takes a stack as an argument and returns a stack as a result. "

But formal parameters are same things[1]. It's just a values pushed to stack before calling a function and values poped from stack on return. That's why you get stackoverflow exceptions in java when your recursion fails.

EDIT: [1]most of the time, in situations that i know about.


The difference is that in one case it's an implementation detail covered by an abstraction to make that fact irrelevant to the programmer, and in the other the language is fundamentally designed around the idea that the stack itself is exposed as a data structure to the programmer.

If you don't understand how the difference is significant, I suggest trying a language like Joy, Factor or Forth.


  class Fun implements Function<Stack, Stack> {
  
    public Stack apply(Stack s) {
      return s;
    }

  }
So this is Joy in Java. But as my neighbor comment asks. How does this benefits a language as tool for humans to get job done (c). From my perspective it only brings you down the abstractions ladder.


Climbing the abstraction ladder does not lead to Heaven. Rather to Noah's ark, according to the law of leaky abstractions.

Java has this reputation of being a lot of boilerplate code, and indeed when you compare it to Forth code this is striking. For instance this is the code to square the top of stack:

: square dup * ;

I don't know much about Java, but I guess it something like:

Stack apply(Stack s) { int tos=s.pop(); s.push(tos*tos); return s; }

Of course this is not fair at all because Forth has no type checking, no OO and gets its terseness from the point-free style that stack-orientation allows.

But this example is symbolic of one thing: Java needs a lot of baggage to be viable. A state-of-the-art GC. A state-of-the-art JIT. A complex lexer/parser. A mildly efficient Forth interpreter/compiler can be written by one teenager.

If your job is to get some sort of interpreter running on a brand new hardware, then Forth is probably the fastest way to get that job done. Chuck Moore, the creator of Forth, has funded a company on this - company that is now more than forty years old.

This extends to more common use cases. As most processors come on the market with a C compiler, if you have a Forth interpreter written in C, you can have it running right away. Even before you have an OS for it.

But you can benefit from the OS and the large number of C libraries too. Writing library bindings for a Forth system is often trivial; the hardest part is to decide how the API should look like on the Forth side (often library APIs are too rich and you want to only expose the useful subset for you).

So "stack-oriented programming" leads to simple interpreters and compilers, which in turns gives a lot of flexibility, which is very valuable to get things done.


Thank you for this! You concisely answered the question I kept having throughout the wikipedia page and these threads. Obviously there's a world in which stack programming lets you be productive, its flourished, but what is that environment? Bootstrapping, new hardware, working at pre-OS levels, these are all places I don't have to think very much about, so I appreciate the reminder and context for why Forth is powerful there.


  If your job is to get some sort of interpreter running on a brand new hardware, then Forth is probably the fastest way to get that job done. 
  Chuck Moore, the creator of Forth, has funded a company on this - company that is now more than forty years old.
This one is really nice example of "getting things done"™. Thank you for your answer


> it only brings you down the abstractions ladder.

Ironically I'd say you're both right and wrong at the same time because of the somewhat paradoxical nature of concatenative languages: you're closer to the metal (especially with Forth) but at the same time working with Forth is an exercise in abstracting everything away. The strength/weakness is that it has only one hammer to do so: defining Forth words to manipulate the stack ("words" == Forth lingo for functions, more or less). It has a Lisp-like minimalism (you can almost think of it as postfix-Lisp in some sense).

The Fun class you describe misses the point entirely of the minimalism of Forth or Joy - the complete lack of syntax is a feature that makes concatenative language inherently composable in ways that other languages can only dream of. In a language like Python, you could compose three function calls like so:

    y = foo(x)
    z = bar(y)
    w = baz(z)
In a language like Joy (or Forth, or Factor) you would write:

    x foo bar baz
.. where the x is only included if we assume it is a stored value that has to be put on the stack first.

Ok, sure. But here is the thing: imagine that I wish to refactor calls to foo and bar into their own function (this particular example is childishly simple of course, but I think it gets the conceptual point across). Compare the Python and the Joy ways of doing that:

    def bla(x): return bar(foo(x))

    DEFINE bla == foo bar
The difference is small but significant. Because the concatenative languages have no function application, refactoring is often a simple cut/paste operation, followed by one big find/replace. You can literally split (almost) any piece of code into parts at any point, define those parts as new words, and the result would be valid code. Precisely because of the brutal minimalism at play here.

The trade-off is an upfront cost of having to structure the entire program around stack operations, and thinking in a postfix way of doing operations.

I'm not saying that makes them better - I like my type checking very much, and in the right amounts a bit of boilerplate makes code a lot more self-documenting. But I also think that learning how to solve problems the "Forth" way is a good programming exercise.


would it be better if we have 'static-typed stack-based language'? But it seems stack based language only support numbers... Event string is treated as chars, which are the same as ints, which is hard to 'type check' statically?


Forth is so minimal that you can implement an interpreter in hardware, which means it has no types.

There are a few typed stack languages. Cat was the first, and Kitten is another one.


> So this is Joy in Java.

No, it isn't. I assume that you are trying to make a point, but I'm not seeing it so please elaborate.

> But as my neighbor comment asks. How does this benefits a language as tool for humans to get job done (c).

That of course depends on the job. It might not benefit you at all. Some will probably argue that this simple model only benefits the implementer in getting their job done. Personally, I think the ability to quickly refactor code that a point-free sequence of stack operations affords can be useful.

> From my perspective it only brings you down the abstractions ladder.

Have you ever found it useful to take a step down the abstraction ladder?


I fully understand the difference but having read up on forth but never used it, I don't know if it's helpful in that sense. Exposing the stack fully is a weakness not a strength to me.

I also understand that this is a formalisation that supports concatenative programming but from a user point of view, does it fit human comprehension as well as, or better than, a more conventional language. Honest question, thanks.


I've been working with Joy for several years now, on an off, in a hobbyist way, and I've got to say, I think it's the most useful, simplest language.

(I've made implementations in Python and Prolog https://joypy.osdn.io/ )


The coolest part of this article imo is the binrec operator. Do other languages have something similar?


From many viewpoints a stack is a nuisance, for example memory management and context switching. Therefore I think that languages which use a stack are going in the wrong direction.


Michelson language has distinctly ruined my taste for language like this.


The point of Forth/Joy is that the language is so simple, you don't need compiler. It is a minimum language.


I still find ada language more advanced than current ones


What do you mean? They’re different branches on the tech tree. Saying one is more advanced than the other is strange.


‘[M]ore advanced’ meaning ‘better’?


Ada is just another Foogol, and is missing a lot of features from other branches of the language development world.


Very confusing as just have another Joy web framework. For this kind of new language, one may have to have a kind of FAQ for the reason why it exists at all. Why not just FORTH?


> Why not just FORTH?

Joy comes from a totally different background, and has a very unique set of features. The fact that a purely functional point-free programming language finished looked quite similar to Forth in some aspects was just an accident (a happy accident, imo).


The language is 19 years old, it's not new.


Though there too much fatigue from trying to give mind space for new languages popping up almost every day, but this seems an older language. While I am not going to try it out, I can give you some ideas like : "The concatenation of two programs denotes the composition of the functions denoted by the two programs."


As one might expect, ‘Joy’ is a popular name. IIRC the Crystal language was also initially named Joy.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: