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 My Go Resolutions for 2017
> nor does it help downstream users of a library

If you add a new type to a sum type in a statically typed language, that's a breaking change for all downstream consumers. Again, you may view this as a benefit (clients get compiler errors about new cases to handle!), but I view it as a drawback (clients can't upgrade until they handle all new cases!).

> Go implicitly makes this choice for the programmer

And I believe that Go (and Clojure, which omits both types and pattern matching for reasons including those I'm discussing) makes the right decision for the programmer, as openness to future changes without breakage is better > 90% of the time.

> could you rephrase?

You said "one can pattern-match using the names of fields", I took that to mean matching on something like Foo{x: 5}. If you do that and also have a clause Foo{y: 10}, now you have ambiguity and the order you compare x==5 or y==10 first matters, since you might have an object Foo{x: 5, y: 10} which would match both clauses.

If you just meant that you could do Foo{x: x} with blanks, that's fine, we're in agreement then. Clojure offers {:keys [x]} in destructuring for this purpose, and it's great.

brandonbloom··on My Go Resolutions for 2017
> The casting pushes type errors to execution-time rather than compile-time

Which is exactly what pattern matching does, which is what I was responding about.

brandonbloom··on My Go Resolutions for 2017
> the compiler tells you all the places your programm needs to handle the case is a tremendous bonus

Except this is simply not true if you have any wildcard patterns in any of your matches. Besides, it's easy to grep for one or each of the names of the types in your closed sum. Which is what you're going to do anyway if you have any wild card patterns!

> interface is not data

I don't know what you intend to communicate by saying this.

brandonbloom··on My Go Resolutions for 2017
> the compiler doesn't help you find the old type-switches that need new branches

Presumably you're adding to a "closed" set of types, so it's quite easy to grep for the type names in that type set. I just don't find this to be that much of a problem in practice. Never mind the fact that wildcard clauses exist and so this problem comes up in ML/Haskell/etc as well, since the compiler will consider those patterns exhaustive.

> only allow switching on the variant

Go already has this. It's called type-switch:

https://golang.org/doc/effective_go.html#type_switch

> one can pattern-match using the names of fields

Of course they can, but traditional algebraic data types impose order on to fields, which is an undesirable feature IMHO. However, field dispatch loses the nice order-independence attribute.

brandonbloom··on My Go Resolutions for 2017
> you can't make any type error when unwrapping the enum

This is also true in Go with type switches:

    switch y := x.(type)
        case MyStruct:
And with type assertions:

    y, ok := x.(MyInterface)
In both cases, y is of the correct type. The ok is optional, if omitted and the assertion fails, you'll get a runtime panic.
brandonbloom··on My Go Resolutions for 2017
Indent 4 spaces for code.

Here's another way to do this in Go:

    func f(obj interface{}) {
            switch obj := obj.(type) {
            case Foo:
                    // Do stuff with obj.x
            case Bar:
                    // Do stuff with obj.y
            default:
                    return fmt.Errorf("obj is of unexpected type: %T", obj)
            }
    }
brandonbloom··on My Go Resolutions for 2017
It's redundant because Go already has type-switch and type-assertions. Your comments about ints vs arrays vs behavior misses an important fact: An object of any type can be promoted to interface{} (aka "dynamic") and then may be "pattern matched" on via `x.(type)`. Sure, it's pretty crummy pattern matching to only be able to dispatch on a single tag, but there are some fundamental problems with traditional algebraic data types and pattern matching:

1) Abstract data types encourages closed systems. You may view this as a positive: It enables exhaustiveness checks. But I view it as a way to make your program more fragile. Go's type asserts let you convert to interface types, so you can add new interface implementers later and not have to go fix-up old type-assertions.

2) First-to-match pattern matching complects order with dispatch. Each match clause has an implicit dependency on _all_ of the clauses before it, since you can match Foo{x, 1} and if that fails match Foo{x, y} where you know now that y != 1. This is sometimes useful, but as your patterns grow larger, it's simpler to just match Foo{x, y} and then branch on y == 1. A series of type-asserts with if statements has a little bit of order dependency on it: interface clauses and named wrapper types are first-to-match, but type-switch on struct clauses are completely order independent because there can only be one concrete representation underlying an interface{}.

3) Relying on positional fields causes two classes of problems: A) it's harder to grow your system later, since every pattern match needs to mention the new field you added and B) you can't _not_ mention a field by name (or at least give it a placeholder name) at every use as well. This is the same issue as the Foo{x, y} vs Foo{X: x, Y: y} notation. It's considered good practice in Go to use the later, since it's more future proof, ie Foo may grow a Z field and it will be initialized to zero.

brandonbloom··on Two's complement – You beauty
Just prefix a + or - just like you do for base 10.
brandonbloom··on Two's complement – You beauty
That's a reasonable tradeoff for Python, but it's worth noting that there is a way to deal with this.

Quoting from http://reference.wolfram.com/language/tutorial/IntegerAndNum...

    Bitwise operations are used in various combinatorial
    algorithms. They are also commonly used in manipulating
    bitfields in low‐level computer languages. In such
    languages, however, integers normally have a limited
    number of digits, typically a multiple of 8. Bitwise
    operations in the Wolfram Language in effect allow
    integers to have an unlimited number of digits. When an
    integer is negative, it is taken to be represented in
    two's complement form, with an infinite sequence of ones
    on the left. This allows BitNot[n] to be equivalent
    simply to (-1 - n).
brandonbloom··on Why Clojure? (2010)
> any example where functions are dyamically composed as a pipeline needs TCO to avoid leaking stack

Sure, but in the rare case that this is a problem, you can trivially workaround it:

    (reduce (fn [acc f] (f acc)) init [f1 f2 f3])
This approach may not match the Haskell ideal of FP, but it's actually got many significant advantages. Frequently, programming with data structures is _better_ than programming with function composition. For example, you may choose to do something like this:

    (reduce (fn [acc f] (println "executing" f) (f acc)) init [#'f1 #'f2 #'f3])
Where this is now a pipeline with logging of the stages. You can't do that if you prematurely select function composition as your primary way to structure computations.

The Haskell research community is in the process of undoing this brain damage on the monad front by embracing extensible effects, free(-er) monads, "reflection without remorse", effect interpreters, etc.

Once you need any sort of symbolic reasoning, you drop opaque composition and work with data. This is true in every language, FP or not. People tend to do exactly the right thing already: abandon function combinators and write interpreters over data structures.

brandonbloom··on Why Clojure? (2010)
You're being down voted because you keep repeating a claim without evidence despite both evidence to the contrary and acknowledgement of the utility of tail calls.
brandonbloom··on Why Clojure? (2010)
¯\_(ツ)_/¯

Extremist views are not particularly useful in engineering, despite their utility is science.

brandonbloom··on Why Clojure? (2010)
I think that Clojure's success is proof positive that explicit tail recursion and lazy sequences is sufficient to do meaningful FP in the absence of proper tail calls. However, even if you insist that it is somehow not "full", who cares? Clojure is about practicality, not appeasing some platonic ideals. And I say this as some one who would very much appreciate having proper tail calls.
brandonbloom··on Why Clojure? (2010)
The fact that monads are not popular in Clojure is a good thing, IMHO. Besides, test.check and clojure.spec get by just fine with macros for generator monads. It's not quite perfect, and everyone would like proper tail calls, but it's far from an impediment for the overwhelming majority of tasks tackled with Clojure.
brandonbloom··on Why Clojure? (2010)
> Without TCO, you can't fully leverage functional composition and immutable data structures

Look, I like tail calls as much as any language geek, but this statement is just plain silly. The idea that TCO is needed to "fully" leverage anything is silly. Never mind that necessitating so-called full leverage is pointless, as even supposedly partially leveraged anything can be useful. In the case of Clojure, function composition and immutable data structures are highly leveraged and, after learning the seq abstraction and lib functions, you won't even miss tail calls unless you try to write a direct-style interpreter for a language that supports recursion.

brandonbloom··on Introducing the React VR Pre-Release
I think it simply means that the runtime and distribution platform are the typical browser technologies stack, namely JavaScript, HTTP, et al.
brandonbloom··on Yarn – A new package manager for JavaScript
> An entirely new tool because you didn't like typing "npm shrinkwrap" once, just once per project?

I don't know what kind of projects you work on, but new dependencies get added with some regularity on any large node project, requiring a shrink-wrap every time.

If you've never experienced the forgetting-to-shrinkwrap problem and its interplay with team dynamics, then I envy you. But I also must give up trying to explain to you, since this drives to the heart of the problem and I must conclude that we simply do not have enough common experiences for you to ever understand. The developers of Yarn et al clearly have lived this nightmare.

> Do you mean in the past

Where the past was < 4 months ago. If it has been fixed since that, I don't care: too little, too late.

> This is an all too common attitude in the JavaScript community.

An equally big problem in the JavaScript community is that core pieces of infrastructure have been "claimed" by people who have absolutely no idea what they are doing, but are happy to community-coordinate their way to some kind of idealistic open source victory.

> weren't co-operative

Again, I don't care if they were willing to co-operate. I have zero confidence in them.

> I'd rather humanity not waste the man-hours duplicating effort.

Give up on this dream. People are going to re-invent everything from now until the end-of-time, and rightly so.

brandonbloom··on Yarn – A new package manager for JavaScript
> > ways that could not be fixed without braking changes

> Examples, please.

The first and foremost: Version pinning should be the only way it works. Obviously _requiring_ shrink-wrap would be a breaking change.

> > Shrinkwrap is still a usability nightmare

> Again, examples, please.

Having to explicitly call shrink-wrap is just silly. Devs on every time I've used shrink-wrap with frequently forget to do it and it causes all sorts of "works for me" problems later. My team has also experienced all kinds of brokenness related to private NPM repositories and shrink-wrap non-determinism.

> Did the Yarn developers make any attempt to contribute to NPM at all

I don't care about this at all. Just because you got there first doesn't mean you should have some kind of special claim to solving the problem.

brandonbloom··on Yarn – A new package manager for JavaScript
> NIH Syndrome

No. NPM is awful in ways that could not be fixed without braking changes. They have a need, so they satisfied it. A need which, by the way, every one of the dozen-or-so Node-using projects I've worked on have shared.

> Whilst there absolutely were issues with Shrinkwrap in the past, they've already been addressed by NPM.

This isn't even remotely true. Shrinkwrap is still a usability nightmare and doesn't begin to address the non-deterministic install behavior that crops up when you add new dependencies.

> why these couldn't be contributions to NPM seems like a serious case of NIH

There's no way that they could have got these changes in to NPM in anywhere near the time it will take for Yarn to become popular; if at all! Politics (or the avoidance thereof) are practically _the_ perfectly valid reason to justify NIH.

I'll wait a while for Yarn to bake, but personally, I hope NPM dies a swift and total death.

brandonbloom··on Racket – Lisp beyond Clojure
Straw man. When was the last time you had to check the documentation for the parameter evaluation strategy of a C function call?

I don't think it is a controversial statement than a badly designed macro can do more damage than a badly designed function. I believe that this is true, even normalizing for bad macro systems. This is not a statement against macros, so much as it is a statement to consider when making tradeoffs.

brandonbloom··on Racket – Lisp beyond Clojure
Agreed. I have two follow up thoughts:

1. Badly designed functions can do less design damage per-function than badly designed languages per construct, but user-defined functions tend to have much higher volume than both language constructs and their usages.

2. Powerful techniques, including DSLs, lead to denser code. Denser code takes more time to read per unit of code. Total reading time may be either more or less, but the cold start ramp is always much steeper.

Combined, this means that people are less sensitive to suffering the complexity caused by user-defined functions vs DSLs/etc because that complexity has lower intensity spread out over a greater area. I'll refrain from any further value judgement on this state of affairs; just wanted to share some more insights in to the tradeoffs.

brandonbloom··on Having fun with Go's nil, interfaces and errors
I won't defend the weird behavior of equality comparisons to nil, that definitely seems wrong. I was mostly just addressing the notion that a language needs a formal specification and must fully abstract the machine to be useful or at least not worthy of ridicule.
brandonbloom··on Having fun with Go's nil, interfaces and errors
It's a stunningly difficult and surprisingly low-value proposition to create abstractions that don't leak. That's not the goal anyway: The goal is to leverage abstractions to build things. The implementation details frequently do matter. Even if your abstraction is airtight, it has to run on a real machine. When possible, it's preferable to design abstractions that have a clear implementation in terms of the lower level's abstractions. This way, when something goes wrong - and it will - you can understand and fix it.
brandonbloom··on Learning from Ada
Big +1 to this. The takeaway for me is to develop languages and tools that scale with the domain-expertise curve. That is, as you learn more about both the problem and solution, you can evolve your implementation without having to start over or get many things right from the start. The goal should be to both tighten the feedback loop and provide a smooth path from tinkering towards production grade.
brandonbloom··on Simpler GitHub Pages publishing
Not sarcastic at all. First, I said README.md. Markdown in readmes was popularized - if not invented by - GitHub. Furthermore, the top billing of README.md files in the GitHub UI took over from more traditional meta-level documentation that used to be in separate places, such as an "About" field/page on SourceForge or a separate web page. I much prefer this documentation in repository. Even if projects had READMEs, they might not include certain information there previously.
brandonbloom··on Simpler GitHub Pages publishing
Great! Please don't give in to the demand for added flexibility without strong justification. Just as it did with README.md, GitHub has an opportunity to drive successful conventions and global simplifications that virtually no other organization in open source can.
brandonbloom··on Technical debt as an opportunity to accumulate technical wealth
> Not less refactoring because we don't care about quality, but less refactoring because we get more right from the start.

Requirements always change and information is never perfect. You can't always get everything right from the start because you might not even know what you're trying to get right.

If you get malleability right from the start, you'd expect _more_ refactoring, since it's easier to do when you inevitably want to. If you're going to spend effort on trying to get things right from the start, that's where you should focus: Minimize things that are slow change, maximize things that make change less painful.

Concretely, this means spend less time code reviewing local variable names and curly brace style; spend more time reviewing public APIs, data format definitions, promised external semantics, etc. with an eye for versioning, deprecating, evolving, etc.

brandonbloom··on The cost of small modules
The problem isn't (necessarily) small modules, it's the _dynamics_ of the Common.js (and similar) module systems. By conflating fully general, extensible, dynamically typed objects with both namespaces and modules, the semantics can't be constrained enough to perform certain kinds of both intra- and inter-module optimizations at compile time.

By contrast, the Google Closure compiler's module system (also for JavaScript) does not suffer from this problem, but in tradeoff, disallows many types of runtime metaprogramming, such as merging modules with Object.assign or similar.

brandonbloom··on A Container Is a Function Call
Taken to its logical extreme, you get Algebraic Effect Handlers: http://math.andrej.com/2012/03/08/programming-with-algebraic...

The idea is a generalization of try/catch in which all effects are accomplished through handler blocks that receive a continuation. Just like when you make a kernel call, your program is a continuation given to the kernel. Usually the kernel calls you back once, but some syscalls return twice (fork) or not at all (abort) by manipulating processes.

Coupled with reusable handler block bundles, you can wrap a "container" around any expression in your entire program.

brandonbloom··on Atom 1.9 and 1.10 beta
I fetch coffee when I want coffee. I don't plan my schedule around waiting for things that have no reason not to be completely instantaneous. I avoid any tool that takes longer than 100ms or so to start up. Anything slower than that is completely unacceptable at this point in the history of computing.

> It seems silly to me for someone to adopt a strategy of opening/closing Atom (instead of reusing an open instance) and then complain about how long it takes to boot.

That's not what happens. They looked at the tool, stated it doesn't meet their needs, explained why, and then _did not adopt the tool_. The Atom folks can decide they don't want those users or they can address their abominable startup time, but you can't suggest "change how you do everything" and hope to win friends or users.

← PreviousPage 5 of 16Next →