top | item 5363003

Clojure: All grown up

240 points| djacobs | 13 years ago |wit.io | reply

185 comments

order
[+] tikhonj|13 years ago|reply
Clojure is nice, but the idea that it is somehow built for the real world where Haskell isn't is just patent nonsense that I wish people wouldn't repeat. You do not need to understand complex math to actually use Haskell! All the important ideas can be understood and used in Haskell terms alone--you can think of them just like the ideas and jargon introduced in other languages. Except more general, more elegant and more consistent because they have a unifying underlying theory which the people who designed them in the first place do understand.

The biggest conceptual shift is to thinking functionally rather than imperatively, so it's going to be similar in both languages. The difference is that Haskell is more thorough, but the fundamental ideas are very similar.

Haskell, of course, has many of its own advantages which are mostly detailed elsewhere. I'm merely going to give you my highest-level take-away from trying Clojure: Haskell has much better facilities for abstraction. Haskell allows you to use types and data representation specific to your donation while still allowing you to take advantage of some very generic libraries. And the custom types are not actually complex, unlike what Clojure leads you to believe with its preference to using the basic built-in types: you construct your types from basically three fundamental operations: combining multiple values into one (like a tuple or struct), giving a choice of values (like a variant or disjoint/tagged union) or creating functions. That's all there is to them!

Basically, don't be afraid of Haskell, whatever the common wisdom is. As usual with these sorts of things, it's much more common than wise.

[+] swannodette|13 years ago|reply
> Haskell has much better facilities for abstraction

I've been using Clojure for about 5 years now. I've actively worked for the past two years on core.logic which has quite a few fancy abstractions (custom types, protocols, macros) that all interact quite nicely together. Now, I'm not a Haskell expert, but I do feel that some of the things I've built would be could be challenging in the Haskell context. I might very well be wrong about that.

That said, I'm actively learning Haskell as there are some aspects of Haskell I find quite attractive and in particular a few papers that are relevant to my Clojure work that require an understanding of Haskell. My sense is that even should I eventually become proficient at Haskell, my appreciation for Clojure's approach to software engineering would be little diminished and likely vice versa.

[+] jamwt|13 years ago|reply
Yes, already having gotten "over the hump" on Haskell, the issue I have with Clojure is that I dig into it (finding it a truly appealing language, and wanting to achieve that sweet spot), but usually walk away thinking it really isn't giving me anything Haskell isn't, and is lacking many of the niceties I've come to rely on. I just have this feeling I would love it if I didn't already know my way around Haskell, but the "benefit-venn-diagram" feels like concentric circles.

If there are some people out there who are well versed in both Haskell + Clojure, I'd love to hear some insight into where Clojure shines, and where you find yourself saying "Clojure is better at this!" in some distinct and meaningful way.

[+] jwr|13 years ago|reply
A meta-comment: I find it amazing that we are arguing whether Clojure or Haskell is better suited for real-world applications.

Discussions like this one convince me that we have advanced our programming techniques over the last decade or so. After all, we could be arguing about semicolons, the GOTO statement, or other similarly important issues.

It's a nice feeling.

[+] rayiner|13 years ago|reply
> Clojure is nice, but the idea that it is somehow built for the real world where Haskell isn't is just patent nonsense that I wish people wouldn't repeat. You do not need to understand complex math to actually use Haskell!

In the abstract, possibly, but not in practice. You can't ignore the effect the general bent of the community has on the language and the code that is idiomatic in the language. People tend to describe Haskell code in mathematical terms, and libraries use operator overloading and the like in mathematical analogies.

[+] pron|13 years ago|reply
Even if Haskell could somehow be proven to be "the better language" (whatever that means), Clojure is still extremely elegant, simple and beautiful, and it has one huge advantage: it runs on the JVM, so it automatically harnesses the vast JVM ecosystem, as well as all of its (unmatched, IMO) monitoring, profiling and runtime-instrumentation tools (+ dynamic class-loading and hot code replacement).
[+] berlinbrown|13 years ago|reply
I think you can use both. As Clojure is more dynamic in nature where Scala/Haskell are good compiled static compliments.

Large projects, I would go with Scala/Haskell and for front-end systems, I would use clojure.

Why do it this way? With clojure, you can easily modify data or small pieces of logic. Simply edit the script without the recompile. With the scala/haskell api or library, you probably need something that changes less frequently. That backend system may act as a core piece of your library. ...

And if you don't like that. You can do Python and Java/C# which can give you the same effect.

[+] djhworld|13 years ago|reply
I think Haskell is great but it's weak in libraries.

This is where Clojure has a slight advantage because you can tap into the Java ecosystem for stuff that isn't currently in the Clojure world.

Database libraries are a good example of this, database support in Haskell is still pretty flaky and cumbersome to setup in comparison to JDBC.

[+] sedachv|13 years ago|reply
+1. People get themselves worked up about words like "monads" and "algebraic data types," but using those things is not difficult, and the amount of guarantees they give you about your code is so far beyond anything else out there that IMO it's not worth doing functional programming unless you have a proper type system together with monads.
[+] da3da|13 years ago|reply
I'm curious what advantages you think Haskell has over ML/Ocaml. I'm pretty familiar with OCaml and like it a lot, and Haskell seems to be very similar, but I haven't used it for anything substantive. What benefits, if any, does Haskell have over Ocaml, and is it worth learning if one already knows Ocaml? I know that Haskell has lazy evaluation while Ocaml doesn't, but you can simulate lazy evaluation in Ocaml.
[+] jes5199|13 years ago|reply
Haskell got some sharp edges which make me hesitant to use it. I once had a Haskell program that had a serious space leak that would cause it to exhaust ram and crash. The culprit? Adding "+1" to a counter repeatedly and then never evaluating the counter. It took a very smart programmer (who happens to have a PhD in math) six hours of debugging to find it.

Also I've never seen Haskell's FFI work - it's just too much of a pain. Clojure's Java integration works right out of the box.

[+] alberich|13 years ago|reply
> I'm merely going to give you my highest-level take-away from trying Clojure:

Then you go on the describe what you find good about haskell? That sounded weird.

[+] weavejester|13 years ago|reply
"And the custom types are not actually complex, unlike what Clojure leads you to believe with its preference to using the basic built-in types"

Custom data types are not always complex, but efficient and useful data types often are. Clojure's data structures have very good performance for immutable data structures. Clojure's maps, for instance, have what effectively amounts to a O(1) lookup, while Haskell's Data.Map is a size balanced binary tree with only O(log n) performance.

[+] coldtea|13 years ago|reply
>Clojure is nice, but the idea that it is somehow built for the real world where Haskell isn't is just patent nonsense that I wish people wouldn't repeat.

Really? Because SPJ, for one seems to agree with it.

[+] dschiptsov|13 years ago|reply
This is just a propaganda - repetitive reciting of slogans made of long words.)

It is, of course, difficult to argue with zealots, but I will try nevertheless.)

The cost of what is called "advanced type system" is inability to put an elements of different types in the same list or tuple or whatever. It is not just a feature, it is a limitation. In some cases, when you, for example, dealing only with numbers, say, positive integers, it is OK, but what then the real advantage of such type checking?

On the other case, the concept of a pointer which all those "packers" trying to throw away is very mind-natural. When we have a box we could put anything in it, as long as it fits. Imagine what a disaster it would be when you moving from one flat to another but must pack stuff only into special kind of boxes. This one is only for such kind of shoes, this is only for spoons, that for forks. So, with a pointer we could "pick up" everything, and then decide what it is and where it goes.

Another mind friendly concept is using symbols and synonyms to refer to thing - s symbolic pointers. It is how our minds work. Those who are able of thinking in more than one language know that we could refer to it using different words, but "inner representation" is one and the same.

These two simple ideas - using pointers (references) and have data ("representation") to define itself (type-tagging is another great idea - it is labeling) gives you a very natural way of programming. It is a mix of so-called "data-directed" and "declarative" and, as long as you describe a transformation rules instead of imperative step-by-step processes, "functional" styles.

Of course, the code will look in a certain way - there will be lots of case-analysis, like unpacking things form a big box - oh, this is a book - it goes to a shelf, it is a computer speakers, it goes on the table, etc. But that's OK, it is natural.

The claims that "packing" is the best strategy is, of course, nonsense. Trying to mimic natural processes in our mind (as lousy as we could do it) is, in my opinion, has some sense.

There are some good ideas behind each language and not so good. Symbolic computation, pattern matching, data-description (what s-expression, or yaml is) are good ones. Static typing, describing "properties" instead of "behavior" - not so.)

Also it is good to remember that we're programming computers, not VMs. There is something called "machine representation" which is, well, just bits. It doesn't mean to swing into another extreme and program in assembly, but it is advisable to stay close to hardware, especially when it is not that difficult.

Everything is built from pointers, you like it or not.) The idea to throw them away is insane, the idea (a discipline) of not doing math on them is much better. The idea of avoiding over-writing is a great one, it is good even for paper and pencil - everything become a mess very quickly, but avoiding all mutation is, of course, madness.

So, finding the balance is the difficult task, and it is certainly not Haskell. Classic Lisps came close, but it requires some skill to appreciate the beauty.) So, the most popular languages are the ugliest ones.

[+] lkrubner|13 years ago|reply
This is very true:

"Leiningen and Cake, joined forces to become an all-powerful build tool. Then Leiningen reached version 2. (And let me tell you, Leiningen 2 alone makes Clojure worth using.)"

Other build tools, and package managers, such as "bundler" in the Ruby world, seem pretty weak compared to Leiningen. This is a very powerful tool.

The tooling and the eco-system are reaching a very powerful level. For now, I use Emacs as my editor, but I am waiting for LightTable ( http://www.chris-granger.com/2012/11/05/meet-the-new-light-t... ) to get just a little further, and then I intend to switch to it.

This whole article is good, but this is the part that gets to the heart of the matter:

"Rubyists know that their language effectively got rid of for loops. In the same way, Clojure gets rid of imperative iteration in favor of declaration. Your thoughts shift away from place-oriented constructs like memory and gravitate to data structures and functions like map, reduce and filter. Your “class hierarchy” turns out to be a type system that happens to also lock away well-meaning functions into dark dungeons (more on that in another article), and getting away from that is freeing."

That might be the best summary of the strengths of Clojure: it helps you think about data structures and transformation, rather than thinking about the ceremonial and imperative code that your language needs to hear.

[+] DigitalJack|13 years ago|reply
I'm a big fan of Clojure, but I don't think I'd ever write an article like this. I guess I must not be an evangelist at heart...I will tell people that I like something, but I never tell them that they must use it.

You know yourself far better than I know you, and so why would I presume to tell you how to live your life?

Functional programming is sometimes great. Most forms of programming are sometimes great. But I don't think there is one form that is great all the time. Maybe there is, and maybe if I come across it I'll be smart enough to recognize it... but in the mean time, I'll just try to use what makes sense to me.

And sometimes, functional programming just doesn't make sense to me. I still can't quite get my head around monads. They have just one or two too many levels of abstraction for me to hold in my head. I think I'm almost there, and was trying very hard to grasp them, but then in one of the videos I was watching, the guy said this: "Monads are a solution to a problem you will never have." He said it in jest, partly because the language at hand was Javascript, but it really stuck out to me.

Clojure has what I would call "sensible" state containment via STM. And sometimes just plain storing some state is the easiest and most straight forward way to go.

I love working in Clojure because it makes it so easy to break down problems into bite sized functions. That, and the concision of the syntax suits me. I'm trying to accomplish the same thing in Java by having some classes that I treat as a namespace and load them up with static functions in that namespace. I'm sure a lot of people would spontaneously barf on their screen if they saw my code though.

[+] tikhonj|13 years ago|reply
Meh. I personally find articles like this useful: that's how I was turned onto all the technologies I really love right now like Linux, Emacs and Haskell. Most importantly, I was convinced to work through the learning curves for all of these--which were all easier than reputed but still took some effort--which turned out to be more than worth it.

Basically, the reader can decide for themselves. Also, each reader reads more then one blog post. So having a bunch of extreme opinions to compare (e.g. a strong case for a bunch of different languages) is more useful than a whole bunch of hedged blog posts that all repeat the same refrain: "well, all languages are basically equal and you should use what seems best".

In fact, blog posts like that are a big waste of time. (I'm looking at you, prog21.) I would much rather hear a bunch of different, reasoned opinions--especially if they contradict each other--than hearing the same boring, condescending tripe about choosing "the right tool for the right job" over and over.

Also, I think the idea that all--or even most--programming languages are somehow equal is patently absurd. Similarly, I think the oft-reused tool analogy is deeply flawed. But that's neither here not there and enough material for a blog post of its own.

Of course, that's something of a false dichotomy, although it does come up in practice. But I think posts advocating a technology are also good by themselves.

The choice of technology may be subtle, but this post only needs to present one option, which it does admirably.

[+] danneu|13 years ago|reply

    > I will tell people that I like something, but I never tell them 
    > that they must use it.
I think it's just a "dude, you've got to check this out" sorta thing. Not really a command but that special itch to just share your opinion with someone.

I gravitate towards the tools/technologies/paradigms that compel this sort of enthusiasm every time.

* Martin Odersky's (and the general community's) enthusiasm led me to pursue Scala and convinced me to take a serious stab at functional programming.

* Rich Hickey (etc.) to Clojure.

* Sandi Metz (etc.) to object-oriented design.

* DHH (etc.) to Ruby and Rails.

Vim, Coffeescript, Alfred, Postgres, tmux, Crystal Reports, Dropbox, most my entire toolchain, where I went out to eat last night -- all cajoled by the infectious enthusiasm of others inspirited by those things.

[+] weareconvo|13 years ago|reply
I tried switching to Clojure, and while I love the purity, simplicity, and logic of it, two things always bothered me:

1) Immutable data structures are always going to be slower than their mutable cousins.

2) "Clojure code is beautiful" should be changed to "Your OWN Clojure code is beautiful". When I finished writing a compact piece of logic or data transformation, I was often struck with the beauty of it. When I tried to read someone ELSE's Clojure code, however, I couldn't even begin to make sense of it.

I am ever open to being proved wrong, however. Any Clojure programmers reading this, please reply with some code that is readable, elegant, and performant, to provide a counterpoint to my pessimism.

[+] chc|13 years ago|reply
> Immutable data structures are always going to be slower than their mutable cousins.

Not always. It depends on usage. Mutable data structures can actually entail more work if the data needs to be accessed from more than one place. Immutable data structures can be shared and reused much more freely. (For example, adding an item to an array you got from somebody else means either the caller or the callee needs to have copied the whole array, while adding an item to an immutable list you got from somebody else means creating a single cons cell.)

At any rate, even if a destructive solution would be faster, the functional solution will probably still be faster in Clojure than the destructive solution would be in Ruby. And if not, you can still do something destructive — it's just not the default.

> "Clojure code is beautiful" should be changed to "Your OWN Clojure code is beautiful". When I finished writing a compact piece of logic or data transformation, I was often struck with the beauty of it. When I tried to read someone ELSE's Clojure code, however, I couldn't even begin to make sense of it.

I believe this is largely a matter of experience (not completely — some code just is hard to read!). Most people get that feeling when they're working with a relatively unfamiliar language. I find that the more familiar I become with Clojure, the better all other Clojure programmers seem to become.

[+] lukev|13 years ago|reply
As someone who has breathed Clojure for the past three years, I very rarely come across code that is truly difficult to understand. It does tend to be more information-dense than other languages (especially Java) but that just means I only have to understand a couple hundred lines split into 3 files instead of thousands of lines across dozens of files.

That said, there is definitely some nasty Clojure code out there, but I'm not sure that's avoidable in any language. Fortunately, the conventional wisdom to only use macros when necessary has started to catch on and that's made the situation a good deal better.

[+] DigitalJack|13 years ago|reply
Clojure has some very clever immutable data structures based on Phil Bagwell's work [1] that help mitigate the slow down.

I think you are totally right with regard to reading other people's clojure. Sometimes it's easy and pleasant, sometimes inscrutable.

I think the issue at hand is that it is so easy to go the DSL route in clojure. I call that an issue because when you invent a DSL to solve your problem, you've essentially created a new language that only you understand.

So now, if I'm reading your code, I have to understand raw clojure, and then I have to try to figure out your DSL.

[1] http://lampwww.epfl.ch/papers/idealhashtrees.pdf

[+] pron|13 years ago|reply
> Immutable data structures are always going to be slower than their mutable cousins.

True in general, but only if you just care about single core performance. A data structure that allows your algorithm to scale gracefully to more cores is actually more performant than a data structure that's faster for a single thread, but is extremely hard to scale in concurrent settings. Clojure sacrifices single-thread performance for multithreaded scaling, and it's the right tradeoff given current hardware trends (for example, consider the design of Clojure's reducers, and how it focuses on multi-core scaling).

My main concern with immutable data structures (and most data structures designed to work in concurrent settings, actually), is their heavy reliance on garbage collection, and on old-gen garbage collection in particular. Old-gen GC still has some ways to go, at least in HotSpot.

[+] hueyp|13 years ago|reply
Reading other Clojure code is weird. I agree that it can be hard to get an eye for other peoples code, but the flip side is that everything is just functions and data. When I decided to go look at ring or compojure it was incredible how simple the code really is. I've looked at plenty of other clojure libraries and I have this revelation every time. Its awesome.

This is in comparison to reading an OO library in a language I'm much more familiar with but where inheritance / mixins mean you have to dig through many files (often not obvious which ones) to understand a piece of code.

[+] pdrummond|13 years ago|reply
I also struggle reading Clojure code that isn't my own. It reminds me of a quote by Brenton Ashworth on the subject: http://goo.gl/yCIWI
[+] bkirkbri|13 years ago|reply
Interesting. One of the things I've come to love most about Clojure is that I find it easy to read and reason about code I did not write. Your comment demonstrates that is not universally true.
[+] sedachv|13 years ago|reply
> 1) Immutable data structures are always going to be slower than their mutable cousins.

This would be ok, if immutable data structures weren't also much harder to reason about, implement, and actually use in your algorithms. Nothing is stopping you from doing functional programming and using immutable data structures in an imperative language (the reverse is very much not true), but why would you bother if it's easier to think about (and prove properties of, if you're into that thing) mutable data structures?

People recommend Okasaki's Purely Functional Data Structures all the time, but the main result of that dissertation aren't the clever immutable data structures, it's the fact that Okasaki was the first person to come up with a somewhat workable way to do asymptotic runtime analysis of immutable data structures. IMO it's not pretty.

[+] pbiggar|13 years ago|reply
Totally agree. We built a PaaS entirely in Clojure (https://circleci.com) and its absolutely a grown-up language. I was unsure when we started (my cofounder has been using Clojure since 2008 and was adamant that this was a good idea), but Clojure makes it very easy to write big hardcore systems. Definitely one of our competitive advantages.
[+] mjt0229|13 years ago|reply
I take this as an extremely positive indication - that someone can be skeptical and won over after having built a substantial system. Virtually every time I look at a shiny new technology, even ones I profess to like, I like it substantially less after I've been using it for some time and know where its faults lie.
[+] bascule|13 years ago|reply
I also started with Ruby (well, after a number of other languages) and used Clojure for a time, and I just can't sympathize with this:

"And—if you like avoiding unnecessary frustration and boilerplate—it will make you happy."

Didn't get this feeling. I like avoiding unnecessary frustration and boilerplate! A great way to avoid boilerplate is to hide it behind macros, however that's not necessarily a great way to avoid frustration.

Know what's really awesome? When a macro injects a recur point, and suddenly you're not recuring to where you think you are, you're recuring to a point within the macro somewhere. The only way to figure this out is to go dig through the source of the macro.

Sorry Clojure fans, what can I say? This did not make me happy. I am told that if Clojure did not make me happy, that's my fault, because I didn't study Clojure hard enough or something to get to the point where I should feel the Zen of Lisp flowing through my brain. Clearly this must be the blub paradox at work.

Maybe it's my fault, or maybe Clojure isn't the greatest language in the world for everyone.

[+] alexatkeplar|13 years ago|reply
I wrote an event collector for SnowPlow in Clojure (https://github.com/snowplow/snowplow/tree/master/2-collector...), and really enjoyed the experience. Leiningen is excellent, far better than any other build tool I've used, and Ring and Compojure were both great.

My only grumble with Clojure is that nobody seems to document the types that their functions take and return. It's a PITA having to read through a whole chain of functions just to figure out the types which are passing through it.

[+] jboggan|13 years ago|reply
We use a lot of Clojure here at Factual: http://www.factual.com/jobs/clojure

I'm still in the process of learning from the Clojure gurus around here but I see that it has a lot to recommend it. I can already see increases in the clarity of my code when I write filters and functions for our data pipeline. The next step is to learn Cascalog.

We're fielding a sizable contingent to the Clojure West conference this weekend (which we're also sponsoring) so come say hi if you're in out in Portland!

[+] juskrey|13 years ago|reply
As for me, I did not even know the word "homoiconic", until yesterday, when I bought a book on Clojure, after reading the author.

What I do know, is that I was in love with Assembler since my first steps in programming and hacking. While everyone in my surroundings were using pascal and basic that days, inlining asm only for occasional IO work, I used to scaffold tremendous routines and structures in a matter of days, using base asm and macro. While my friends, looking at my sources, were only able to say "what the ...ck is this, that is insanly sick, how do you understand all this?", asm was so natural and fluent to me.

Then dark times of C and C++, Java, C# etc. followed (BTW, I hate purified OOP deep inside, it always seemed to me so unhuman), and several years ago my roads crossed with LUA, and I instantly loved it. Pity, I had no chances to use it much, but I remember that feeling, when code and data magically interlace and create beautiful structures.

Now I am looking at Clojure and recalling my Asm youth, and these awesome days with LUA. But this time it has all the power of interop with major libs and services. I am giving it a try.

[+] kreek|13 years ago|reply
Any recommendations for a web framework in clojure? I played around with Noir for a while but it seems like that project is abandoned now? What's the Sinatra/Flash of Clojure?
[+] tensor|13 years ago|reply
This is currently one of the best sites explaining the clojure web stack:

http://www.luminusweb.net/

There isn't really a single framework, rather you can pick and choose your components depending on your needs or preferences.

[+] DigitalJack|13 years ago|reply
Compojure, Ring, and Enlive or Hiccup are a pretty common stack.
[+] dschiptsov|13 years ago|reply
Sounds good when you know nothing but Java and never heard of CL - knew nothing about, say (disassemble #'foo) or what is FFI etc.

This is also very telling - lets you deploy your Clojure Web app to a JBoss server and take advantage of JBoss’s scalability without any XML configuration. You get a mature enterprise-ready Java server without the pain of Java or of configuration.

I wonder how many orders of magnitude difference in "scalability" we would see with a simple nginx -> fastcgi -> sbcl setup.)

Memory usage under long periods of time with pending storage/back-end calls is also interesting topic - how JVM blows up just after few hours in "production".)

[+] robertfw|13 years ago|reply
Can anyone speak to the experience of debugging Clojure, especially without having JVM experience? The one thing that has me concerned is the depth of crash dumps and the amount of JVM knowledge required to interpret them.
[+] virtualwhys|13 years ago|reply
Luminus and LightTable (wow, that in-editor javascript demo) look pretty awesome.

Coming from Scala/Play my initial resistance to jumping the fence are: 1) lack of compile time type safety 2) odd, for me, language syntax 3) no ScalaQuery/Slick functional SQL wrapper equivalent

How is Scala-Clojure interop? Would be interesting to jar up existing Slick-based model layer and invoke within Clojure stack ;-)

Not having yet taken the plunge, based on the LightTable demo Clojure development seems pretty rapid fire (read: no waiting for compiler to catch up).

[+] dogweather|13 years ago|reply
This is a very intriguiging post it constantly makes reference to Rails and Django. But it stops there at the abstract.

Can someone point to an A/B comparison ... a simple Rails/Django app and the Clojure equivalent?

"Just show me the code."

[+] Freaky|13 years ago|reply
Weird font rendering on this site - the glyphs are very distractingly blobby. Increasing or decreasing the zoom level helps a lot if anyone's finding it similarly unreadable.
[+] bitwize|13 years ago|reply
No thanks, I'll stick to Gambit.

Fast, RnRS/IEEE Scheme compliant, has a great runtime layer and FFI, can integrate with anything written in C.

[+] caycep|13 years ago|reply
I'd like to play around with this (as well as actually read SICP). However, does the fact that Apple keep blacklisting the Oracle JVM get in the way? Would be nice to have Clojure bundle its own JVM rather than have a dependency, unless I am totally doing something wrong in setting it up (homebrew).
[+] razielek|13 years ago|reply
Anybody would recommend Eclipse instead of Emacs? Or maybe other IDE? I don't want to learn Emacs and its keybindings, but if I have to, better tell me now. :)
[+] berlinbrown|13 years ago|reply
Clojure has been grown up, it hasn't change that much since several years ago, especially on the surface.
[+] gtani|13 years ago|reply
This guy reminds me of Uncle Bob