1,965 karma · joined May 16, 2013
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
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.
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.
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.
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.
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.
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...
Source: I worked on the original "Hour of Code".
Source Code: https://github.com/code-dot-org/code-dot-org
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.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:
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.
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.
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.
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.
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.
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.