HNHacker News
TopNewBestAskShowJobs

brandonbloom

1,965 karma · joined May 16, 2013

brandon@deref.io

http://www.brandonbloom.name

https://github.com/brandonbloom

https://twitter.com/BrandonBloom

http://www.linkedin.com/in/brandondbloom/

Old account: https://news.ycombinator.com/threads?id=snprbob86

submissionscomments
brandonbloom··on Comcast is injecting 400+ lines of JavaScript into web pages
As an (unwilling) Comcast user, I purchased my own modem because your rental rates are preposterous. However, I wish I didn't have to think about this at all. If you force me to upgrade a modem I've purchased, I'll be very annoyed by the unanticipated cost.

I get that's problematic for your modernization efforts, but in that case: eliminate modem rental fees. Bake the fees in to the standard cost of the service and don't let customers use their own equipment. I understand that non-cable competitors don't have this cost to shuffle around, and that this will mean you are forced to either A) raise prices publicly or B) have lower margins. That's your problem because of your technology legacy; don't pass the misery on to the customer.

While you're at it, offer two hardware choices: one with, and one without routing/wireless. I refuse to run a wifi network in my household for your other customers and expect complete control over my LAN configuration.

On the topic of injection: I get that you don't think it's immoral, but hey, 1) most people who understand it think it is totally unacceptable. And 2) the window for this approach is rapidly closing for you as the web moves to SSL everywhere. Give up on this approach now and save face.

brandonbloom··on Volkswagen Official Gets 7-Year Term in Diesel-Emissions Cheating
That's not how the law works; intent matters. It's not enough for the tests to pass or for some proof to hold. These things are artifacts of some underlying human objectives.
brandonbloom··on Comcast Hints at Plan for Paid Fast Lanes
"fast lanes" is just a euphemism for throttling. It's still a protection racket.
brandonbloom··on Dueling Rhetoric of Clojure and Haskell
If f were deleted, it would still be in memory unless you explicitly call ns-remove to clean it up. In practice, this is rarely an issue, but I do wish the experience was a little cleaner there.

The same things do apply to integers, but if you use them at the top level (outside a function) then they will be dereferenced immediately (there is no delayed function body to wait for) and so you will get the initial value only once. If you want to enforce a delay, you can use (var x) or the shorthand #’x and later derefence that with @

brandonbloom··on Dueling Rhetoric of Clojure and Haskell

    (require '[foo :refer [f]])
    ; edit f in foo.clj
    (require '[foo :reload])
    (f 1) ; should call NEW f.
Node doesn’t have a reload construct. If you hack it in by mucking with the module cache, you still won’t get the new f in your module’s local copy of it.
brandonbloom··on Dueling Rhetoric of Clojure and Haskell
Yes, but it’s fast!
brandonbloom··on Dueling Rhetoric of Clojure and Haskell
These are old tired arguments.

I prefer being forced to keep my program simple by making complexity intolerable over encapsulating it. Your preference may differ.

I find I have to refactor my dynamically typed programs less frequently than my statically typed ones. Your mileage may vary.

No amount of type safety will prove my game is fun, or that my user can understand the UI. I want fast iteration times, since I can’t wait on the compiler to test a new enemy behavior or GUI layout.

brandonbloom··on Dueling Rhetoric of Clojure and Haskell
Clojure is following in the tradition of lisps that do this right. The important bit is the extra indirection on top-level names. I wrote more about it here a long time ago: https://www.brandonbloom.name/blog/2012/12/21/the-nodejs-rep...

Also interesting is the other end of the spectrum: Forth. Instead of mutation, offers snapshots and restores of the “dictionary”. See this video: https://youtu.be/mvrE2ZGe-rs

brandonbloom··on Dueling Rhetoric of Clojure and Haskell
The Haskell REPL is pretty good for a static language, but the experience is dramatically different to how a Clojure programmer would use want to use it. To be fair, Node and Python also have totally not usable REPLs for this style.
brandonbloom··on Dueling Rhetoric of Clojure and Haskell
Clojure has type hints, I can add ^int to a symbol and get some “static typing”. That is: shitty static typing. However, this is about the same as the average Any type in a static language. In order to implement a good Dynamic type, you’d need to implement reflection, caching dynamic dispatch, etc. Haskell’s Typeable is an OK implementation, but not nearly as good as say the JVM’s or JavaScript, despite their many flaws.
brandonbloom··on Dueling Rhetoric of Clojure and Haskell
It's not surprising though, since usefully typing control effects with delimited continuations would require at least answer type polymorphism, but even more usefully something like session types.
brandonbloom··on Dueling Rhetoric of Clojure and Haskell
And get works on more types, like vectors. It also returns nil instead of throwing:

    cljs.user=> (get [:x :y :z] 1)
    :y
    cljs.user=> (get 5 :x)
    nil
brandonbloom··on Dueling Rhetoric of Clojure and Haskell
The Edn type given here is closed. That's a correct definition of Edn, which is a closed sum. Edn accomplishes extensibility via the Tag type. However, not all Clojure data is Edn. In order to implement the clmap and clget functions with their full generality, they need to support an open set of types. For example, both `#inst "..."` and `(eval '(Date. ...))` are separate types: TaggedLiteral and java.util.Date respectively.

You need either Dynamic or existentials because Clojure enables you to pass data structures between two functions expecting collection elements of differing capabilities without either A) whole program / inter-module analysis or B) an O(N) type translation.

brandonbloom··on Dueling Rhetoric of Clojure and Haskell
Except I really don't have that choice because the language and library design matters. If I chose to use ByteString for everything, I'd first have to implement Tcl in order to get anything done.

But, yes, you're right, most dynamic languages lack good tools for stating invariants and checking them early. I would like to see that change. However, I'd rather the solution account for runtime dynamism, extensibility, and partiality. We're _slowly_ getting there with more and more advanced type system features. It's time to take that knowledge and repackage it at the foundational level of typed languages.

brandonbloom··on Dueling Rhetoric of Clojure and Haskell
> A dynamically typed language is a statically typed language with precisely one type.

While technically true, saying this is about as useful as saying "You can do anything in any Turing-complete programming language."

brandonbloom··on Dueling Rhetoric of Clojure and Haskell
EDIT: deleting this post because it was needlessly counter-inflamatory.
brandonbloom··on Reason 3
I'm not saying that Reason doesn't deserve some of the credit, but surely some of this must be attributable to relative maturity of Messenger. Right?
brandonbloom··on GraphQL Fragments Are the Best Match for UI Components
This is a classic static/dynamic problem. Luckily, in the case of React, there may be a really good solution that covers most necessary use cases. First, an analogy:

Consider Ruby's method_missing and corresponding respond_to? methods [1]. method_missing may respond to a method dynamically, but respond_to? should return true if a method will be responded to. If you constrain yourself to normal methods, or carefully defined method_missing bodies, you can predict with 100% accuracy, but what if you do something like this?

    class Whoops
      def method_missing(method)
        if method == :fancy and Random.rand > 0.5
          "it's your lucky day!"
        else
          super(method)
        end
      end

      def respond_to?(method, include_private = false)
        if method == :fancy
          true # Well, actually, "maybe?".
        else
          super
        end
      end
    end
Temporarily ignoring the problem created by randomness: Implementing respond_to? methods is annoyingly redundant. In order to fully-automate the process of implementing respond_to?, you'd need to analyze a method_missing implementation. In the common case, this is static type inference. In the limit case, this is arbitrary abstract interpretation.

However, effectful code, such as this random example, is provably impossible to analyze correctly! You need to have some conservative approximation. Returning true from respond_to? for the :fancy method means that respond_to? should actually be called might_respond_to?.

OK, now back to React and GraphQL: Luckily, over-fetching data is usually not that big a deal (as long as you're within bandwidth constraints). It's often desirable to! Since you might need that data soon when the view changes. Knowning this, it's safe to assume a method for assembling a GraphQL query from fragments can be conservative. That's essentially what a totally static, manual composition of query fragments does. If that's a tight enough approximation for you (like some other commenters have implied) then great! But what if it's not?

Luckily, virtual DOMs are dramatically simpler and less powerful than arbitrary method_missing implementations. If your render method is already pure, you can call it an extra time to use it as its own abstract interpretation. It will produce a virtual DOM, which you can then analyze yourself. For example:

    let capture = (f) => {
      let oldCreateElement = React.createElement;
      try {
        React.createElement = (component, props, ...children) => {
          return {component, ...props, children};
        };
        return f();
      } finally {
        React.createElement = oldCreateElement;
      }
    };

    class DynamicQuery extends React.Component {
      graphql() {
        let dom = capture(() => this.render());
        return gatherQuery(dom);
      }
      render() {
        ...
      }
    }
Here, the gatherQuery method will walk the objects returned by our monkey-patched createElement method, composing query fragments along the way. By following the component references, you can find static methods (easy!), or even instantiate components to do recursive renders yourself (this gets hairy, since React doesn't really make any of this public API). You can do something pretty simple with this: Just hard code a few component types to make some limited dynamic decisions, fallback to static over-query beyond that. Or you can get really fancy and do recursive rendering and analysis, if you really want to.

[1] http://blog.enriquez.me/2010/2/21/dont-forget-about-respond-...

brandonbloom··on Relicensing React, Jest, Flow, and Immutable.js
That's what all the Facebook & React folks were arguing. Facebook doesn't want to do unconditional patent grants. Plus, they decided that the weak patent retaliation clause in Apache 2 license wasn't strong enough. This new license is strictly riskier for people than the old license, but it's more familiar, so people won't freak out.
brandonbloom··on Honey SQL – SQL as Clojure data structures
It's explained a bit here: https://www.postgresql.org/docs/9.6/static/plpgsql-implement...

In short, if your parameters are highly dynamic, plan caching will be disabled. This is still better than dynamic queries, because the results of parsing will be cached.

If you almost always use one particular set of filters, but just enough dynamic values that the cache plan is not useful, AND your bottleneck is planning, then you can prepare a fast path.

To do that, use a PREPARE statement or your language's SQL bindings for that. Essentially, you tell it "these are usually going to be the defaults, let's call that staticFoo". Then staticFoo and dynamicFoo are subject to independent plan caching. The fast path, staticFoo, will get a good cached query plan, and dynamicFoo will be subject to dynamic re-planning.

The braindead (and therefore "better", by some measure) approach is to just just create an additional query for your fast path. The tradeoff of course is violating the DRY principle.

brandonbloom··on Honey SQL – SQL as Clojure data structures
> when moving beyond the complexity of "select this user record") I ran into issues with yesql's lack of power.

This is definitely true, however, I'd like to point out that static queries (either Yesql-style, or even stored procedures) can go a lot further than you'd initially expect. Unless you have a pressing application need for dynamic queries, you may consider static queries as they are more predictable with respect to performance and debugging.

Here's the main trick: Most of the time, you want dynamic filters, not dynamic selections. When this happens, you can just include the total set of filters and provide default parameters that are always true.

For example:

    SELECT id, name, created_at, score
    FROM players
    WHERE created_at BETWEEN '-infinity'::timestamp AND 'infinity'::timestamp
    ORDER BY score * -1
This will return all players, regardless of when they joined and will sort by score descending. You can parameterize the time range as well as the sort direction between +1 and -1. You can use EXPLAIN to prove to yourself that Postgresql is smart enough to make proper use of indexes.
brandonbloom··on Clojure Concurrency (2008) [video]
Yeah, this is exactly it. Atoms were added as a cheaper alternative to Refs for when you didn't need transactions, just compare-and-swap. Turns out that atoms work well enough for even huge single-JVM systems, and are much simpler.
brandonbloom··on Why did we switch from Braintree to Stripe?
By all means, lock your machine when you walk away from it. Just don't pretend that would provide any protection against strangers walking through your office for even a few seconds.

Here's a USB dongle that's actually a key-logger with 2GB of storage and WiFi offloading for about $150 bucks available on Amazon:

https://www.amazon.com/Wi-Fi-Premium-Hardware-Keylogger-comp...

Options as cheap as $50 are available if you're willing to accept less storage and risk a second physical contact to recover your device (no WiFi).

brandonbloom··on Toward Go 2
> you can think about generics as a better kind of interfaces

No, you can't, because generics don't provide the key feature of interfaces: run-time dynamism.

You could make a case that static polymorphism and dynamic polymorphism could be accomplished by a single mechanism, as traits do in Rust, but you can't say generics are "better" than interfaces, since they solve a different set of problems, with different implementation strategies, despite related theoretical foundations.

> The problem of not having generics is that the community can't build its own implementation of useful data structures

This is not true either, although it is true that it is harder to do this sort of thing in Go, the result may in fact not be as fast as you can achieve with monomorphisation, and you may have to write more code by hand.

The "trick" is the Go model is to provide an interface (which yes, potentially unnecessarily uses a dynamic mechanism) to implement indirect operations. For example the Swap function of https://golang.org/pkg/sort/#Interface enables https://golang.org/pkg/container/heap/#Interface - which is a reusable data structure. Compiler-level static-generics for slices and arrays, combined with indirecting through array indexes, enables you to avoid boxing when instantiating the heap interface. The data structure can be instantiated with an interface once, so there is no dynamic lookup for heap operations, but there is still a double indirect call.

Yes, this is less convenient than proper generics, but this doesn't mean you can't do these things.

Furthermore, I've never actually needed to do this outside of sorting (which is, absolutely, is annoying) in large projects. Almost every time I need a specialized data structure, I usually need it for performance reasons, and so hand-specializing for my needs tends to be worthwhile anyway.

> Generics are rarely useful in your own code, but they allow people to create good abstractions in their libraries.

I'd like to see something like templates or generics for Go, but I definitely don't want Java-style "fake" generics without code specialization. Furthermore, I found that the vast majority of generics-heavy libraries back in my C# days were more trouble than they are worth, despite meaningful opportunities for better performance with value types. Copy/paste almost always resulted in a better outcome.

brandonbloom··on Linus: “I no longer feel like I can trust ‘init’ to do the sane thing”
This is only a tiny piece of the experience though. What about journalctl? systemctl? etc? That stuff is alien vs tail -f or ./just/run/some.sh which works with everything _except_ systemd.
brandonbloom··on Go and Simplicity Debt Redux
One example: You get a class cast exception at run-time, rather than corrupted memory.
brandonbloom··on Go and Simplicity Debt Redux
I have a whole bunch of references related to dynamic languages and type inference, but can't find the specific one I'm thinking of on my hard drive. Sorry.
brandonbloom··on Go and Simplicity Debt Redux
Yup! Type assertions work this way too. It's a much nicer design than traditional casts.
brandonbloom··on Go and Simplicity Debt Redux
How is it mystifying? You said "besides the one difference you cited, they are not different". I explicitly listed that difference as being the thing that I feel justifies the feature's existence!

> the introduction of generics has added a tremendous amount of safety

It's added marginal _static_ safety. It added no dynamic safety. Static safety is welcome, but frequently not worth the lengths people go to achieve it. Hence my comment about instantiating generic types with Object in the event that the cost outweighs the safety.

> to what used to be typecast littered Java code pre 1.5

Occurrence typing would dramatically minimize the syntactic overhead to casting. There have been research variations of Java that accomplished this: instanceof checks insert implicit casts on branches where the instanceof check is true. Similar extensions have been explored for collections to further omit the instanceof checks without requiring explicit type parameterization. For example, if a collection is only written to privately, you can infer casts from what types are inserted in to the collection. This can achieve the same safety without massive complexity increases to sub-typing, static dispatch, reflection, type signatures, etc.

brandonbloom··on Go and Simplicity Debt Redux
1 & 2) Read my comment again. My remark about functors (in the ML sense) addresses this point. I'm quite well versed in how type abstraction works. Your comment about being aware of underlying type details is completely non-sensical with respect to nesting. The problem you're worried about applies to non-disjoint union types, ie dynamic typing, not Go's typical use of type-safe pointers. Nil is type-safe in Go, there's no risk of confusing:

    a nil *Foo with a nil *Bar
3) You say "meh", but abstraction for the sake of abstraction is exactly what drives working programmers away from so called "better" languages.

4) Meh.

Anyway, I'm done with this thread.

← PreviousPage 3 of 16Next →