Hacker News new | past | comments | ask | show | jobs | submit login

We want to call them one after another, and return a failure if any of them fails, success if they're all succesful

I didn't find that example particularly motivating since I can easily think of a simpler solution: exceptions. (Alternatively, if I was using C, it'd be setjmp()/longjmp(), which perform a very similar style of control flow.)




Exceptions are a more complex solution. They're much harder to reason about, and you can't abstract over them because they're special-cased in the language. I've literally seen Java libraries offer an interface something like this:

    <T> T doSomething(Callback<T> callback)
    <T, E extends Exception> T doSomethingThrow(CallbackThrow<T, E> callback) throws E
and even that doesn't properly cover the cases, because ideally you'd like to be able to pass a callback that throws two or more unrelated exceptions as well. Setjmp/longjmp is "simple" like goto is simple; if you want to refactor a function that uses them you have to think very carefully.

If we have \/, the possibly-error is just a value. We can use it with generic functions in the normal way. We can refactor them in the obvious way and it will just work correctly, and the type system can assure us we've done it right.


Exceptions are a more complex solution.

Are they really? I'd like to see the actual code which gets generated from that ultra-abstract solution, since I already know what creating an exception frame or setjmp/longjmp turns into.

If we have \/, the possibly-error is just a value.

If you use pointers, then an error can also be signified with a value (0), and there's also this very simple but possibly slightly less efficient way (in the success case):

    ret = (v1 = func1(v0)) && (v2 = func2(v1)) && ...


It's only "simpler" if you approach programming from a low-level, non-functional perspective, which seems to be where you come from. The parent is talking about complexity in terms of solution complexity (avoiding undesireable states and writing fail-proof software), not in number of machine-code instructions generated, or not having to learn a new abstraction.

The people interested in functional programming and compile-time guarantees want to work at higher-levels of abstraction, where Exceptions are just hard to typecheck crutches that effectively implement the Option/Either types but outside of control of you compiler (at the call stack level).


> Are they really? I'd like to see the actual code which gets generated from that ultra-abstract solution, since I already know what creating an exception frame or setjmp/longjmp turns into.

Do you know the micro-ops that that "actual code" will be executed as? The voltages that will pass through each logic gate? Or do you trust that the processor will behave the way that it's specified to and not worry about how it actually implements it?

Have you seen how Smalltalk does control flow? if or while are just ordinary (virtual) functions that take a function as an argument. If you understand the concept of passing a function to a function, and the concept of calling a virtual function, then you don't need to have a separate concept for if or while, they're just ordinary functions written in the language that you can read the code of. That to me is real simplicity, and either gives you the same thing.

> If you use pointers, then an error can also be signified with a value (0), and there's also this very simple but possibly slightly less efficient way (in the success case)

I like that. Genuinely. And at an implementation level that's pretty much what Option is. There are a couple of awkwardnesses: the vns would have to be declared beforehand, making it possible to use them uninitialized (though I guess that's always a danger in C?), and you can't extend it to something like Either where your error can contain data, but as a first cut it's pretty cool.

The advantage of using an applicative or monad over that is that you get access to an existing library of functions. E.g. traverse (apply a possibly-error function to each element of a list, returning either a list of successful values or a single error), or the various control flow operators like ifM (execute one or other of two possibly-error functions, where the boolean we use to decide might already have been an error). The standard library could define all these functions just for possibly-zero-pointer, but it's nicer to define them generically so that they can be reused for possibly-error, async-operation, operation-with-programatically-accessible-log, operation-depending-on-value-I'll-supply-later, operation-that-needs-to-be-inside-a-database-transaction and so on, in the same way that e.g. sort is defined generically rather than just on integers.

At a more advanced level you can also define your own generic operations. E.g. I have a generic Report class; one of the subclasses uses async I/O (because it needs to call a web API) and also needs to record some statistics about the rows, another does the per-row piece of the report in a way that might fail, another needs to write to the database on each row. By writing my (abstract) Report class in terms of a generic monad (with abstract methods that return that monad), I can reuse the same logic for all these implementations.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: