Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Google SoundScript: faster OOP for JavaScript (2ality.com)
68 points by jashkenas on Feb 4, 2015 | hide | past | favorite | 60 comments


This makes sad, as Douglas Crockford said in this recent talk

> The prototypal school has a lot of advantages particularly compared to the classical school

> So in the classical school you have to create a classification of all the objects that are likely to be in your system. And, it's a lot of work to figure out what they all are and identify them and what their characteristics are and so on and then you have to determine how they are all related to each other, what’s going to inherit from what, what’s going to implement what, what’s going to interface with what, and that’s really complicated. And it usually happens at the beginning of the project before you fully understand what all this stuff means. So it’s likely your going to get the taxonomy wrong. It’s inevitable.

> And then you’ve got two hard choices. One is you have to live with a broken taxonomy and if you do that then with each new class you introduce things get weirder and weirder because everything’s wrong, or you have to refactor and refactoring is hard and it’s error prone and it’s another world of hurt.

> Those are your two choices and people who grew up in the classical school assume that’s just the way life is so no sense in complaining about it since that’s life.

> But in the prototypal school you don’t do any of that. You just make an object, if you don’t like the way it works you make another one and that is literally it.

> So you can tell people from the classical school “You don’t have to do all of that” and they go “Yea, I hear ya, but you still have to do it” and you say “no, you don’t have to do it” and they go “yea, yea, yea” (blowing you off)

> And so that’s why I think classes in ES6 are a bad part because they lock you into that paradigm.

> applause

> So when you’re locked in a paradigm like that, when you can’t see past it, you just have no understanding how miserable you are. And you’ll never know until you change your model

from https://www.youtube.com/watch?v=bo36MrBfTk4#t=28m50s


I don't mean to be argumentative (being argumentative on HN is so passé), but what you just quoted is so full of shit that I feel like someone is obligated to comment.

In the prototypal model — as it exists in JavaScript — you do do all of that. You make a series of prototypes that you intend to use in your application, and those prototypes (or classes), serve as the effective taxonomy for many of the objects you're making in your app.

Classes in ES6 are prototypes. They don't lock you into any such Java-esque paradigm unless you're going to be willfully ignorant about them.

Please don't repeat baloney just because Crockford said it.

---

For example, to be concrete:

    class Crockford {
      speechify() {
        return "...you're going to get the taxonomy wrong. It’s inevitable.";
      }
    }
A class. A terrible, inflexible, prototype-destroying, paradigm-locking-in, JavaScript class. Eww, gross.

But wait! What's this?

    Crockford.prototype.speechify = function() {
      return "...the shape of an object can change at any instant, because \
             someone can fiddle with the prototype.";
    };
You can have your classical cake and eat prototypal cookies too. Because in any decent dynamic language, the two concepts are synonymous.


It's not baloney IMO. As someone who's coded C since the 80s and C++ since the 90s I find JavaScript massively freeing.

In C++ (and similar classical languages) in order to be able to pass in different implementations of a class you need to create a base class or interface. You don't need to do this in JavaScript.

In JavaScript you can build objects on the fly. In fact he goes on to say

> so I used to think that the important thing in JavaScript was prototypal inheritance I now think it is Class Free object oriented programing. I think that is JavaScript’s gift to humanity. That’s the thing that makes it a really interesting important language, is that it did that. That we can use functions in order to create objects and it simple and elegant and powerful and fast...

And it's true. Look at the mocking libraries for JavaScript. They're sooooooooo much easier to write than anything for classical languages because it's so easy to do in JavaScript. In my own experience I have a mutli-user networking system written in JavaScript and a library in C#. To reimplement the same things in JavaScript in C# is a huge amount of code bloat. I have to make classes and meta classes and all kinds of other indirection just so I can make an Event system that is generic. In JavaScript that's 2 or 3 lines

That we don't actually need all the structure to get shit done is what people are finally coming to realize.

The problem with 'class' in ES6 is that it will prevent people from learning this new, easier, faster, simpler way of working because instead they just bring the classical baggage to JavaScript. That's in fact what most programmers from classical languages do. It's what I did for years until I started actually grokking JavaScript. ES6 "class" will only make it take longer for people to actually get pass their old ways.


> As someone who's coded C since the 80s and C++ since the 90s I find JavaScript massively freeing.

I've gone back and forth between dynamic and static and strict and loose in every combination and I don't find the dynamic/loose to be particularly freeing anymore. The problem is you can't really rely on anything. It doesn't so much as solve the problem that Crockford is talking about, it just moves it somewhere else. You don't have to refactor but you're going to end up with two (or more) different things that exist at the same time instead of one.

> Look at the mocking libraries for JavaScript. They're sooooooooo much easier to write than anything for classical languages because it's so easy to do in JavaScript.

This is a great point and it's a perfect example of the advantages and the horrors of the JavaScript model. A mock is supposed to be an object that does something completely different (typically nothing) but otherwise looks and functions like the real thing. Maybe just in one slight exceptional way. JavaScript makes this easy. But it also makes it easy to make those same changes outside of mocking and tests and that is just debugging hell waiting to happen.


You can write bad spaghetti code in any language. You avoid that with style guides, linters, and code review.


I don't see how strict typing couldn't fit at the end of your list as well.


It could but some of us have had the experience of massive productive increases in JavaScript over our decades of experience in statically typed languages.

And note I'm specifically calling out JavaScript. Other dynamic languages IMO haven't provided the same benefits because they're still following old models.

When you truly grok JavaScript and stop trying to use it like your other languages it really starts to shine. It's not perfect but it is different.


> And it's true. Look at the mocking libraries for JavaScript. They're sooooooooo much easier to write than anything for classical languages because it's so easy to do in JavaScript. In my own experience I have a mutli-user networking system written in JavaScript and a library in C#. To reimplement the same things in JavaScript in C# is a huge amount of code bloat. I have to make classes and meta classes and all kinds of other indirection just so I can make an Event system that is generic. In JavaScript that's 2 or 3 lines

That does not follow from what you quoted, and what you quoted is a non-sequitur to start with.

What you're raving about can be achieved just as easily in any dynamically typed language. Hell you probably get 80~90% of that in a structurally typed language (OCaml objects, Go).

> In JavaScript you can build objects on the fly.

Which you can do in many other languages, including statically typed ones. Here's an object in Ocaml:

    # let s = object
        val mutable v = [0; 2]

        method pop =
          match v with
          | hd :: tl -> 
            v <- tl;
            Some hd
          | [] -> None

        method push hd = 
          v <- hd :: v
      end
And that's statically typed.

> The problem with 'class' in ES6 is that it will prevent people from learning this new, easier, faster, simpler way of working because instead they just bring the classical baggage to JavaScript.

That's patent nonsense, not to mention ES6's class shortcuts apply to object literals as well, the only thing the class statement does is remove the bullshit and mostly useless boilerplate Javascript demands when creating a reusable prototype due to it being a garbage language, and its "prototypal" object system being there not because it's good and going to save the masses but because it was the easiest way to have an object system working in a week. It's just a sad and pitiful shadow of Self's object system from which it draws so little you have to know the relation's there to find it.


> That we don't actually need all the structure to get shit done is what people are finally coming to realize.

Getting stuff done until the code base grows and others have to adapt or build up on the code you've written : the idea of bringing in structure is to encode knowledge about the problem you are solving and keeps it all maintainable (admitted, if done right).

> In C++ (and similar classical languages) in order to be able to pass in different implementations of a class you need to create a base class or interface.

And that is a good thing IMHO, see above. However I agree that, particularly in C++, an interface definition is very verbose. Unfortunately it has no pure Interface concept.


You can always use pure virtual classes, but requires discipline and virtual inheritance.


As a javascript programmer, I have perceived es6 "classes" as nothing but syntactic sugar. They should just construct the equivalent constructor+prototype combo. You can still create anonymous objects quickly, and use any object as prototype for another.


> As a javascript programmer, I have perceived es6 "classes" as nothing but syntactic sugar.

That's basically what they are.

> You can still create anonymous objects quickly, and use any object as prototype for another.

Hell you can create anonymous objects even faster with ES6 since object literals also get the short method notation and computed properties.


Yes, but let's admit it, the presence of classes will inevitably lead to their ever-presence, if only for the fact that they are what most people coming from other languages are used to.


The absence of class already leads to their ever presence, each project simply reimplements its own instead of using the built-in syntactic sugar. The class sugar is simply a way for the creation of (prototype + constructor) pairs not to be a pain in the ass, nothing more and nothing less.


" They're sooooooooo much easier to write than anything for classical languages because it's so easy to do in JavaScript."

You can do that in a language with classes and even one with optional types e.g. you can use jasmine perfectly well to mock typescript objects (because they are just JavaScript dynamic objects)

" I have to make classes and meta classes and all kinds of other indirection just so I can make an Event system that is generic."

I remember reading Gilad Bracha saying somewhere that enabling exactly this kind of system was exactly one of the use cases for Dart which is class based with optional types. You can have your cake and eat it too!


I think you're confusing this new Chrome-only thing with ES6 classes that just saves you from using a library to create constructor/prototype objects. In ES6 "class" is just a keyword that makes it easy to contruct objects that are very typical in day-to-day JS. Inheritance is already a thing that happens.


I'm not. See above, "class free object oriented programming". You don't need prototypical inheritance to have inheritance in JavaScript. Crockford gives examples. You don't need the "class" keyword. All it's going to do is steer people in the wrong direction.


Can you give an example of inheritance without prototypes in javascript?


from D.C.

    function base(spec) {
        var that = {},
            member,
            method = function() {
                // spec, member, method
            };
        that.method = method;
        return that;
    }

    function derived(spec) {
        var that = base(spec),
            member,
            method = function() {
                // spec, member, method
            };
        that.method = method;
        return that;
    }
I'd suggest you watch the talk linked above for this stuff in context.


That is not inheritance. `instanceof` doesn't work. There would be no way to know that an object from derive dis of the type of base.

That's also very memory inefficient. The method function gets made for every instance. With prototypes there would be only 1 function in memory. This stuff matters at scale.


Watch the talk.

You've got a gig of ram in your pocket. Memory doesn't matter for 99.99999% of apps. The memory taken by your objects isn't where your memory goes. It goes to images and other big stuff. A few extra bytes per object isn't going to kill you. Optimize for programmer time not memory, especially not in memory on things that don't matter.

Why are you using "instanceof". The whole point of inheritance is you shouldn't have to ask. If you are asking your doing it wrong


and then how do you implement super with your system? because that's the point of prototypes,the fact that you're not overloading methods but you have access to the prototype chain.much harder to do with your method.


You shouldn't need access to the prototype chain. Accessing that chain is an anti-pattern.


no it isn't,that's how you implement "super" in javascript.


Check out Daniel X. Moore's `{SUPER: SYSTEM}`. http://stackoverflow.com/a/3731253


> You just make an object, if you don’t like the way it works you make another one and that is literally it.

This is, like jashekanas eloquently puts it, so full of shit.

You're still creating a bunch of objects. Unless you are a mashocist (or a sadist, if other people have to maintain your code), you are still going to want to reuse methods between those objects.

Once you do that, you have to have some organization between what gets reused how. Sure, prototypes give you more freedom to define different ways of organizing and reusing behavior across objects. In practice, single-delegation actually works pretty well most of the time.

> So you can tell people from the classical school “You don’t have to do all of that” and they go “Yea, I hear ya, but you still have to do it” and you say “no, you don’t have to do it” and they go “yea, yea, yea” (blowing you off)

It's not that you don't have to create a taxonomy in JS. It's, "First you have to conceive and implement the very idea of a taxonomy itself. Then you get to use that to create a taxonomy."

You need to reuse code somehow. Figuring out a scheme for doing that is metaprogramming. JS basically says, "Well, you have to do this metaprogramming before you can get to the task you actually care about."

Using classes (or any other baked-in reuse scheme like parent slots in Self) just means the language has done the metaprogramming for you and you can use it out of the box.

The flexibility JS gives you is nice when the language's built-in mechanisms for code reuse aren't a good fit. But, so far, most homegrown code-reuse-systems don't seem to be that much better than what classes (with maybe some mixin or trait special sauce on top) provide.


> You're still creating a bunch of objects. Unless you are a mashocist (...) you are still going to want to reuse methods between those objects.

Aaaaaaand solution for that doesn't have to be a class (or something pretending to be a class), but for example a collection of functions/methods that you attach to an object, so you for example assign behaviour to data. This is something that Reginald Braithwaite for example advocates.


> a collection of functions/methods that you attach to an object

Yes, that's what I meant when I said you have to design your own metaprogramming layer. You can do that. Just like you can invent your own class or prototype layer in C if you like.

The question is "Is having to do that a compelling feature for the language?" Are user's metaprogramming needs so diverse that the language should punt this problem onto users?

I don't see much evidence that this is the case. Most JavaScript libraries end up reinventing something that's within spitting distance of classes and single inheritance, maybe with a dash of mixins. All of that re-invention is a huge waste of engineer time.


That's called "composition over inheritance" and OO languages are perfectly capable of it.


I don't get it. I know how prototypes in Javascript work, I can agree that you get a certain degree of flexibility that you don't get with class-based languages, which sometimes is useful, however people are still modeling classes in Javascript and people blaming education have the burden of proof - show problems that could have been solved with classes, but that were solved with a "class free" design instead.

And heck, I'll go further - in my mind, both class-based and prototype-based OOP, at least as present in Javascript or other mainstream languages, sucks because it mixes data with behavior and still suffers from the expression problem, because while you can take any object and modify it for a new interface, a process called monkey patching, that's just a terrible way of doing it.

OOP as a means to achieve polymorphism is simply inadequate for many use-cases. Sometimes it is much better to have type-classes (or similar, like protocols in Clojure). So you know, comparing 2 ways of dealing with OOP, misses the point that OOP on the whole is not enough, even broken as some would say.


> I can agree that you get a certain degree of flexibility that you don't get with class-based languages

That's not even the case. Not in Javascript anyway. The only thing you get is not having a separate hierarchy of class objects.


Most class based languages won't let you inherit from an instance, and many won't let you use the same techniques you use to inspect or constrain instances to inspect or constrain classes.


>But in the prototypal school you don’t do any of that. You just make an object, if you don’t like the way it works you make another one and that is literally it.

Yeah, which goes to show you shouldn't listen to Douglas Cockford.

First, what he describes is playing in a REPL at best. You don't just "make another [object] if you don't like the way the fist one works" when you write a program, unless you write a structureless blob. You still have to think about your objects and/or inheritance chain, be it prototypal or not.

Second, ES6 classes are nothing more than syntactic sugar over barebones prototypal inheritance.

Third, you already do all that stuff in JS. You have to write a lot of boilerplate code (and usually everyone does a slightly different version) to get the same end result.


I feel like the concepts are being abused here. Prototypical — at least in my understanding — refers to a mechanism of inheritance or delegation, and not as Crockford uses the term to the way you build your objects. Confusingly, JavaScript didn't even have prototypical inheritance until relatively recently, because even though every object had a prototype, it couldn't be set or changed on an individual basis, but was determined solely by which Constructor (read: Class) that created it, which made JavaScript's inheritance mechanism essentially indistinguishable from the classical, albeit with more obscure nomenclature.


ES6 classes are just syntactic sugar for constructor/prototypes like we write them today. You can mutate them as much as you like.


not in SoundScript they aren't


Pretty sure they still are syntactic sugar for prototypes, just like in ES6. But SoundScript enforces that you cannot modify prototypes. There's not really a problem with that, it's doing it for speed. So it's not for your average Javascript app where performance doesn't matter as much.


although i agree somewhat, prototypal inheritance does nothing to help solve the taxonomy.

the article below i think explains the situation much better: https://oleksandrmanzyuk.wordpress.com/2014/06/18/from-objec...


I see the 'stricter' mode (without types) as a kind of asm.js assertion. The code is 100% legal JS and will work on all JS VMs, you're just telling the VM that it can assume you won't modify class prototypes after load. The VM can check this assertion and fail if you violate it, and then run at fulls peed.

On other VMs that ignore "use 'stricter'" the same code runs, just less optimized by the VM.

The stricter+types stuff will be harder to swallow by the community since it extends the language grammar. That will break other VMs without transpilation.

Maybe they could do a "use 'stricter+asm'" which has the class-sealing properties SoundScript, but with the type-declaration syntax of asm.js (|0, 0, etc)


Sealing prototypes is easy - V8 already "pseudo"seals them: it removes the map checks against prototypes and instead deoptimizes the code depending on hidden classes when somebody changes them.

Similar pseudo-sealing technique can be applied to objects: when optimizing some code you can assume that if some hidden class is a leaf in the transition tree it is no likely to change - then make the code depend on this assumption. This would mean you only need to check this once.

I think the most interesting part that stricter-minus-types mode brings to the table (at least it's informally described part) are hole-less arrays. This is something that is missing from the ES as it is described now: new Array(N) is producing a hole-full array. V8 is capable of tracking whether array is containing holes or not - but the way to efficiently preallocate holeless version is simply missing from the ECMAScript.


I'm still unclear on the relevant details, though. If this is just a hint - no semantic effect - then JS engines already speculate and assume you won't modify class prototypes after load. Such speculation is what JS engines put a lot of work into.

Maybe the hint would justify more aggressive speculation, I suppose?

Or, if this is more than a hint - if it has semantic effects, like "use strict" does - then this would be very different than "use asm".

I guess when more info comes out this will become clearer.


I'm guessing that it implicitly implies Object.seal() on all prototypes.

Any legal program that uses 'stricter' and depends on sealed prototypes would be a legal program on a VM that ignores stricter, except in the case of bugs where code was modifying prototypes when it shouldn't, but those would presumably be caught during testing on VMs which did use it.

Presumably, "use stricter" on a codebase that contained attempted prototype modifications would generate errors instead of silent failures.

I don't know the full reasoning behind this, but it might be more about startup time than runtime speed. If you assume early-bound classes, snapshotting becomes a lot easier, you can ahead-of-time trivially compile hidden classes without having to discover them after the fact, and without having to put in code to revert the assumptions later.

Seems like this would be a startup and memory win mobile web, especially if you can easily snapshot a previously loaded app.


IMHO for the most part, programming with classes in JS is just waiting for pain. If you use generic objects for state, then compose your applications by using generic functions passing in state, and any other arguments that function needs to operate, it goes much smoother than OOP.

You can create wrappers by binding independent methods against said state for composite objects with encapsulation... It tends to keep code cleaner and more modular. Sometimes just thinking about the problem differently will lead to a more elegant solution...

    var Promise = require('i-promise')
        ,getDealerPendingStream = require('./process-get-dealer-pending-stream')
        ,transformDealerPending = require('./process-transform-dealer-pending-stream')
        ,calculatePayment = require('./process-payment-calculation')
        ,logTransaction = require('./process-log-transaction')
        ,updateSalesforce = require('./process-update-salesforce')
        ,runPayment = require('./process-payment')
        ,logItemOutput = require('./process-log-item-output')
        ;

    module.exports = function(){
      return new Promise(function(resolve, reject) {
        getDealerPendingStream()
          .pipe(transformDealerPending())
          .pipe(calculatePayment())
          .pipe(runPayment())
          .pipe(logTransaction())
          .pipe(updateSalesforce())
          .pipe(logItemOutput()) //email notifications for processing errors (dataObject.error)
          .on('error', reject) //should never happen
          .on('end', resolve)
          .resume() // needed so last stream can process data
          ;
      });
    }


How would you write a GUI framework with this method? because that's where OOP shines.Here,you're just doing sequential operations on a chunk of data.How would you write layouts and widgets? people often forget that OOP was kind of created because it solves the UI problem.


You can still uses events and state against UI constructs without going full-on OOP. An application can render in similar ways to how flux, mercury, elm and other patterns are handled. (not suggesting a virtual DOM... but there's more to an application than the UI alone, and there are more functional ways to apply state and eventing to a UI.

    uicontrol.on('eventName', handleSomeEvent.bind(null, state, uicontrol));
In this case, you still have a uicontrol, that raises events, and can use that to trigger a generic handler pre-bound against a given state. You can remove this farther by having the handleSomeEvent being a method that is already part of a state machine that is bound against an instance of said state.

If you follow 1-way dataflows (like flux) you can break some of the other constructs down... just the same, the above example is not OOP, but can still use UI events, and be composable.


Same way Elm does?


I thing this is a testament that strong typed is more desired than dynamic.


Could this be (partially) motivated by making Dart-compiled JS run faster in Chrome?


If I had to place a bet now, it would be on asm.js. It's the abstraction that nobody wants but everyone can live with, because it takes the standards bodies out of the equation on the higher level decisions that have to be done differently per domain.


So there have been a lot of contenders to replace JavaScript, and it hasn't budged from it's market seat at all. Here's why:

You can do almost anything in JavaScript these days. The down side? You can do almost ~anything~ in JS these days. Including lots of stupid things.

Since JS works extremely well for user interaction, and is "good enough" for simple object-oriented programs, it will likely never go away in the foreseeable future in these realms. What I think things like SoundScript are for, is 1) for people who are used to strongly-typed programming languages to feel more comfortable on the web (which is actually pretty important), and 2) for larger OO or enterprise-style systems that really shouldn't have been written in JS in the first place (I'm looking at you Angular).

I wonder if it will end up like Go, though. An interesting language which never gets more than a niche market.


I'm not sure you read the article. "Soundscript" is just a fancy name for "notifying the Javascript interpreter that you promise never to do certain things, thus the interpreter can stop checking for them on every access and the optimizer is allowed to make stronger assumptions". It's not a new language so much as slightly different 100%-backwards-compatible dialect.

It is a way of deliberately putting down some of the often-silly dynamism Javascript provides.

Whether it takes off or not, I can't call it a "contender to replace Javascript" when it is Javascript.


100% Backwards compatible? So this will work in IE6? Or 7, or 8, or 9, or 10?

I think you meant 100% backwards compatible with ES6. As far as that goes, it sounds like a pretty good addition to the ES6 standard.


Google are trying to find a direction for web programming. The browsers have made plugins obsolete, but the plugins did bring something that the standard web technologies may have been missing.

What is hard is to change JavaScript from the outside. They may have found that impossible to do. The direction started with asm.js shows that JavaScript needs to change from the inside. JavaScript can be changed and remain largely compatible, but it will not be without the drawbacks. The path started with transpiling and polyfills may help to keep some backward compatibility, for example. :-)

Not all problems can be solved with just 1 technology. It will continue to look more like a mash-up. The challenge is to make it cross-platform. Google have to run ads on those things for it to help with the bottom line.


In my opinion Javascript is on its path to marginalization. Just glancing the recent tooling options for ClojureScript and TypeScript, I'm pretty sure now is the time to start putting your alt-js experience ahead of Javascript on the resume. The syntactical checks are great (typescript ides) or instant feedback (clojurescript figwheel) but for me the killer is the performance optimization over 'raw' Javascript due to compilation optimizations.

Better tooling, better performance, why would I want to go back?

The language of the future includes a Javascript compilation target.


Before you start talking about what to put on your resume, I'd look at what job descriptions are asking for. I haven't seen many asking for ClojureScript or TypeScript yet.

TypeScript (and to a lesser extent CoffeeScript) seem fine to me, but ClojureScript looks very different to JS. I'd always advise someone to learn JS first, then your compiled language of choice.


Which is why I said marginalization. Knowing Javascript is important, but I believe a savvy team will jump on one these languages to turbo charge their productivity.


Possibly, but you can argue the opposite. I've seen a bit of commercial TypeScript and it's been horrific and to me just matched the anything but Web/JS approach I was expecting.

I'd also worry that the TS community itself would risk marginalization, end up as a small niche ghetto, with most of the interesting stuff going on in ES7+.

Hard to be sure but I wouldn't write off JS just yet, people have been doing it for years as new/better (flash/silverlight/compile to JS) technologies have promised to make it seem soooo 90s.


The typing notation of TypeScript is on it's way to becoming part of Javascript. See http://www.2ality.com/2014/10/typed-javascript.html

Going forward does not mean abandoning Javascript, just abandoning ES5


I don't foresee type annotations being standardized any time soon. The resistance to that is going to be fierce.


Brendan Eich made the following comment here recently:

" Indeed we had structural types in ES4 toward the end, and almost bluffed MS into folding (I'm told by a reliable source). But ES4 was trying for too much too soon. At this point we really need to see ES6/7 and TypeScript + Flow "gene- culture co-evolution". As I said at the last TC39 meeting on the topic, big de-facto standard wins trump rushed de-jure standards any day. /be"

and also:

" Type system implementors and the JS stewards must communicate well for this to win. It's looking good so far, on Ecma TC39: JQuery, Facebook, Netflix, PayPal (Ebay originally, and again), Twitter all represented along with Apple, Google, Microsoft, and Mozilla. Also academic researchers from various universities, all of whom love type systems and theory :-)."

https://news.ycombinator.com/item?id=8906807




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

Search: