Hacker News new | comments | show | ask | jobs | submit login
Problem Solving and Clojure 1.9 with Rich Hickey (case-podcast.org)
231 points by tosh 70 days ago | hide | past | web | favorite | 91 comments



On a tangential note, I'm learning Clojure and kind of just getting started on using it.

I like it how its a Lisp, and ever since I've learned Common Lisp, I've been wanting to use Lisp at work for everyday projects, and Clojure seems to fit just perfectly well.

As with these new things you are always excited to explore further and see what's about to come next. After searching, I was not able to find anything on the Clojure roadmap anywhere on the internet. I understand that Clojure is largely an Cathedral kind of an open source project, largely developed in private at Cognitect and released to the world. But it was still a little strange to not see anything on Clojure's future plans. Sometimes you also see the Clojure repo on Github go without commits for weeks, for example the last commit as of today is July 4, 2018. Which is like strange for a programming language of this size.


I don't find that too surprising, Rich tends to let things bake internally (in his head and at Cognitect) for a while before releasing anything publicly for consumption and feedback.

Most of Clojure's current development is around tooling [1] and some changes to move clojure.spec out of alpha. I believe they want to accomplish this for 1.10, but I'm not sure where I remember hearing that.

Alex Miller has mentioned that Cognitect built and continues to work on a tool that tracks all of your functions with spec definitions and generatively tests them only when the function or one of its dependencies changes. This would be extremely helpful since generative testing can be expensive to run, especially if you want to generate huge amounts of test cases. It'd be nice to say "test all of my codebase's functions 10 million times" and then have that information stored locally with the code.

In Rich's "Effective Programs" talk [2] he outlines the "10x problems" that Clojure was targeted at solving. In it there are two 10x problems that weren't solved by Clojure: resource utilization and libraries. There's not much Clojure can do to solve resource utilization as it's dependent on the host (JVM), but the library problem (especially Clojure libraries with spec definitions) is solvable (though there seems to be some disagreement on whether only allowing growth in libraries in a practical).

Pure speculation: All of their tooling projects (spec, codec, tool.deps, and the above mentioned generative test runner/tracker) are going to come together in a tool that attempts solves the library dependency problem. Rich outlines a lot of his thinking about how to accomplish this in his "Spec-ulation" talk [3]. He wants Clojure to "become the first community to make [dependencies] great". I hope that we'll see more tools and guidance from the Clojure team on this soon (and maybe improved out-of-the-box error messages)!

[1] https://github.com/clojure/tools.deps.alpha [2] https://youtu.be/2V1FtfBDsLU?t=29m53s [3] https://www.youtube.com/watch?v=oyLBGkS5ICk


It's a journey. :) Do not expect "one grand tool" but as you've noted, all of these things are pointed in the same direction.


That's a better way to put it. Thanks Alex. I'm excited to see where we end up.


Clojure Spec is one of the newer developments in Clojure that is being worked on: https://clojure.org/about/spec

On the ClojureScript side there is also a lot of movement. I follow https://twitter.com/mfikes on Twitter to get a heartbeat.


> the last commit as of today is July 4, 2018

That's not terribly surprising. Clojure is incredibly stable. It's a pattern you'll see across the ecosystem. There are libraries that haven't seen a change in 2+ years which still see heavy usage and downloads. In that regard, it's unlike any other ecosystem I've seen. Don't let it dissuade you-- after a while, you'll get used to it and see it as a strength, and by comparison most other languages will seem insanely churny.


To be fair I think most of the OSS languages are Cathedrals. Golang recently had an uproar over dep (community lead initiative) vs vgo (core committer). Ruby seems similar. With the recent departure of Guido as BDFL I think Python is the exception.

The low number of commits is somewhat intentional in that they actively avoid providing too much functionality in core and relative to Go which has the might of Google backing it there's fewer people with hands on who are incentivised based on innovation. It would be nice to see more frequent integration of commits from the community but ¯\_(ツ)_/¯.


If it helps, the core team uses Atlassian JIRA [0] for issue tracking and Atlassian Confluence [1] for design and planning discussions.

[0] https://dev.clojure.org/jira/browse/CLJ

[1] https://dev.clojure.org/display/design/Home


I was just considering learning Clojure recently, what resources would you recommend?


Many people talked about reading the book "Clojure for the Brave and True", which I did. It's nice to begin with it for the first chapters and read the rest later, after some coding, in my opinion. For me, the best resource was the MOOC on Clojure available at https://moocfi.github.io/courses/2014/clojure/ You can do the exercises, have the system check your results, but don't get grades or any certificate of completion.

I didn't finish the course as I moved to Elixir, although I liked Clojure very much. Elixir is also functional, much better for newcomers, lots of support, people, documentation, examples, books, courses, growing very fast... And one doesn't need to wait several seconds to start the virtual machine.


It's a simple and gentle introduction, but surprisingly effective at slowly immersing you into the language to the point that you feel comfortable exploring on your own.


In addition to Clojure for the Brave and True that others have recommended, http://www.4clojure.com is a collection of bite sized problems where you fill in the blank to solve the problem. After solving a problem, go look at a few top users and compare against their solution.

When I was first getting into lisp/functional programming a few years ago I would find that after writing a 6-8+ line solution on some of the tougher problems, the top users would have a way of doing it in 1-2 either due to a standard library function I didn't know about or they just took a better approach overall.


If you already know a little Lisp, The Joy of Clojure is a very good book to start at.

But I went through a complete journey of reading ANSI Common Lisp, The Little Schemer, The Seasoned Schemer, On Lisp and various other scheme material on the internet.

So syntax and other Lisp related stuff wasn't the problem. The Joy of Clojure book showed the Clojure way of doing things really well.

If you have to start on absolute basics of Lisp, I'd recommend watching the SICP lectures and reading PG's ANSI Common Lisp book.


Clojure for the Brave and True by Daniel Higginbotham is a wonderful intro into the entire ecosystem (Leiningen, Clojure/lisps in general, Java interop, emacs, etc.). If you're already experienced with Lisps, you could probably start with The Joy of Clojure.


Getting Clojure (Russ Olsen), Clojure for the Brave and True (Daniel Higginbotham), Living Clojure (Carin Meier), and Programming Clojure (me, Stuart Halloway, Aaron Bedra) are the best intro books, in my opinion.


The Clojure Programming book, it's excellent both for beginners and advanced users (I've been using Clojure since 2013).

https://www.amazon.com/Clojure-Programming-Practical-Lisp-Wo...

A lot of tips and insight of how to do and not to do things in Clojure without too much hype. "Practical Lisp for the Java World" - the title does not lie :).


Clojure Programming is great, but is at this point pretty old. Thanks to the stability of Clojure, the language parts are largely still accurate, but it is missing features from the last several Clojure releases and the the evolution of the ecosystem outside the language.


Yes, I agree. Your book (Programming Clojure) is great at that and quite up-to-date I believe.


This is largely due to the core team being on and off vacation during the summer. :)


The core team also works on other parts of the core libs - so you'll see commits and releases in tools.deps.alpha, the clj installer, contrib libs, or in Datomic, which the core team also works on.


Just wanted to say thanks for making such awesome stuff! I've used clj/cljs/datomic on several projects over the years and it's been great.

The Clojure core team does not get anywhere close to the praise it deserves.


Thank you Alex, for the all the work over the years and brining us this beautiful piece of technology.



Nice, thanks for the link! :)

Also:

> That is the number one problem in programming - the time [thinking] is not spent, and people flounder around.

That's a summary worthy to be framed if I ever heard one.


Rich made some interesting points on developing libraries in such a manner that it doesn't introduce breaking changes (for the calling code). Does anyone here agree (or have counterpoints) to his suggested approach?


Clojure has been the most stable ecosystem I've ever dealt with. Once you get into it it's not uncommon to see libraries that are years old that function perfectly. I'm not even scared to update clojure to the latest alpha builds because things just always work.

There's also this to look at on the subject: https://lkml.org/lkml/2018/8/3/621

Reddit discussion: https://www.reddit.com/r/linux/comments/95b1hf/linus_torvald...


It is amusing, because the very thing that leads to stability is among the things that causes people to claim ecosystems like CL have gone "stagnant."

Similarly, TeX shows the halmarks of stability. I can reliably rebuild any document I've ever written. However, that means that the styles I used to use to write documents have to still be supported. Contrast that with HTML, where they constantly give new methods to do something because they changed their mind about how it should be done. Worse, they no longer support the old ways, because they were supposedly "never supported."

I think there is some merit to change driving progress. It is just frustrating when so much change is effectively user hostile.


Yea, that was my impression about the CL ecosystem and clojure made me rethink that. Nowadays it's pretty much the other way around for me - CL serves as an example of how clojure could keep dying and still be a useful tool for years to come


I think part of the point of clojure is a hosted, small core, programmable programming language can't really die, it would just be dormant and could spring back whenever someone needed it.

It's weird to me when people claim clojure is dying, yet the ecosystem is evolving rapidly and people are making money with it. It's not a model that's easy to starve, it's too lightweight to collapse under it's own load, and there's no way to kill it, so how is it dying?

Mindshare ebbs and flows, but being THE programming language was never clojure's goal.


I think most of the crowd claiming that clojure is dying are those that feel that way by virtue of it being a lisp. And, oddly, most habits in most languages tend for them to pull more and more into them such that they are all looking for an uber language.


You might be right. I do think that by being intentionally hosted, it avoids a number of traditional lisp problems. If it was a stand alone implementation, I wouldn't be nearly as bullish on it.


I have enormous respect for Alex et al that are maintaining Clojure at Cognitect. From my observations there tends to be few changes in core which is reinforced by a fireside chat with the guys at Cognitect showing the level of Java code churn over time has continually decreased. Everything has benefits and tradeoffs. The benefit of this lack of churn is a stable foundation but I think the tradeoff is that there aren't as many eyes on that code and the depth of familiarity required to improve the code for legibility, maintainability, performance, and security is lost with lack of intimacy. Much of the Java code is undocumented and at times can be hard to follow with unused variables and little if any tests available to validate behaviour. It's delegated much higher up the stack.

During the release of 1.9 there were some issues relating to spec, libraries, and build tools in the pre-release versions. So I'm not sure I would say the rule is followed to the letter such that you can consume Alpha releases are safe. It was prerelease candidate so no biggie. I know there were some discussions on the mailing list of a formal security audit of the Clojure but I'm not sure where that went but I'd be interested in seeing the results.


> Maybe it worked because the user had taken the bug into account, maybe it worked because the user didn't notice - again, it doesn't matter. It worked for the user.

That's also an argument for keeping security holes around. By definition those are bugs and users (albeit malicious ones) depend on them. How dare you fix a security hole and break a perfectly working exploit!


Only at an absurd reduction. Few security holes are actually built on features for users.

Now, there is a tradeoff between security and convenience. That is also why fraud techniques haven't taken away credit cards as a thing. To that end, sometimes it is better to engineer a detection/mitigation strategy instead of removing the convenience.

And life seems to be full of more strawmen then makes sense. So, yes, you can easily find examples for this in either direction.


I imagine in case of a Kernel that should be like a rule beyond any scope of discussion. And may be for any piece of software that is at the bottom layers of any stack.

You just can't break backwards compatibility.

This was a big mistake Python 3 made.


The problem with almost all non-lisps is that you have to add / modify syntax to get a lot of new features (think async/await), whereas in a lisp, most such changes are done via macros and require no breaking changes to the language itself.


> You just can't break backwards compatibility. This was a big mistake Python 3 made.

At least Python's backers had the guts to release a breaking version 3, unlike some other dynamic languages, specifically Ruby and Apache Groovy. Ruby's backers have tentatively slated 2020 for a (MRI) Ruby 3 release after two more Ruby 2.x versions. As for Groovy, 2 months ago its backers canceled its version 2.6 release which was to backport the planned features of Groovy 3 into a JDK 7 compatible release. Looks like we won't be seeing version 3 of either language anytime soon.


> At least Python's backers had the guts to release a breaking version 3, unlike some other dynamic languages, specifically Ruby and Apache Groovy.

The backward-compatibility breaking Ruby release often held up as a parallel to Python 3—Ruby 1.9—was released December 25, 2007, about a year before Python 3.

If Python had as much guts as Ruby in this area we'd be seeing a backward-compatibility breaking Python 4 around 2021.


>>At least Python's backers had the guts to release a breaking version 3,

If you have to absolutely must break backwards compatibility. Break it once, and never again, in a way you fix your language permanently. Like Perl 6.

Python broke backwards compatibility for a print statement.


Gradle is the only thing holding Groovy alive, beyond maintenance projects.

I remember attending JSF Days in 2009 and how we would be all writing Groovy JEE Beans in the near future, show in different ways across a few sessions, and here we are.


... and Android Studio could be the only thing keeping Gradle alive, beyond 20-liner build scripts. When Google finally releases Fuchsia, it will virtually replace Android's market share within 2-3 yrs. Because Flutter uses Dart for building apps, it'll probably go with Dart for specifying build scripts as well. Although one of the Apache Groovy PMC is probably busy politicking inside Google to get them to use Gradle for Flutter, hopefully that team won't repeat Android's mistake. Otherwise the 3rd-party app market for Fuchsia will be stillborn.


Yep, if it wasn't for Google having the silly idea of adopting Gradle, I would never bothered to learn it.

I don't suffer from XML allergy and am pretty fine with Maven in what concerns Maven projects.

Actually I was pretty happy with Eclipse + Ant + ndk-build as well.

To this day the NDK already went through a couple of Gradle build variations and it still doesn't support NDK libraries across NDK projects, as AAR only support binary libs for Java apps.

I follow Fuchsia with attention, but it might end up just as Brillo.


there was freedom I feel Ruby had that Python didn't which allowed Ruby to successfully go from 1.8 to 1.9;

* Ruby had better dependency management. Python's only recently introduced Pipenv which provides a standard approach for managing development and prod dependencies. VirtualEnv's been around for a while but didn't handle scope.

* Ruby wasn't used as extensively as Python in stuff like system libs and start-up scripts so system packages weren't constrained in the same way.

* Ruby's ecosystem was more focused around web development. Rails in particular was fairly early in adopting new versions of Ruby within about 1 year of the release of Ruby 1.9. Django on the other hand was about 5 years before it had it's first Python 3 compatible release.

* "microbenchmarks" generally got better with Ruby releases whereas Python 3 seemed to have gotten worse from what I remember. I don't think microbenchmarks are terribly useful out of the context of an actual application but many people use them as indicators.

* subjectively I think the Ruby community was more committed to unit testing which made "fearless upgrades" a little more palatable.


That's why it's called Python 3 and not Python 2.x

Think about it like totally new language


If it's like totally a new language, it should be called TotallyNewython 1.0


AlmostTotallyNewthon would be better, correct. Would shift discussion from 'upgrade' to 'migration' without all the usual cries. Other than that it's understandable that some architecture decisions from 1991 haven't aged well.


I think “Perl 6” was already taken, so they couldn’t use that :-)


This is essentially the model the Go community is aiming to do with versioning standards. It's also the philosophy the linux kernel takes to e.g. syscalls.

It's something a lot of libraries could learn from. Most users want to build software for the long term and tend to avoid upgrading libraries too often.

Fwiw, this is a large part of Rich's Spec-ulation [0] talk.

[0] https://www.youtube.com/watch?v=oyLBGkS5ICk


I don't know the specifics of the Go standards but semantic versioning doesn't add anything if you don't break your consumers.


It sounds like the philosophy is "don't break your consumers", which I agree with. Somebody else already linked his Spec-ulation talk, but I think somewhere in there he compares the 10 minutes a library author spends on deprecating or removing some piece of code versus the 10 minutes each and every one of your consumers spends trying to determine why a dependency they put trust in broke their application.

I do think it can be very difficult to know if you, as a producer, are breaking any consumers. I'd love to see some sort of OSS build tool and CI system that automatically rebuilds and tests all consumers of your library to give you, the library author, more information about the changes you make before releasing.


Rust (core) uses the cargo ecosystem as tests


Similar idea in ClojureScript: https://github.com/cljs-oss/canary


Around 14:50 in Rich talks about breaking changes and mentions a library he worked on (codec) to track function changes (checksums, not semantically but still) instead of looking at a library as 'changed' as a whole. This makes a ton of sense.

Knowing a set of functions I rely on did not change (at all) provides more peace of mind to me than just knowing the function signature is still compatible.

Versioning (or at least understanding changes) on the function level in addition to the library level sounds so powerful to me that I wonder why this concept isn't more mainstream yet. Are there other examples?

[edit: I found codec: https://github.com/Datomic/codeq#codeq]


He’s been thinking some really interesting thoughts about dependency management and libraries. It sounds like if he had his way, library versions might disappear entirely.

From the interview: “But there are really two very distinct kinds of change - there are breaking changes, where your expectations have been violated, and there are accretions, where there's just some more stuff where there wasn't stuff before. And in general, accretion is not breaking.”

To paraphrase stuff I’ve heard him talk about elsewhere, your library should never change a function to require more input or provide less output. If you decide to do that, call it something else and keep the old function around. He’s talked about function-level versioning too, touches on it here a bit. If I use library functions x y and z and the maintainers only broke (the contract for) functions a b and c, then I probably don’t care and can grab the latest version for its other new goodies. But that decision still requires me to read the library source or at least a changelog, even though it’s simple enough for the right tool to be able to report it to me. Spec looks like a step in that direction.


I agree with the principle that you shouldn't arbitrarily change API's. I disagree if it is extended to being an unbreakable contract. There are specific scenarios where people should consider breaking changes most of which fall under some for of improving usability of the library;

1. improving/providing secure defaults. 2. reducing the API surface so that usage is more evident. 3. refactoring that eases maintenance. 4. improving performance where appropriate. 5. probably others I haven't thought of.

In RFC style I would say it's a SHOULD rule rather than a MUST.


In all of these cases you can add new functions without removing (and breaking) the old ones.


Agree in many cases you can do this however with point 2 the removal of a function(s) is literally the aim. The accretion of functions benefits legacy systems however its tradeoff is potential harm to users new and unfamiliar with the library. Accretion creates a cognitive overhead (even if only minor) for both the maintainer and new users. Maintainers when they return to code to update and modify behaviour. New users when they seek to understand the library through documentation, examples, and usage. I don't think it's coincidence that a number of languages and libraries acknowledge this by having "one correct way to do X".

Using a concrete example relating to security; libressl maintained much of the API surface that OpenSSL provides. In essence they aimed to provide a "drop-in" replacement. However there were whole families of algorithms and functions which they deemed "unsafe/unfit" for purpose (e.g. FIPS algorithms). I think that's a perfectly valid exception to the rule. It acts as a canary in the coal mine and you have options; fix your code or defer upgrading.

I would advocate for thoughtful deprecation cycles over ossification of poor APIs and algorithms.


Elm goes a step further, it enforces semantic versioning. If the package has any breaking interface changes, it forces major version change.


That's not a step further, you lose the ability to use the latest version without code changes.


Which in any case you'd have to do in other languages. What Elm provides is a way for library users to decide whether to upgrade the library or not and more importantly not automatically upgrade to a library which needs code changes.


If you just don't have breaking interface changes you never need a major version change.


I'm not an expert in Clojure, I've played with it a little. I think it's one of the best designed languages I've ever seen - and I'm a static-type kind of guy :)

However, I have the feeling that its popularity has been decreasing over the past few years. I very rarely see job descriptions mentioning it. Have people observed the same thing?


I have seen this mentioned once or twice before, and I'm scratching my head as to where this perception comes from. Maybe it's because of its position in the hype cycle?

My personal impression is the opposite.


Interesting. What do you think its position in "the hype cycle" currently is? No longer cutting-edge and therefor a little "boring"?

I also think I saw that it ranked pretty low on various "popular languages" studies. I would be very happy to find out that I am mistaken TBH.


It's achieved it's highest rankings ever on TIOBE this year. We have also seen record high numbers of Clojure language, ClojureScript language, and library artifact downloads this summer.


There's a substantial amount of Clojure jobs every month on the "HN who's hiring" thread.

Personally I would have loved Clojure to absolutely win, but being an elite language for senior devs isn't a bad situation either (I'm much enjoying my latest Clojure jobs and the peers I could find there).


This.

I really love Clojure, but these days I'm still mostly a Java/Kotlin guy. Haven't seen any Clojure job or Clojure being used in production yet, unfortunately.

Sadly, that is a common fate for Lisp(-like) languages nowadays. People are scared of unusual syntax.


I'm curious - how hard have you looked? There are lists with clojure success stories [0] [1] [2] and quite a few companies hiring clojure devs [0] [3] [4].

Anecdotally, there are 5 startups within a 1 mile radius of my office (in Charlottesville, VA) whose primary stack is clojure.

[0] https://clojure.org/community/success_stories

[1] https://news.ycombinator.com/item?id=14302762&__s=r8nvw64mp3...

[2] https://github.com/clojure/clojurescript/wiki/Companies-Usin...

[3] https://www.indeed.com/q-Clojure-jobs.html

[4] http://hnhiring.me/ (search "Clojure", top right)


Sorry, should have mentioned that I'm not in US


We’re based in Bristol, UK and are exclusively Clojure on the backend and starting to use Datomic in production this year.

There are a few other clojure focussed companies in Bristol too.


If I look in LinkedIn I find more job offers for Clojure than golang. For some reasons other metrics do not apply, and I guess golang is more open source related than clojure which is more about building private services.


I don't think that characterization of use is true for either Go or Clojure.


I think the reference might be more in relation to OSS tooling such as docker, kubernetes, and a lot of the other stuff in the CNCF project catalogue which is often VC backed start-ups with OSS codebases.


I'd be interested in clojure if I didn't have to use third party tools to set it up on windows, or if the process was documented so I don't have to.


I felt the same way when I first wanted to try the language (have to use Windows for work). I went to clojure.org only to find the message:

  Installation on Windows: Not yet available - see Leiningen or Boot instead.
That was a disappointment to me, seeing as the language has been around for more than 10 years now. Also, when I went to try the Leiningen route I found that the installer wouldn't work on Windows 10:

https://github.com/technomancy/leiningen/issues/2412

So I decided to try another language instead... But eventually I returned to Clojure and managed to get it up and running by using the fix that's suggested in the GitHub issue above. I'm really enjoying the language, but I fully agree that it can be frustrating to install on Windows.


The leiningen setup has been fairly stable, if still a bit obnoxious these days. It's a bit more difficult to get the supporting tools up than the language, in my opinion.


That's disappointing. I've been using clojure with windows 10 and didn't run into this problem, but I think I used https://scoop.sh/ to install it. I'm not really interested in the clj tool and deps.edn until it's gone through a few releases.


We've done 13 releases of clj so far. It's pretty stable now.


Stay tuned! Work in progress.


If you use the linux subsystem you can simply use the clj tool. Not a perfect solution, but it's workable.


And if you are on win7? :)


I have a powershell port, just haven't quite polished it enough to release it yet. But it's coming!


I will definitely be interested in that when released :) Less friction will mean more adoption...


Side query: are there any good (or any) Lisp-related podcasts? (In the sense of the whole series being Lisp-related rather than a particular episode.)


If you consider Clojure Lisp-related there's https://m.soundcloud.com/defn-771544745 (disclaimer: I'm a co-host) and there is http://blog.cognitect.com/cognicast/ from Cognitect (the company that stewards Clojure)


Thanks! I had known (but forgotten) about the Cognitect one, but defn is new to me.

I also came across (not a Lisp-devoted series admittedly) but an episode of another podcast which focusses on Racket [https://softwareengineeringdaily.com/2015/11/04/racket-with-...].

I haven't found any podcast series devoted to (Common) Lisp or Scheme so far though.


Trivial asside: almost everyone on this site says ‘disclaimer’ when they mean ‘disclosure’ - unless it’s some cutesy humble tongue-in-cheek thing?


I tend to disown because we call the podcast _irreverant_ for a reason :)


Any recommended resources for experienced developers on the OO side wanting to learn Clojure?


Clojure For The Brave And True is where I learnt Clojure and I can personally recommend it as a good read. It does start out slowly but the FP concepts can be a little tricky to grok coming from an OO background.


I really didn't enjoy the book when I read it in ~2014 I think. It's a pain to get set up with emacs and then it walks you through functional programs without introducing anything first. And it is indeed difficult to grok, especially because they are toy programs with zero real world value. In that sense, I'd compare it to Learn You A Haskell For Great Good, but with less context.

This[0] is a better way to learn, in my opinion.

Then after that, maybe check out Brian Will's videos[1].

Then definitely check out this helpful video on web dev[2]. There's a better video somewhere but I can't find it anymore. It's just a guy walking his friend through how to make an isomorphic clojure web app. Very nice pair programming walkthrough guide. But I guess it's forever gone.

[0]: https://kimh.github.io/clojure-by-example/

[1]: https://www.youtube.com/playlist?list=PLAC43CFB134E85266

[2]: https://youtu.be/LcpbBth7FaQ




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

Search: