Live data from Hacker News

Exotic Programming Ideas, Part 3: Effect Systems

stephendiehl.com

71–80 of 95 posts

Re: Exotic Programming Ideas, Part 3: Effect Systems

#71
post #40

Earlier quoted context omitted.

I'm wondering if you've found it useful in practice to distinguish between total and possibly-diverging functions? It seems like it's the sort of thing that's useful in something like Agda, where you use the existence of a function (without running it) to prove that its result exists. (The type is inhabited.) Or so I've read; I haven't used it. But if you're going to run the program, you typically want to know if a f…

I have found total checks in languages like idris helpful, but really only for catching small mistakes that I've made (for example, recursing on the original argument)

If your language has equality proofs, for example `a = Int` then, if your language is non-total I can prove anything by divergence. So I can prove `Int = String`. This is still type-safe as long as you don't erase the proof, because you can never use the invalid proof because your program will diverge.

So if you want to be type-safe but you still want to be able to erase proofs, your proofs have to be total.

Re: Exotic Programming Ideas, Part 3: Effect Systems

#72
post #62

Earlier quoted context omitted.

Source: https://www.reddit.com/r/programming/comments/7wbtg/who_is_o... - Oleg eats lambdas for breakfast - The Y combinator fears Oleg - Oleg knows all the programs that halt on a Turing machine - Oleg is the guy that Chuck Norris goes to when he has an algorithm complexity question - Oleg reprograms his own DNA with Scheme macros - All of Oleg's Haskell programs are well-typed by definition - Oleg can read C++ comp…

- Oleg can read C++ compiler error messages. Oh, come on. You were doing so great until this point.

Agree. Because even gods can't read Cpp template errors.

Re: Exotic Programming Ideas, Part 3: Effect Systems

#73

Earlier quoted context omitted.

Honestly, in the Haskell world, no one really complains about broken APIs, it's actually pretty common for a library to change things in a breaking way. People tend to either use Nix or stack to deal with versioning or they just plan to refactor when they have to. I personally just started with Haskell, but I've switched a small tool from one streaming library to another and it was super easy even though each had a v…

Yes, there are easier and harder ways to deal with api migrations. But even in Haskell, isn't "Cabal hell" a thing? And the type of the standard prelude's head function is wrong but it can never be changed. Dependency migration is the sort of thing that's easy enough in small examples and gets harder as you scale up.

You should use Haskell and see what it can do, you can change the prelude easily, for instance.

The standard head function is partial, yes, but that's not a big deal at all.

Re: Exotic Programming Ideas, Part 3: Effect Systems

#74
post #51

Earlier quoted context omitted.

Well, that's the standard Common Lisper opinion. I don't buy it though -- I don't get the impression that it's a problem in clojure (which does not have scheme-style hygenic macros, but shares the single namespace for variables and functions) either.

The hygiene problem has multiple aspects. One issue is that the user's code can bind identifiers which the macro expansion expects to be predefined. In a Lisp-1, we have to worry about this: (let ((list ...)) (mac ...)) ;; expansion of mac wants to call (list ...) Since the macro is not defining anything (its expansion is not introducing a binding for list), the ordinary gensym approach is not applicable. THe problem…

Again this is not fundamentally tied to whether functions and variables share a namespace, in clojure they do and yet your example above does not cause a problem. Watch:

Let's start out with a function:

   user=> (defn enlist [x] (if (list? x) x (list x)))
   #'user/enlist
   user=> (let [list 1] (enlist list))
   (1)
Create a (pointless) equivalent macro:

   user=> (defmacro enlist' [x] `(let [y# ~x] (if (list? y#) y# (list y#))))   
   user=> (let [list 1] (enlist' list))
   (1)
No hygiene and yet it works!

How? The secret is that backquote is automatically namespace qualifying:

    user=> '(list 1 2 3)
    (list 1 2 3)
    user=> `(list 1 2 3)
    (clojure.core/list 1 2 3)
In practice this + convenient gensym syntax (also demonstrated above) seems to be enough, just like in practice Common Lisp's approach of separate value and function cells for symbols + prohibition of rebinding function cells of symbols in the standard COMMON-LISP namespace is. Scheme has no cl style namespaces and no prohibition on redefining standard bindings and thus came up with a much more complex macro approach, at the gain of stronger hygiene guarantees (but see article above!) and the cost of an ugly design of considerable complexity that has a completely different sublanguage (non-orthogonally) baked in.

As far as purely hygiene is concerned that's IMO not a good trade-off at all; I could rattle off a long list of major usability gripes with both cl and clojure but problems caused by lack of macro hygiene wouldn't be on it.

Racket, which is basically several iterations beyond R*RS macros, OTOH is interesting because it's more principled approach gives you something qualitatively different to what you can do with clojure (no read-syntax control; bad error messages) and common lisp (readtables suck and break tooling; bad error messages).

Re: Exotic Programming Ideas, Part 3: Effect Systems

#75
post #3

I highly recommend Oleg Kiselyov's talk titled "Having an Effect"[0] in which he talks about - purely functional model of computation and its pitfalls - Actor model, effectful programming with requests and responses and an implementation in Haskell. - denotational semantics and combining effects. Once you have a model of your language, what if you want to extend it by adding another effect? It forces you to rewrite t…

Ever seen network protocols modeled in pure FP ?

Re: Exotic Programming Ideas, Part 3: Effect Systems

#76
post #22

Earlier quoted context omitted.

Well, certainly no one seems to understand how e.g. syntax-case works. But my impression is that macro hygiene in itself is a solution looking for a problem. The key advantage e.g. racket's macro system has over clojure or common lisp is not hygiene but being sufficiently well structured and rich to allow proper tooling. Good error messages with accurate locations >> macro hygiene.

I think in theory they’re orthogonal but in practice the two go together. It’s all fine and dandy when you’ve got a restricted set of battle tested macros from a single library interacting in real world code, but it rapidly breaks down when you’ve got application authors of various skill sets all contributing their own macros because the dare not touch the ones that came before. Macro hygiene provides the equivalent…

Is this based on conjecture or personal experience from working on multi-dev projects with both hygienic and unhygienic macro systems?

I lack such comparative experience but my guess would be that if your org's development approach is such that inexperienced people churn out dodgy macros without more expert review you are screwed and having a hygienic macro system will not save you. I'm very sceptical hygienic macros are at all comparable in utility to what a type system provides for multi-dev projects of any size. These benefits of types at scale are fairly massive and apply to basically any code. In contrast macros should make up a tiny amount (much less than 5% certainly) of code in a non-trivial application code base which means careful review is not a big overhead (and in my experience competent programmers do not introduce a lot of hygiene problems). So to be equivalent, in those instances where you do get a benefit from hygiene, that benefit would need to be absolutely massive compared to the average benefit you get of types.

Re: Exotic Programming Ideas, Part 3: Effect Systems

#77

Arrow Fx implement this idea for Kotlin -> https://arrow-kt.io/docs/fx/

It only implements the first part, not the more difficult one of composing different kind of effects. Scala has a library for the difficult part, but I don't think it was very successful: https://github.com/atnos-org/eff

There's also effekt for Scala: https://github.com/b-studios/scala-effekt though it does not seem to be maintained.

Re: Exotic Programming Ideas, Part 3: Effect Systems

#78

Earlier quoted context omitted.

Nothing prevents having a type system capable of dealing with those problems. Yes, Java and Golang make this difficult, but if you have a language that supports it, there's nothing which prevents writing an API that says "Whatever the effects of the Callable you passed me are, I also perform those effects".

Introducing generic types doesn't make the function-coloring problem go away. Now, instead of having a single function that has some effects, you have a family of closely-related functions, each with different effects. These functions are incompatible with each other. The function-coloring problem has gotten worse! :) (Generic types are still useful though.)

No, it doesn't make it go away, but it makes the "function coloring problem" about as much a problem as the "colors" of functions as determined by their return types. You can't pass a `func () string` to an api accepting a `func () int`.

Re: Exotic Programming Ideas, Part 3: Effect Systems

#79

Earlier quoted context omitted.

It does help by telling you what's wrong. But in a way, increased precision makes the problem worse. Suppose you have have a language with two categories of functions, those that can fail (returning an error) and those that can't. It's nice that within the "functions that can fail" category, you don't have to worry about what kind of error it might be. Error propagation can happen in a generic way. Adding a new kind…

Honestly, in the Haskell world, no one really complains about broken APIs, it's actually pretty common for a library to change things in a breaking way. People tend to either use Nix or stack to deal with versioning or they just plan to refactor when they have to. I personally just started with Haskell, but I've switched a small tool from one streaming library to another and it was super easy even though each had a v…

Honestly, in the Haskell world, no one really complains about broken APIs

I don’t believe that’s true. The very existence of Stack and Stackage is testament to how significant the compatibility problems had become in the Haskell ecosystem.

Even if it were true, Haskell world is tiny and (in)famously has the motto “Avoid success at all costs”, so it isn’t necessarily a representative example of practices that would be effective in other contexts.

The advantage with a type system as explicit as Haskell’s is that at least if there is a breaking change in an interface you depend on, you get quite good information about where the problem is and what you need to do to fix it.

Also, breaking API changes happen in every language, on every codebase, ask the time.

Some people take backwards compatibility extremely seriously, and code that depends on their interfaces still works just fine many years after it was written. The pace of development is usually slower when robust standardisation is part of the process, but that isn’t necessarily a bad thing given the stability you get in return.

At least with static types and a common linter I can refactor and be a hundred percent sure it works since it compiles

This is a common claim in the Haskell world, but of course it’s not really true. It always reminds me of Knuth’s famous quip, “Beware of bugs in the above code; I have only proved it correct, not tried it.”

Re: Exotic Programming Ideas, Part 3: Effect Systems

#80

Earlier quoted context omitted.

Honestly, in the Haskell world, no one really complains about broken APIs, it's actually pretty common for a library to change things in a breaking way. People tend to either use Nix or stack to deal with versioning or they just plan to refactor when they have to. I personally just started with Haskell, but I've switched a small tool from one streaming library to another and it was super easy even though each had a v…

Yes, there are easier and harder ways to deal with api migrations. But even in Haskell, isn't "Cabal hell" a thing? And the type of the standard prelude's head function is wrong but it can never be changed. Dependency migration is the sort of thing that's easy enough in small examples and gets harder as you scale up.

But even in Haskell, isn't "Cabal hell" a thing?

Less so than it used to be. Cabal received a big update a few years ago, and the new-style commands now operate more like many other package managers in terms of isolation etc.

Post reply on HN