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

I had no clue this was about async functions. I assumed it was safe/unsafe functions until I got to the part about it not being. I think that is a much stronger issue than sync/async.

These kind of distinctions happen all the time. The classic http://www.joelonsoftware.com/articles/Wrong.html - one colour for html-encoded strings, one colour for plain strings. Functions that might fail with an error or functions that never fail. Logging versus non-logging. Database-accessing or not. Callable from user scripts or not.

Keeping track of colour - in a way where correctness is guaranteed by the computer - is such a useful general feature that any serious modern programming language should support it. We should have easy, simple syntax for specifying colour, and should make polychromatic functions almost as easy as monochromatic ones.

When you do that - when the red of "async" is just one colour among a whole rainbow that your program is already tracking - then the cost of making an explicit distinction between async and not is minimal, and the benefits - being able to track, control, and see instantly whether a function is async or not - are more than worth it.

I'm not sure if you're leading this or not, but to be clear... this is exactly a type system.

(Edit: read your blog; it's clear you're leading it. Poe's Law is in effect sometimes here, sorry 'bout that!)

It's a good type system. Many programming languages don't support this kind of type information, notably C.

I'm always curious how far a sufficiently motivated masochist could go with C... I guess it's just C++ ;)

You can write very safe code by using structs (even 1-element structs) for everything. But there's no generics system and very little in the way of safe tools for making one; writing a safe "generics runtime" and macros to use it puts you halfway to writing a new language.

>structs (even 1-element structs) for everything.

Are there any large C projects written in this style?

I don't recall the LOC, but I used this style at a recent position. Basically, the idea is not to use bare primitive types anywhere - instead wrapping them in something semantic. Probably the two biggest wins from this approach were distinguishing types of indexes and (in the particular project) distinguishing price from quantity.

It does add a bit of boilerplate. For the indexes in particular, foo_lookup(foo, idx) is substantially safer than foo[idx.value] - but then you've gotta write the lookup functions (which may just be a macro but is still a bit of cruft). Locally, if using var.value everywhere in math is getting ugly, you can of course pull things out into temporary primitive variables - enforcing things at the boundaries between functions provides almost all of the benefit.

All I'm aware of is a friend's private project of around 20KLOC.

I would love to see even small examples of this.

I intend to write a blog post on this at some point... but I recently colored functions in C by passing an empty struct as the first argument, with a standard name and annotated with __attribute__((unused)). The propagation was manual, but trivially easy, and it proved a major help in refactoring when I had to move some functionality to a differently colored context. I was even able to get some (extremely limited) polymorphism using unions to express "any of these colors". It could even be marginally nicer if C allowed passing one union as another if the first was a subset of the second - which I think would be correct - but really parametric polymorphism would be the bigger win...

The Linux kernel is a good example of just how far you can push C (plus GCC and Sparse extensions), including type system enhancements (__user and __kernel), pseudo-OO (kobject, many structs of function pointers), and other safety features.

I was convinced it was going to be about pure vs impure (monadic) code, but then he just picked out one particular monad instance (coroutines/asynchronous operations).

Any time you have side effects, you potentially have "red" code, in that it can add arbitrary restrictions on how you must use the functions.

I guess more generally, it's about effects. The same rant could apply to any particular effect, because they share the property of being infectious: async, unsafe (in C#, not Rust), throws (checked exceptions), IO, etc.

My question is: In a language that has a first-class effect system, does the red/blue problem disappear? Does being able to generalize over effect allow you to avoid cutting the world in half, and allow you to compose effects easier?

Absolutely, yes.

For instance, here is an abstraction of code which reads and writes

    class Monad m => MonadTeletype m where
      writeLn :: String -> m ()
      readLn  :: m String
Here is one which receives the current time

    class Monad m => MonadNow m where
      now :: m UTCTime
And here is code which transparently combines them

    echoTime :: (MonadNow m, MonadTeletype m) => m ()
    echoTime = do
      line <- readLn 
      t0   <- now
      writeLine (show t0 ++ ": " ++ line)
You then, when actually executing echoTime, have to create a monadic implementation which you prove to instantiate both MonadTeletype and MonadNow. For instance, we can always show that `echoTime` can be satisfied by the Haskell "sin bin" type, IO:

    instance MonadTeletype IO where
      writeLn = putStrLn
      readLn  = getLine

    instance MonadTeletype IO where
      now = getCurrentTime
That said, it's easy to write monadic languages like MonadTeletype and MonadNow which aren't trivially satisfied by IO. This occurs when you've imputed new meaning and language into your monad, which is really cool. IO is the "sum of all evils", but it's not terrifically expressive.

Except that's not an effect systems because you only have one real effect - the IO type. The challenge of effect systems is to describe effects and how they interact. For example, you'll have an effect that says "a lock is obtained" and one that says "a lock is released", and if you call them both, in the right order, in the same function, then that function has an effect of "mutating something under lock".

Sure it is! The effect `(MonadTeletype m, MonadNow m) => m a` is just as real as IO. There's nothing special about IO except that the Haskell RTS knows how to interpret it.

I could, for instance, build a pure interpreter of those effects into, say, a stream transformer or compile it into a different language (though we'd have to do some tricks to expose Haskell's name binding).

If you want your lock obtained/lock released bit then you want indexed monads. It's easy enough to do, but the safety/complexity tradeoff in the Haskell community has landed on the other side of that... probably for more historical reasons than actual technical ones.

> There's nothing special about IO except that the Haskell RTS knows how to interpret it.

But that is what makes it an effect. Sure, you can model effects in a "hosted" language this way, but that's not what we mean by effects.

I disagree. In force, the Haskell RTS is just a "model" of the IO effects as well. In particular, there are at least two such models, the GHCi interpreter and the GHC compiler!

As another example, it'd be completely possible to write your own IO and interpret it in another language. You can read about this on Edward Kmett's blog where he talks about implementing IO for Ermine [0]


Of course, there's something important that makes us want to differentiate "real world" side effects from internal "model effects". Ultimately, from a correctness and reasoning POV, there ought to be no difference. From a practical point of view, some models are more interesting or important than others. But as a compiler writer you're put right back into the same hot seat.

[0] Unfortunately, his blog appears to be OOC right now, so here's a weird chinese mirror!

At first, it reminded me about const-correctness in c++.

Applications are open for YC Winter 2020

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