notes-computer-programming-programmingLanguageDesign-prosAndCons-racket

links

tutorials


Racket offers a number of interesting features not found in other languages, making it an attractive option for real-world work. Racket puts into practice state-of-the-art research on macros, continuations, contracts, and interoperation between static and dynamically typed code

"


" Racket offers a number of interesting features not found in other languages, making it an attractive option for real-world work. Racket puts into practice state-of-the-art research on macros, continuations, contracts, and interoperation between static and dynamically typed code. The integrated Scribble system makes it easy to provide high-quality documentation and/or write literate programs. It comes with a pleasant, lightweight IDE complete with an integrated debugger and profiler (as well as innovative features such as a specialized macro debugger). ... For many years, Python set the standard for me, offering easy syntax to manipulate extensible arrays (called lists in Python), hash tables (called dictionaries in Python), tuples (an immutable collection that can serve as keys in a hash table), and in recent versions of Python, sets (mutable and immutable), heaps, and queues. ... You can write vector-based algorithms in Racket, but they look verbose and ugly. Which would you rather read: a[i]+=3 or (vector-set! a i (+ (vector-ref a i) 3)) ? But if you can get past the more verbose syntax, there's still the fundamental issue that all the patterns change when you move from using a list to a vector. The way of working with them is so fundamentally different that there is no easy way to change code from using one to another.

...

the interface for interacting with hash tables is a total mess. The literals for expressing hash tables use dotted pairs. If you want to construct hash tables using the for/hash syntax, you need to use "values". If you want to iterate through all the key/value pairs of a hash table, it would be nice if there were an easy way to recursively process the sequence of key/value pairs the way you would process a list. Unfortunately, Racket provides no built-in lazy list/stream, so you'd need to realize the entire list. But even if that's what you'd want to do, Racket doesn't provide a built-in function to give you back the list of keys, values or pairs in a hash table. Instead, you're encouraged to iterate through the pairs using an idiosyncratic version of its for construct, using a specific deconstructing pattern match style to capture the sequence of key/value pairs that is used nowhere else in Racket. (Speaking of for loops, why on earth did they decide to make the parallel for loop the common behavior, and require a longer name (for*) for the more useful nested loop version?) Put simply, using hash tables in Racket is frequently awkward and filled with idiosyncracies that are hard to remember. ...

There are downloadable libraries that offer an assortment of other data structures, but since these libraries are made by a variety of individuals, and ported from a variety of other Scheme implementations, the interfaces for interacting with those data structures are even more inconsistent than the built-ins, which are already far from ideal.

...

Clojure gets data structures right. There's a good assortment of collection types built in: lists, lazy lists, vectors, hash tables, sets, sorted hash tables, sorted sets, and queues. ALL the built-in data structures are persistent/immutable. That's right, even the *vectors* are persistent. For my work, persistent vectors are a huge asset, and now that I've experienced them in Clojure, I'm frustrated with any language that doesn't offer a similar data structure (and very few do). The consistency of working only with persistent structures is a big deal -- it means you use the exact same patterns and idioms to work with all the structures. Vectors are just as easy to work with as lists. Equality is simplified. Everything can be used as a key in a hash table.

Data structures in Clojure get a little bit of syntactic support. Not a tremendous amount, but every little bit helps. Code is a little easier to read when [1 2 3] stands out as a vector, or {:a 1, :b 2, :c 3} stands out as a hash table. Lookups are a bit more terse than in Racket -- (v 0) instead of (vector-ref v 0). Hash tables are sufficiently lightweight in Clojure that you can use them where you'd use Racket's structs defined with define-struct, and then use one consistent lookup syntax rather than type-specific accessors (e.g., (:age person) rather than (person-age person)). This gets to be more important as you deal with structures within structures, which can quickly get unwieldy in Racket, but is easy enough in Clojure using -> or get-in. Also, by representing structured data in Clojure as a hash table, you can easily create non-destructive updates of your "objects" with certain fields changed. Again, this works just as well with nested data. (Racket structs may offer immutable updates in future versions, but none of the proposals I've seen address the issue of updating nested structured data.) Furthermore, Clojure's associative update function (assoc) can handle multiple updates in one function call -- contrast (assoc h :a 1 :b 2) with (hash-set (hash-set h 'a 1) 'b 2).

Even better, the process for iterating through any of these collections is consistent. All of Clojure's collections can be treated as if they were a list, and you can write algorithms to traverse them using the same pattern of empty?/first/rest that you'd use on a list. This means that all the powerful higher-order functions like map/filter/reduce work just as well on a vector as a list. You can also create a new collection type, and hook into the built-in sequence interface, and all the built-in sequencing functions will automatically work just as well for your collection.

...

Although the sequencing functions work on any collection, they generally produce lazy lists, which means you can use good old recursion to solve many of the same problems you'd tackle with for/break or while/break in other languages. For example, (first (filter even? coll)) will give you the first even number in your collection ... "

" assoc works on vectors, hash tables, sorted hash tables, and any other "associative" collection. And again, you can hook into this with custom collections. This is far easier to remember (and more concise to write) than the proliferation of vector-set, hash-set, etc. you'd find in Racket. "

"

Summary:

    Clojure provides a full complement of (immutable!) data structures you need for everyday programming and a bit of syntactic support for making those manipulations more concise and pleasant.
    All of the collections are manipulated by a small number of polymorphic functions that are easy to remember and use.
    Traversals over all collections are uniformly accomplished by a sequence abstraction that works like a lazy list, which means that Clojure's higher order sequence functions also apply to all collections."

"

CLOJURE'S NOT PERFECT

The IDEs available for Clojure all have significant drawbacks. You can get work done in them, but any of the IDEs will probably be a disappointment relative to what you're used to from other languages (including Racket).

Debugging is difficult -- every error generates a ridiculously long stack trace that lists 500 Java functions along with (maybe, if you're lucky)... Many of Clojure's core functions are written with a philosophy that they make no guarantees what they do with bad input. ...

Clojure inherits numerous limitations and idiosyncracies from Java. No tail-call optimization, no continuations. Methods are not true closures, and can't be passed directly to higher-order functions. Proliferation of nil and null pointer exceptions. Slow numeric performance. Compromises with the way hashing and equality works for certain things to achieve Java compatibility. Slow startup time.

"

"

Clojure has a number of cool new ideas, but many of them are unproven, and only time will tell whether they are truly valuable. Some people get excited about these features, but I feel fairly neutral about them until they are more road-tested. For example:

    Clojure's STM implementation - seems promising, but some reports suggest that under certain contention scenarios, longer transactions never complete because they keep getting preempted by shorter transactions.
    agents - if the agent can't keep up with the requests demanded of it, the agent's "mailbox" will eventually exhaust all resources. Perhaps this approach is too brittle for real-world development?
    vars - provides thread isolation, but interacts poorly with the whole lazy sequence paradigm that Clojure is built around.
    multimethods - Clojure provides a multimethod system that is far simpler than, say CLOS, but it requires you to explicitly choose preferences when there are inheritance conflicts, and early reports suggest that this limits extensibility.
    protocols - This is an interesting variation on "interfaces", but it's not clear how easy it will be to compose implementations out of partial, default implementations.
    transients - Nice idea for speeding up single-threaded use of persistent data structures. Transients don't respond to all the same interfaces as their persistent counterparts, though, limiting their usefulness. Transients are already being rethought and are likely to be reworked into something new." "

singular 982 days ago

link

I had horrific experiences trying to get Racket namespaces to work correctly (in Racket, a namespace is a value rather than simply a name). I found I couldn't get files to use the same namespace no matter how hard I tried, and without a specific namespace some code I was eval-ing simply refused to work.

I suspect Clojure lacks this 'feature' :-)

It's frustrating because I'm sure Racket has a lot to offer but not being able to do something really simple (even after hours and hours of trying) completely put me off.

--

DASD 204 days ago

link parent flag

Am I the only one who forgets Racket? Everytime I visit the site and browse through the documentation, I find myself wondering why I'm not using the language. Years ago I used Chicken Scheme but this seems more robust and the standard library is adequate. Who's using this in production? What's your experience?

tikhonj 204 days ago

link

Well, I haven't used it in production, but I used it for a research project last semester. It was actually pretty good, and there are definitely some very nice features. The standard library was pretty good except it missed some functional features I wanted like immutable vectors (or even just functional operations on mutable vectors).

The language is extremely flexible, and you can take advantage of this to write some really elegant and maintainable code. It's one of the more productive languages I've used--of the languages I've used extensively, it's better than JavaScript?, Java or Python.

So I liked it quite a bit over all. However, it doesn't quite clear the threshold where I would use the language voluntarily. I thought it had lackluster support for functional programming, especially from what is supposed to be a functional language. This was evident in the library (e.g. the lack of immutable vectors and the like) and also in the way certain pretty fundamental structures (like ports) worked.

Also, for a dynamically typed language, I thought it had too many different types. It was too easy to get mixed up between bytestrings and strings, for example. It was also not obvious when you should use or expect nil vs #f. That said, the features for creating your own types (mostly structs) were very good--I found they were very lightweight but still flexible enough to be useful.

I am actually a big fan of the syntax. Not just the s-expressions (although, with paredit, they are pretty awesome) but also the reader macros. That said, I'm also rather partial to infix operators, which are obviously not present.

So over all, it's a great language. However, I would still choose Haskell or OCaml over it for virtually any project. The main exception would be projects very heavy on metaprogramming, where Racket really shines.


_sh 204 days ago

link

Racket now has all manner of purely functional data structures (Okasaki and Bagwell) available as a library on Planet:

http://planet.plt-scheme.org/package-source/krhari/pfds.plt/...


elibarzilay 203 days ago

link

Not just now -- this library has been up for a number of years.


chimeracoder 204 days ago

link

> Also, for a dynamically typed language, I thought it had too many different types. It was too easy to get mixed up between bytestrings and strings, for example. It was also not obvious when you should use or expect nil vs #f. That said, the features for creating your own types (mostly structs) were very good--I found they were very lightweight but still flexible enough to be useful.

You can also used a typed dialect of Racket if that's easier - just write #lang typed/racket at the top of your file: http://docs.racket-lang.org/ts-guide/


JadeNB? 204 days ago

link

> > Also, for a dynamically typed language, I thought it had too many different types.

> You can also used a typed dialect of Racket if that's easier

I'm not sure that proposing a move to a typed dialect will solve a problem with too many different types. :-)


tikhonj 204 days ago

link

Actually, I think it would. The problem is not too many types per se, but too many types without a good way of managing them. All a static type system is is a good way to manage types!


seabee 204 days ago

link

Infix operators can be emulated thanks to a built-in reader macro. Example:

(i . <= . (length lst))

Which moves the stuff between . to the head of the sexpr.

---

http://stackoverflow.com/questions/3345397/how-is-racket-different-from-scheme

---