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 The Wolfram Language: Fast Introduction for Programmers
Lisp's evaluation strategy is based on a very particular category of rewriting systems known as the lambda calculus. Mathematica's is an abstract rewrite system with a broad set of evaluation strategies that are not easily represented in the lambda calculus (without first implementing a term rewriting system).
brandonbloom··on The Wolfram Language: Fast Introduction for Programmers
You speak a language with three to six thousand "common" words and over one million total words. English speakers utilize no such hierarchical naming scheme, only practice.

Typical Mathematica notebooks (I won't say "programs") use a fair bit less than one hundred of the common functions, plus a handful of domain-specific functions from that the greater set of five thousand. At any time, you can enter ?FunctionName to access documentation far more comprehensive than most languages offer.

The UpperCaseForStandardLibrary and lowerCaseForYourCode convention actually makes a lot of sense in the context of Mathematica's primary use cases vs traditional software engineering projects. When I'm just trying to solve some problem in Mathematica, using my computer as a bicycle for the mind, I appreciate not having to putz with import statements or avoid name collisions.

brandonbloom··on The Wolfram Language: Fast Introduction for Programmers
Beyond surface similarities like an emphasis on data transformation over side-effects, I wouldn't call Mathematica a functional language. It's much better classified as a term rewriting language: https://en.wikipedia.org/wiki/Category:Term-rewriting_progra...
brandonbloom··on Design Patterns in Clojure
There's a lot of stuff packed in to your comment here.

The code/data equivalence isn't necessary to make this data-first approach work. Consider JavaScript, where it's quite popular to power complex processes with simple JSON (maybe + extensions, like functions). The approach also works well in typed functional languages, albeit with greater declaration burden. Moreover, it would even work well in languages like Java, if you're willing to buck the XML trend.

That said, data being a subset of code (which is also implied by equivalence) is what makes this technique much more popular in Lisps (and JavaScript). Being able to easily move forms between evaluation, quoting, interpretation, etc is critical to evolving the right approach.

Regarding whole program transforms: They don't compose, at least not without a substantially more sophisticated macro system (like Racket's) or evaluator (see work on "extensible interpreters"). Check out Racket's rich literature on macros if you want to see the state of the art here.

Lastly, changing behavior based on context can be accomplished by many techniques including interpretation, dynamically scoped binding, etc. Read about Common Lisp's condition system, or Eff's "Algebraic Effects and Handlers" to see the state of the art of the dynamic substitution of effects. However, each new context needs to carefully reason about the interleaving of effects, since time is linear.

If you want more flexibility with time, you have to turn it in to space. That's what happens when you take a step-by-step procedure and turn it in to data you can interpret. Consider code that prints a lot of stuff. Instead, you can return a lazy sequence of strings and then at the very end do something like `(apply str parts)` to concatenate it all together. Now that each sub-printer doesn't have side effects, you can re-order and refactor the calls without worrying about messing up the printed output. You've decoupled the evaluation timeline from the linear order of the output.

brandonbloom··on Design Patterns in Clojure
One key difference between this and the IO monad or a free monad: There's no continuation.

That is, you can't communicate between the code that produces the command sequence and the code that interprets it. This is either a shortcoming, or a dramatic simplification, depending on your needs. I find that it's often the latter, since it enforces a one-way information flow.

brandonbloom··on 1% the Code (2001)
That's one definition of "typed", but not a very useful one. By that definition _everything_ is typed. That's an interesting metaphysics discussion worth having, but not relevant to explaining Forth to a C programmer.

Vanilla Forth values do not have runtime type information (such as class pointers or discriminator tags) nor does a traditional forth compiler have compile time type information (such as an abstracted understanding of the state of the stack at any given point of execution).

Meanwhile, Factor, a modern stack language has both: SmallTalk-style object-oriented class pointers on every value, and a compile-time stack-effect checker. Factor values have a Lisp-like dynamic typing discipline. That is to say "uni-typed" as "dynamic", or a discriminated union.

brandonbloom··on 1% the Code (2001)
Forth is completely untyped. Traditionally, all values are machine words.
brandonbloom··on 1% the Code (2001)
My experience mirrors yours. I'd like to also stress that it's not just 30:1 ratios that are worthwhile.

Most recently, I just rewrote a decently written ~8 kloc C program in to a ~4 kloc Go program that does 5 times as much, 10 times faster. If I had more time, I could probably shrink the new Go program to about half its size. This new program includes the old, as well as a new simplified interface, both HTTP.

Moving clients to the new interface has had cascading C#/Scala code reduction of ~1 kloc each. This is the most important point! Simplicity breads more simplicity.

brandonbloom··on Design Patterns in Clojure
Thinking in data & functions just takes some practice. Traditionally imperative code can often be reimagined in terms of reducing over a command sequence, or interpreting some more sophisticated representation. The benefits for implementation, testing, debugging, refactoring, etc can be massive.

Consider your chain of examples for "Upload something to S3, if X then call some web service, otherwise log and call something else, etc etc etc." You could represent this as a sequence of steps which could be interpreted by a central side-effecting loop. Let's call it an "action plan", in this case for your "upload" operation:

    (defn upload-plan [req]
      [{:action :upload
        :file (get-in req [:params :doc])
        :max-size 2000
        :name :doc}
       {:action :notify
        :message (fn [state] (get-in state [:data :doc]))
        :target [:somebody]}])
Now you can process this plan centrally:

    (defmulti take-action (fn [state {:keys [action]}] action))

    (defmethod take-action :upload ...)
    (defmethod take-action :notify ...)

    (defn execute [plan]
      (let [{:keys [status]} (reduce take-action {:status :ok, :data {}} plan)]
        (when (not= status :ok)
          (log "omg!"))))
While this is initially a tad more verbose, there's great reasons to do this sort of thing.

For example, now have a central place to mock all side effects! You could substitute a different execute method during testing, or automatically log the "action" object that failed.

More importantly, you can develop each of your "plan" functions independently. You can reuse them, re-order them, etc.

You can also do hypotheticals, for example: "Which plan would cause less network round trips?" can be answered before committing to a plan.

You can _record_ plans. Instrument your execute method to record and save plans to a file, then you can just check them right in as test cases.

You can optimize plans. If five different functions all notify the same target of the same thing, your central execution method can filter subsequent calls. Or you could separate your "optimization" from your "interpretation" and you can ask "After accounting for idempotence, which operations will be performed?"

The list goes on and on...

brandonbloom··on Blockly is a library for building visual programming editors
Yup, code.org uses Blockly.

Source: I worked on the original "Hour of Code".

Source Code: https://github.com/code-dot-org/code-dot-org

brandonbloom··on Dropbox closing Carousel and Mailbox
While I too dislike when stuff I use is shutdown, there's another possible way to look at this. Maybe the product lived a much longer life than it would have otherwise, had the original creators abandoned it after failing to make sufficient money on it directly.
brandonbloom··on Six years of Go
I tend to use structs like mini "dynamically" scoped namespaces for breaking up hairy tasks. Pattern looks like:

    struct fooer {
      x int
      blah string
      // ... more state
      err error
    }

    func Foo(x int) (string, error) {
      f := &fooer{x: x};
      defer f.cleanup()
      f.stepOne()
      f.stepTwo()
      f.stepThree()
      return f.blah, f.err
    }

    func (f *fooer) stepOne() {
      // ... do stuff and maybe set f.err
    }

    func (f *fooer) stepTwo() {
      if f.err != nil {
        return
      }
      // ... do stuff and maybe set f.err
    }

    func (f *fooer) stepThree() {
      if f.err != nil {
        return
      }
      // ... do stuff and maybe set f.err; set f.blah
    }

    func (f *fooer) cleanup() {
      // ...
    }
While this is initially a tad more verbose, it has some nice benefits. For one thing, you can easily fmt.Printf("%#v", f) to dump all the relevant state. However, it also tends to be more resilient to refactoring:

- No method signatures to patch up.

- Easier to non-local jump via return if you have some unusual control flow, etc.

- You can easily find all references to `f.err` to verify your error handling.

- The method names and step list take on a self-documenting quality.

Another key insight I had when learning to tolerate Go: It's ok to do throw away work. You don't need to abort instantly on failure, you can just make sure code paths handle zero values or other invalid states more robustly. For example, I sometimes add a function like:

    func (f *fooer) fail(err error) {
        if f.err == nil {
            f.err = err
        }
    }
And then just add sprinkle some f.fail() calls, make code handle nils, invalid states, etc, and make cleanup code idempotent. Seems to work out much more nicely than if err, return.
brandonbloom··on Asm.js Chess Battle
Or, potentially, does an equally good job optimizing the code with or without the asm.js hint.
brandonbloom··on Reader macros in Common Lisp
Ease of implementation is not the reason for either no reader macros or a single pass compiler.

Rich Hickey discusses reader macros here: http://clojure-log.n01se.net/date/2008-11-06.html

Quotes:

"Clojure doesn't allow user-defined reader macros because they can't be combined - there's no namespace support, unlike for regular macros"

"CL has user reader macros and the consensus is to avoid them"

"you are unlikely to convince me they are a good idea"

"I recognize fully this is less flexible than CL in this area, OTOH, I'd like to think Clojure will engender less desert-island programming than did CL -- speaking a common language fosters library development, shared idioms etc. When everyone creates their own language there's less of that"

"and even standard reader macros pose an understanding challenge for newcomers"

Compilation units are discussed here:

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

brandonbloom··on Transit: JSON Data Interchange Format
Another important feature is stream-oriented caching of values. Essentially some range of opcodes in the stream language are reserved for referring to a cache of recently seen values. It's a "dynamic" encoding feature that's super beneficial for common payloads, like JSON API responses with lots of similar keys. This is something a protobuf/thrift-like should steal, as it can often be more efficient than vanilla field-number + value encoding.
brandonbloom··on Show HN: Cancel your Comcast in 5 minutes
This is usually my strategy, but it failed hard with TWC. No matter what I did, they would redirect me to the automated "relocation" service and ask for my new zip code. They did this even when I told them I wasn't moving, they do it just to get rid of you. I even tried saying "I just no longer want internet, I feel like it is bad for my health." and sure enough "click Please enter the zip code...."
brandonbloom··on A regular expression matcher in Scheme using derivatives
The follow up work on parsing with derivatives is even more fantastic: http://matt.might.net/articles/parsing-with-derivatives/
brandonbloom··on Ritzy – A collaborative web-based rich text editor
You can say the same thing about any text editor built with any technology. This problem is fundamental and the tools to solve it are foundational. It's also no different than a game that doesn't render objects behind the player or obscured by walls. You need to choose data structures that enable efficient visibility queries.

For a game, there's some sophisticated math involved. But for a text editor? You only need to perform basic arithmetic on the scroll position and window height to index in to a vector of lines.

In React, you could render exactly 50 <Line> components and choose which 50 by exactly the same means vim chooses which 50 ncurses lines to show.

brandonbloom··on Ritzy – A collaborative web-based rich text editor
The techniques for addressing this are essentially the same for any display technology: Avoid rendering stuff that is not on the screen. This works just fine in React.
brandonbloom··on Preparing Windows Server Technical Preview for Windows Containers
Again, it's not an exit code or return value, it's an _argument_ to the catch block.
brandonbloom··on On Monolithic Repositories
With SVN (or similary) you'd simply configure your CI to 1) checkout 1 or more files and subdirectories and 2) config your CI to run a particular build script.

The notion of zero-config CI is a recent invention, thanks to conventions of small repos with well-known project file names per language/platform ecosystem.

brandonbloom··on Show HN: My SSH server knows who you are
And now I've rotated my keys and fixed my ssh config file. Thanks :-)
brandonbloom··on Java 7 changed the structure of String
Can just bit-or 1.
brandonbloom··on Java 7 changed the structure of String
This reminds me: Generic Persistent RRB vectors should be part of every standard library. There's a great master's thesis out there waiting to be written about a similar data structure for variable length encodings of elements (such as UTF-8 strings).
brandonbloom··on Why Invest in Tools?
React as Relay/GraphQL aren't from a blank slate either. Hell, nothing really is. How much to invest in what level of tooling is, as it should be, a function of what already exists, what you're trying to build, and what resources you have at your disposal.
brandonbloom··on Why Invest in Tools?
I think that game studios have the right model: Hire full-time tools engineers and task them with making the tool-users more effective.

Stellar games simply can't exist without stellar tools, and since every game is a little different, everyone's tooling needs are a little different. So you have no choice but to build your own tools (to some extent). Unlike armchair architects designing wheel frameworks or factory factories, the guy building the level design tool has real users (in this example: level designers) he has to build for.

This same model can be applied to web or service development. You just need to involve users from the start, especially so if your users are themselves developers who are especially demanding and critical and whose tools are especially difficult to create.

brandonbloom··on Clojure 1.7 is now available
I used them in Fipp to replace Reducers:

https://github.com/brandonbloom/fipp/commit/b83dfb3b3ac7c90d...

My use cases are 1) to emulate yield / a pipeline of generators 2) to avoid intermediate object creation. My code is an indirect port of some Haskell code, so it's a tad awkward, but overall Transducers were a small perf win over reducers (which was a big perf win over lazy seqs) and also provide a good effectful-ish model for my pipeline.

brandonbloom··on HUSL – A human-friendly alternative to HSL
The interactive color picker is neat, but the irregularity of the space is hard to visualize in 2D. I'd love to see a 3D point cloud version of the color space.
brandonbloom··on Rental America: Why the poor pay $4,150 for a $1,500 sofa (2014)
Many people do learn from their first mistake like this, but 1) there are always more fools and 2) for many, it's unrecoverable

As for the condescension: let kids test out. We really ought to abolish this entire notion of a class moving between grades together at the same pace, it's arcane.

brandonbloom··on Rental America: Why the poor pay $4,150 for a $1,500 sofa (2014)
These companies are despicable, for sure, but they couldn't exist without a long line of poor suckers. Both parties are clearly at fault in different ways, but we can fix the problem without ignoring the poor or inventing significant new and complex laws to clamp down on bad actors: We invest in education.

When we have another sub-prime style meltdown we need to bail the economy out of, we're going to be wishing we invested in our schools and added basic life skills such as personal finance to the lesson plans. It's certainly cheaper to bailout schools than financial institutions.

Anything else will fix a symptom, not cure the disease.

← PreviousPage 8 of 16Next →