HN user

carbonica

544 karma

Carbonica - http://carboni.ca/ - OSS projects, usually involving Ruby.

Posts14
Comments98
View on HN

It is truly disheartening to see you'd been downvoted when I came into this thread.

The truth is, the OP's domain was probably considered to be in a bad "neighborhood" because his mail server had been compromised for spamming purposes at one point or another. It's dreadfully easy to either misconfigure a mail server or to end up with your mail server compromised.

Regardless, it's easy to hate on Google, especially in a primarily entrepreneurial forum where those posting are often trying to solve tough problems with far fewer resources. But Google is solving tough problems, even when you feel you've been wronged by an algorithm. Gmail has had an unbelievably successful spam filter for years, forcing the competition to rise to the occasion and match it, to the point where people forget how serious a problem spam is. It's not trivial, and it doesn't mean there's a democratic crisis when your e-mails end up in a spam bin. Especially when it's quite likely because your mail server was compromised.

And:

Unknown said...

Agreed 100% with Rachel's comment above. Also, what makes you think Groupon would even accept you? Top tier deal sites (LivingSocial, Groupon) expect established or reputable businesses. You're too new and unproven to make the cut. You also sound naive in general.

Unknown User Stats

On Blogger Since August 2011

Profile Views (approximate) 4

It's close to the same thing, but having more named registers does provide more optimization opportunities. It shouldn't be completely ignored as a good thing.

I mentioned function calls because that's a boundary when an optimizer - either in-processor or in-compiler - is often forced to use memory and not registers. The big-N-register "register window" of RISC processors kicks this problem's ass. But with aggressive inlining already common, these boundaries don't come up as often as you'd think. So the x86-64 calling convention does a pretty damn good job too.

Edit: by the way, I guess I haven't really commenting on TinyVM one way or the other when I responded to you. You're absolutely right and the compatibility is not necessary at all. I think the register count limitation especially is just silly. I pointed out in a different thread that they could double their register count and they'd still fit in a single cache line... they even already have a naming convention (r08-r15) they can hijack.

> But in hardware at least, the x86 registers have been found quite limiting on software and performance.

While the doubled register count makes a big difference - especially with the new x86-64 calling convention - register renaming and hidden registers do a lot on x86 to mitigate the lack of general purpose registers. You don't need more than 8 registers to get the benefits of having more than 8.

The instruction set is vastly simpler than x86, but it does inherit some limitations for no reason. x86 had 8 named registers for so long because the mod_rm byte in x86 only has 3 bits for each operand. Adding the REX prefix byte in x86=64 complicates things a bit, but isn't too tough.

This VM doesn't have a particularly compact in-memory encoding - it uses an entire `int` for each argument! They could double the number of registers and the entire register set would fit in a cache line (except for EIP) at 4 bytes apiece. I guess it is nice that all 9 fit in one line now, but I doubt that was considered.

> "Closure" (at least to me) doesn't imply this bevaior in a language with mutable variables.

I don't think I can name a single reasonably-known language with mutable variables and closures that doesn't exhibit this behavior.

I'm sorry? Python is the same way:

    >>> i = 10
    >>> def x():
    ...   print i
    ... 
    >>> x()
    10
    >>> i = 11
    >>> x()
    11
The value is not closed over in any language I can think of off the top of my head. It's a reference to a variable in lexical scope.

What python does slightly differently is writing to variables in outer scopes. But it still closes over references.

But what I pulled out from the Wired article amounted to "it's silly that if the file sizes are slightly different you need to upload your whole song." If file sizes are different, the MD5 will be wrong in nearly all cases. They're talking about something different in that paragraph than you are.

> However, the ruling makes clear that if MP3tunes scanned a customer’s music collection and found “Stairway to Heaven” ripped from a CD with a slightly different file size, the company could not simply substitute a master copy. Instead, that customer would have to upload the file.

> While the latter case still seems non-sensical, the ruling still must come as a relief to Google, Amazon and Dropbox.

Come on, how is that non-sensical? It'd be dead simple to set the ID3 tags of any 4MB mp3 file to match the tags accepted for a given song. From what I read, and the legalspeak got pretty heavy so I may have missed it, there wasn't any discussion of audio fingerprinting or more advanced ways to determine two files are the same song.

Want an entire artist's discography? Use a 15KB app which spits out 100 junk MP3 files with the right ID3 tags and submit them to MP3Tunes. If we do what this author considers "sensical," you should get the real music back.

You've repeated the burrito example in nearly every subthread here, which is poor form to begin with, and it is a horrible example. You are comparing ("chicken burrito" -> "beef burrito") to ("something with words I've never seen in the past several billion searches" -> "something for which I have many results that get many clicks")

Please stop spamming your analogy to every subthread and please consider that it may in fact have some flaws.

Russ is pretty much describing Go as it existed in its developer's minds in 2007. He even explains nearly all of Go's (future) interesting features - with nearly identical syntax - before he even gets to an example.

> Is $10K/month necessary for living expenses?

While at first glance, the "needing 10k/month" seems surprisingly high, it's likely he means "need 10k/month to maintain my present lifestyle." I doubt he actually means "I need 10k/month to survive," just that having a family means less compromising on lifestyle.

If kids are in school, maybe even private school, those expenses are likely something he doesn't intend to compromise on, which is completely fair. If he has a nice home in a nice neighborhood, he still has to pay property taxes. Healthcare costs more for a full family than 1 person. And so on. Sure, there's a path toward having lower living expenses, like moving where property taxes are lower and taking kids out of private school. It's just going to be a lot harder to take.

I Like PHP 15 years ago

Considering Facebook uses Haskell to automate changes to their PHP codebase, I'd imagine PHP isn't doing them any favors.

You've done an excellent job of summarizing why Google does what it does: extremely few languages, and all code must pass readability review (or be written by an author with readability for the language(s) used) before it can be committed.

Though reading code isn't just it, language interop everyone knows how to use is important too. While all the fancy JVM languages have Java interop, it's still a huge pain for every old Java developer who wants to call your Clojure library. They'll end up learning how to do it separately for every language, every time, multiplied by the number of developers who aren't fluent in these languages (most).

What's a Closure? 15 years ago

Monads, in the context of programming, are used as abstractions for computation. That's all - some people mention state, because some useful computational strategies maintain state - but not all do. In Haskell:

The List monad represents nondeterministic computation. Each possible result from the previous step in the computation is fed into the next step of the computation.

The Maybe monad represents computation that can fail. If one step of the computation fails, then the rest fail. The previous step of the computation is only fed into the next step of the computation if it succeeds.

The IO monad represents globally stateful computation. The entire state of the program is fed into each step of the computation. Kind of. That's sort of the idea, anyway - it's a model.

The Reader monad represents computation in the scope of a single shared bit of data.

Only the latter two mention state. The idea of monads in programming is just abstracting computational strategies, and sometimes those strategies require or provide state.

Monads are neat in Haskell/ML because you can write code that works on arbitrary monads. Think of it this way: you know how you can write code that operates on a `List` in Java, and it will work on `ArrayList` and `LinkedList`? Well in Haskell, you can write code that operates on/with a given function regardless of computational strategy used.

My undergraduate thesis tackled static analysis in Ruby. One of the problems I attacked was "does a given method yield to its block?" I broke methods into a few classes, two of which are "Block-Required" and "Block-Optional."

I had to write software which proved a method optionally yielded, which isn't just undecidable, but unrecognizable. For non-CS folks, that means it's actually harder than the halting problem. Yet my software works on nearly all Ruby code I throw at it - it only really has difficulty on complex delegation patterns. Unsurprisingly, the code people write to create Block-Optional methods is highly structured.

If that's intuitive for you already (great!) consider one step further: Rice's Theorem. Rice's Theorem in layman's term is "all nontrivial properties of a program are undecidable in the general case". "Undecidable" is the difficulty class of the halting problem (there are actually harder problems!).

A couple accessible examples:

1. Constant Propagation - we know that we'll never detect all compile-time constants because of Rice.

2. Exception frequency - I can write a function that has "raise new FooException()" in the code, but the function never raises, and you won't be able to prove it never raises.

It's a matter of purity inference. They already trivially know the value isn't used based on the AST, so they need to show that the function being called is pure to elide it. Naturally, in the general case this is undecidable, but there are fast algorithms for very weak purity inference that might do the trick, at least in this case.

There may be other Python-specific concerns I'm missing - my work in this area is in Ruby static analysis - but one other thing is that allocating memory is a side-effect itself. The main side-effect visible to Python is that it might raise an exception for being out of memory - this would have to be special-cased as an acceptably ignored side-effect by any purity analyzer.

What Computer Science program would leave you buried in CSS for 2.5 years? If he meant he spent 2.5 years working with CSS on the side, why didn't he ever think "maybe I should learn legacy support"?

> Can you comment on the differences/advantages of ripper, compared with RubyParser?

Absolutely! To provide context, what got me working on Ripper was my undergraduate thesis: Laser, a Ruby static analyzer, written in Ruby. It targets Ruby 1.9 only. To find out more, check the github: https://github.com/michaeledgar/laser/

My comparisons will be to the latest version of Ripper (the one in 1.9.3).

1. RubyParser only parses Ruby 1.8.x code - this was a dealbreaker for me, but won't be for some.

2. Ripper is C, primarily - it's actually just a separate set of action routines brutally hacked into the normal Ruby grammar file. This also means the code for it is pretty inscrutable. But, it's very fast and is integrated into the actual parser. It should grow with the language by design. However, bugs have shown this isn't as reliable as it could be.

3. Ripper does not provide comment nodes for any nodes - RubyParser provides them for def, singleton def, class, and module nodes. I had to reconstruct this manually using the lexer stream for my purposes, though I get the added benefit that I can attach comments to many nodes (but not all). See the code here: https://github.com/michaeledgar/laser/blob/master/lib/laser/...

4. Ripper is a true AST, in fact it is closer to a concrete syntax tree sometimes, which makes working with it a bit harder. RubyParser has more friendly output, but goes too far in the other direction - it infers semantic information for you, sometimes (imo) quite egregiously.

4a. Constant literals - from numbers to regexes - are returned as objects, with no AST information. The ruby parser does this internally to save some execution time, but I don't believe that's appropriate for a general-purpose parser.

4b. It adds :scope nodes inside class, module, and def nodes, to indicate closed scopes. They don't need to be there - they aren't part of the syntax, they're a property of how you interpret the syntax as a Ruby program.

4c. The worst offender is if you parse "begin; rescue Foo => x; end", it actually inserts an assignment "x = $!" into the rescue block. This is well beyond an AST.

5. Sometimes RP's output is a bit inconsistent in the number of child nodes for a given node. For example, the :rescue node in "begin; foo; rescue; end" has two child nodes. In "begin; rescue; end", it has 1 child node: just the :resbody node. A proper tree would have at least a nil node for the begin body, but RP elides it. This means if you see a :rescue node, you have to always check the first child node's type before you can do anything. That's why consistency is important.

6. RubyParser doesn't pick up errors as often, and as far as I can tell, doesn't report them at all. For example, parsing "def foo(x, x); end" in Ripper will give you a "param_error" node. I'm not too happy with the exact reporting style, and I've blogged about it (http://carboni.ca/blog/p/ripper-plus-How-Ripper-Must-Change), but it at least notes the error so you don't have to, as a parser should in this case (as it's a parse-level error). RP doesn't note it. If you do an invalid global alias: "class A; alias $foo $1; end", RP just ignores the node, whereas Ripper will report it with an :alias_error node.

All told, RubyParser is a nice library and it's important to have an all-Ruby option out there. But for me, Ripper is far more appropriate from a theoretical and practical standpoint for a large-scale project.

Edit: grr, my formatting wasn't saved twice now.