It also has an incredibly powerful inheritance system. In 3-5 lines I can inherit all the functionality from Racket and then add, modify, or delete any part I like. Reminder, I can do all this from the compile time level, not the runtime level. I can change something as basic as how you define functions with just a few lines of code while still getting everything else.
Basically, I can easily write Racket+X or Racket with modified Y or Racket without Z and make it it's own entire language with very minimal effort.
A lot of this occurs because of Racket's insanely good macro system. You can also use these tools in libraries/packages and still get the compile time control while the language is still officially Racket.
No more work than any other language - Racket has reclusive descent, lex/yacc and PEG parsers generators. There is a whole section on parsers at http://docs.racket-lang.org/ including ‘Megaparsack’ based on higher-order parser combinators (like parsec for Haskell)
Racket can also read ‘C’ yacc/bison grammars.
It’s worth noting that a variety of non-Lisp-parenthesis-languages have been done in Racket including Java and Python.
https://docs.racket-lang.org/scribble/getting-started.html
But there's also stuff like:
http://docs.racket-lang.org/sweet/index.html
And after a quick Google I found:
https://github.com/soegaard/minipascal
And there are of course things like:
https://docs.racket-lang.org/datalog/datalog.html
But I suppose that qualifiés as "lisp like"...
[ed: and the article mentions "brag" which I think would be odd to call "lisp like" : https://docs.racket-lang.org/datalog/datalog.html ]
1. Lispers often find s-expression syntax easier to edit for many applications. So it's not surprising that a lot of the folks who design languages in Racket also might choose an s-expression syntax for their language design.
2. By using the same (or similar, in the case of Pollen/Scribble/...) syntax as the main racket language, it's easy to give access to the full power of racket in a DSL.
Some of the more specialised languages are different, though.
Let's be fair, many lisp programmers would be perfectly fine with that.
Unless your goal is to be source compatible with another lisp variant, of course.
I don't know. Most examples I've seen from people extolling the virtues of lisp macros and metaprogramming wind up just generating more lisp code in the same language with the same syntax and semantics, so I don't know why you couldn't just use functions in that case.
To be fair, I only have a surface understanding of one lisp variant (Arc) so it's entirely likely I just don't get it.
#lang racket
(define (show x)
(match x
[(vector a b c)
(display "It's a vector, total = ")
(displayln (+ a b c))]
[(list p q)
(display "I's a list, average = ")
(displayln (/ (+ p q) 2))]))
(show (list 8 2))
(show (vector 1 2 3))
(Playable version http://pasterack.org/pastes/75154 )The idea is that `match` can analyze the value of the first argument (`x` in this case) and then it binds the variables (a,b,c or p,q in this case).
In this case I'm matching only a vector with 3 values or a list with 2 values, but you can match more complicated and nested pattern.
The interesting part is that inside match you have the full power of the language, you are not restricted to a few special hardcoded cases (like `display`). You can send an hppt request, play a sound, save a value to disk, calculate the prime factorization, transform the text to uppercase, anything that is available in the language is available inside match.
But `match is just a normal macro. Racket is implemented in layers. The bottom layer is implemented currently in C, but it doesn't define `match`. The bottom layer is almost hidden and it is used to define some libraries and an intermediate language that it is used to define some libraries and an intermediate language, ..., and after a few steps you get the official Racket language.
But if you like you can reimplement a macro like `match`, or a variant, or your own macro, with all the magic to define local variables and use all the power of the language inside it.
Despite having the same syntax, `#lang lazy` and `#lang racket` behave differently:
#lang lazy
(define (say-hi _)
(displayln "Hi!"))
(say-hi (exit 1))
$ racket lazy-example.rkt
Hi!
$ echo $?
0
versus #lang racket
(define (say-hi _)
(displayln "Hi!"))
(say-hi (exit 1))
$ racket racket-example.rkt
$ echo $?
1
Exact same syntax, different semantics.So given an and() function, you can't safely do "and(False, fire_missiles())", because the language will evaluate both arguments.
But an and!() macro could: macro expansion is essentially "lazy", in that it happens prior to the evaluation phase, so it can avoid evaluating any pieces of code it wants to, such that "and!(False, fire_missiles());" is perfectly safe, because our macro can stop expanding after "False" and thus fire_missiles() is never evaluated.
Incidentally, this is why at least some macro patterns are unnecessary in lazy languages like Haskell: you can write short-circuiting and() as a function there, because a function in Haskell only evaluates as much as is necessary (provided it's been written properly). Yet even there, Haskell still has support for macros and things like TemplateHaskell and so forth, because there's just some things you can't do solely with functions, like arbitrary syntax, language extensions, etc.
Modifying control flow, for one. A classic example is anaphoric if. If you want to implement that, you can't do it just as a straight forward function. It needs to be a function that modifies syntax, aka a macro.