Live data from Hacker News

A difference between Haskell and Common Lisp

chrisdone.com

171–180 of 203 posts

Re: A difference between Haskell and Common Lisp

#171
post #124

Earlier quoted context omitted.

Clisp is an implementation of Common Lisp. Common Lisp includes a core of the original Lisp. Lisp programs from the 60s can either be run or ported to Common Lisp with little or no effort. Clojure is FULLY incompatible with any other Lisp dialect or Lisp derived language. Porting code means 'rewrite'.

Oh. Sorry. I was abbreviating Common Lisp. Whoops. Anyways, that's like saying that Go, Plan 9 C, D, Java, and Cyclone aren't in the C family, because you can't run ANSI C '99 on them and have it work. The Lisp family is diverse.

See C++, Objective-C and a bunch of other languages actually in the C family.

They all share C. Real basic C.

'The Lisp family is diverse.' You interpret it in a way to make it practically meaningless.

Re: A difference between Haskell and Common Lisp

#172
post #170
post #169

Earlier quoted context omitted.

> But how is `a = read(); print(a)` not explicit? It doesn't tell me which of the calls is effectful and which is pure. It's clear enough at the direct level, but if I'm calling methods that I don't know the implementation of then the =/ > That's only to explain the typing. Java's checked continuations are an effect system, and the list of thrown exceptions can be viewed as a list of effects a function may have. That…

> It doesn't tell me which of the calls is effectful and which is pure. But it does because their type says so: String read() effect IO (where `effect` is just like Java's checked `throws`) > what is the type of a? "(String effects IO)" or some such? Right. It is as I said, a type on read, not on a. `a` is still `String`. > only allowed inside the scope of a method with IO as an effect then it's the same problem as c…

> But it does because their type says so:

Sure. But I remember doing this with checked exceptions, mousing over each method call one by one to figure out which ones could or couldn't throw. It's much nicer if you can see which methods are the throwey ones immediately - =/> But that's the opposite of a problem. That's like using `IO a` outside of an IO monad. You want the function to only be called in the right context (an appropriate handler is in effect).

Disagree. I want to be able to have a value of type IO a outside a method - e.g. in a static field. More generally it's really huge to be able to just treat these things as ordinary values that I can manipulate using the ordinary rules of the language.

> I'm not sure if the same effects would make sense, though. For example, Either is a monadic value which doesn't make sense in an imperative setting.

The two effects are "possibly skip the rest of the computation, returning an Error instead" and "record a programatically-accessible Log" - I think those both make sense. The point is the reason you have to use the horrible monad transformers is that the order in which you evaluate these effects matters - if there's an early error, are later log statements recorded, or not? There are more complicated cases when e.g. catching I/O errors.

Re: A difference between Haskell and Common Lisp

#173
post #172
post #170

Earlier quoted context omitted.

> It doesn't tell me which of the calls is effectful and which is pure. But it does because their type says so: String read() effect IO (where `effect` is just like Java's checked `throws`) > what is the type of a? "(String effects IO)" or some such? Right. It is as I said, a type on read, not on a. `a` is still `String`. > only allowed inside the scope of a method with IO as an effect then it's the same problem as c…

> But it does because their type says so: Sure. But I remember doing this with checked exceptions, mousing over each method call one by one to figure out which ones could or couldn't throw. It's much nicer if you can see which methods are the throwey ones immediately - =/ > But that's the opposite of a problem. That's like using `IO a` outside of an IO monad. You want the function to only be called in the right conte…

> It's much nicer if you can see which methods are the throwey ones immediately - =/Well, I guess that's a matter of taste, but 1/ what information does "effectful" convey if you don't know what the effect is until you mouse-over? 2/ I think that's a very, very low price (if it is a price at all) to pay for something that fits much better with non-pure languages.

> I want to be able to have a value of type IO a outside a method - e.g. in a static field

But continuation effects don't have `IO a`, just `a`. The effect comes with the context. An `IO a` would translate to a lazy value, namely a function, which could be a continuation. There's nothing stopping you from storing a continuation in a field. You can manipulate the `a` just like any value, and the "IO" with handlers -- just as you do with monads.

> possibly skip the rest of the computation, returning an Error instead

Yes, but that's not all Either says. It says "return an error or a value of type a". In imperative code that makes less sense. What you want to say is: "throw an exception or don't (and return whatever value it is that the function returns)". The type of the normal return value is not recorded in the Either.

> The point is the reason you have to use the horrible monad transformers is that the order in which you evaluate these effects matters

You need monad transformers not because of ordering, but because monads don't generally compose. Or it's a bit reversed: because monads don't compose they are sensitive to accidental ordering (whether you care about the order or not), which makes monad transformers tricky. With continuations you have the choice of when to be sensitive to order and when not to.

For example, in the imperative case, you could encode the information "if there's an early error, are later log statements recorded, or not?" directly in the type, (I think) but I don't see any reason to. In Haskell you must because the types need to be nested in one particular way according to the transformer.

In any case, my claim is as follows: if you generally prefer the imperative way of doing effects (like in Java, OCaml, F#, Scheme, JS, Python, Clojure, Erlang etc.) -- as I do -- then continuations are far more natural than monads. If you prefer the PFP way, you should stick to monads (or if using Haskell, must stick to monads).

Re: A difference between Haskell and Common Lisp

#174
post #173
post #172

Earlier quoted context omitted.

> But it does because their type says so: Sure. But I remember doing this with checked exceptions, mousing over each method call one by one to figure out which ones could or couldn't throw. It's much nicer if you can see which methods are the throwey ones immediately - =/ > But that's the opposite of a problem. That's like using `IO a` outside of an IO monad. You want the function to only be called in the right conte…

> It's much nicer if you can see which methods are the throwey ones immediately - =/ Well, I guess that's a matter of taste, but 1/ what information does "effectful" convey if you don't know what the effect is until you mouse-over? 2/ I think that's a very, very low price (if it is a price at all) to pay for something that fits much better with non-pure languages. > I want to be able to have a value of type IO a outs…

> An `IO a` would translate to a lazy value, namely a function, which could be a continuation. There's nothing stopping you from storing a continuation in a field. You can manipulate the `a` just like any value, and the "IO" with handlers -- just as you do with monads.

I think the Java world is finally accepting that methods are not a substitute for first-class values; one of the biggest remaining problems with Java lambdas is that it's awkward to use them with checked exceptions (e.g. do you to create a named @FunctionalInterface for each input/output/exception combination you want to store? You can use generics but they don't let you abstract over arity). Handling effects this way would presumably have the same problems?

Put it this way: Either[MyError, A] is much more useful to me than a checked MyException, because I can use the former as a value whereas the latter I can only throw from methods. That's the difference I'm worried about here.

> You need monad transformers not because of ordering, but because monads don't generally compose. Or it's a bit reversed: because monads don't compose they are sensitive to accidental ordering (whether you care about the order or not), which makes monad transformers tricky. With continuations you have the choice of when to be sensitive to order and when not to.

What does the choice look like in code then? Under monads I'd write:

    Left(MyError()).liftM[WriterT[...]]
    WriterT.tell("Here".point[...])
or

    EitherT.left(MyError().point[...])
    Writer.tell("Here").liftM[EitherT[...]]
and while it's clunky it's clear which one's which. Under your approach if I write:

    throwc(new MyException());
    add(Writer.class, "Here");
then which semantics do I get, and how do I get the other one?

Re: A difference between Haskell and Common Lisp

#175
post #174
post #173

Earlier quoted context omitted.

> It's much nicer if you can see which methods are the throwey ones immediately - =/ Well, I guess that's a matter of taste, but 1/ what information does "effectful" convey if you don't know what the effect is until you mouse-over? 2/ I think that's a very, very low price (if it is a price at all) to pay for something that fits much better with non-pure languages. > I want to be able to have a value of type IO a outs…

> An `IO a` would translate to a lazy value, namely a function, which could be a continuation. There's nothing stopping you from storing a continuation in a field. You can manipulate the `a` just like any value, and the "IO" with handlers -- just as you do with monads. I think the Java world is finally accepting that methods are not a substitute for first-class values; one of the biggest remaining problems with Java…

> Handling effects this way would presumably have the same problems?

First, I didn't suggest actually using checked exceptions as they are now to model "typesafe continuations" in Java. I'm perfectly fine with continuations not being fully type-checked (I like types, but I'm far from a type zealot).

But more to the point, such an effect system (or actually even Java's checked exceptions) won't pose such a problem at all. On the contrary. Just like you can't store a monadic value in a variable of a "plain" value, it makes sense to reject `int foo() throws IO` from a computation that doesn't support IO. It is a problem in Java with checked exceptions because `int foo() throws InterruptedException` would work where `int f()` is expected, and having it rejected (and requiring a wrapper) is indeed a nuisance. That's not the same issue if effects are involved.

> I can use the former as a value whereas the latter I can only throw from methods. That's the difference I'm worried about here.

I'm not sure I understand the distinction. You can always use something like Optional (Maybe) if you want a value. Alternatively, no one is stopping you from using the monadic Either even in an imperative context (although you may lose the stack trace). You can still use it this way if that's how you like doing things. I don't.

> then which semantics do I get, and how do I get the other one?

    throwc(new MyException());
    add(Writer.class, "Here");
would never call the writer (like your first example, I believe), while

    tryc(() -> {
           add(Writer.class, "Here");
           ...
        },
    // catch:
        (MyException e) -> 
             add(Writer.class, e));
would log the exception, as in your second example.

You don't even need to think about it. It works just like plain imperative code. Continuations are the general (mathematical if you will) formulation of how imperative code already behaves.

Re: A difference between Haskell and Common Lisp

#176
post #175
post #174

Earlier quoted context omitted.

> An `IO a` would translate to a lazy value, namely a function, which could be a continuation. There's nothing stopping you from storing a continuation in a field. You can manipulate the `a` just like any value, and the "IO" with handlers -- just as you do with monads. I think the Java world is finally accepting that methods are not a substitute for first-class values; one of the biggest remaining problems with Java…

> Handling effects this way would presumably have the same problems? First, I didn't suggest actually using checked exceptions as they are now to model "typesafe continuations" in Java. I'm perfectly fine with continuations not being fully type-checked (I like types, but I'm far from a type zealot). But more to the point, such an effect system (or actually even Java's checked exceptions) won't pose such a problem at…

> I'm not sure I understand the distinction. You can always use something like Optional (Maybe) if you want a value.

I mean that the "value or error" that I get back from a function that returns Either is itself a value, that I can use in the normal way I would use a value. I can pull it out into a static field. More importantly I can pass it back and forth through generic functions without them having to do anything special about it (e.g. as a database load callback), because it's just a value. I notably can't do this with exceptions (or e.g. if I'm emulating Writer with a global or ThreadLocal variable), though checked exceptions will at least fail this at compile time.

> would never call the writer (like your first example, I believe)

Hmm. If you're willing to fix the order in which effects happen (and explicitly handle when you want to change it, as in your example) then you can write something much simpler than the usual monad transformers, so make sure you're comparing like with like.

Re: A difference between Haskell and Common Lisp

#177
post #176
post #175

Earlier quoted context omitted.

> Handling effects this way would presumably have the same problems? First, I didn't suggest actually using checked exceptions as they are now to model "typesafe continuations" in Java. I'm perfectly fine with continuations not being fully type-checked (I like types, but I'm far from a type zealot). But more to the point, such an effect system (or actually even Java's checked exceptions) won't pose such a problem at…

> I'm not sure I understand the distinction. You can always use something like Optional (Maybe) if you want a value. I mean that the "value or error" that I get back from a function that returns Either is itself a value , that I can use in the normal way I would use a value. I can pull it out into a static field. More importantly I can pass it back and forth through generic functions without them having to do anythin…

> I can pull it out into a static field. More importantly I can pass it back and forth through generic functions without them having to do anything special about it (e.g. as a database load callback), because it's just a value. I notably can't do this with exceptions (or e.g. if I'm emulating Writer with a global or ThreadLocal variable), though checked exceptions will at least fail this at compile time.

I understand, and as I said, I normally prefer not working this way. Others -- like yourself -- do. Both ways are perfectly valid. I prefer exceptions to not be values, as, to me, they capture a failed computation not a missing result. For a missing value, I can use null/Optional etc.. But in any case, it's not an either/or thing. You can always catch exceptions and store them if you want; you can even use Either if that's how you prefer to work.

> If you're willing to fix the order in which effects happen (and explicitly handle when you want to change it, as in your example) then you can write something much simpler than the usual monad transformers

How? You need to chain functions, each emitting some set of effects, and you don't control in advance the order of the effects in each function.

I am not fixing the order of effects any more than in your examples. The reason the examples look different is because I've chosen to represent an error not as a value but as a control-flow construct, but I don't have to, so let's look at other effects:

If I have two functions,

    void foo() { print("hi"); log(1); } 
    void bar() { log(2); print("bye"); }
This is how I compose them:

    foo(); bar();
With monads you need to make sure that they both return the same nesting of Log[Writer] or Writer[Log], but I don't need to care.

Re: A difference between Haskell and Common Lisp

#178
post #171

Earlier quoted context omitted.

Oh. Sorry. I was abbreviating Common Lisp. Whoops. Anyways, that's like saying that Go, Plan 9 C, D, Java, and Cyclone aren't in the C family, because you can't run ANSI C '99 on them and have it work. The Lisp family is diverse.

See C++, Objective-C and a bunch of other languages actually in the C family. They all share C. Real basic C. 'The Lisp family is diverse.' You interpret it in a way to make it practically meaningless.

No. The lisp family is a real thing. They all share homoiconic syntax, singly linked lists as a primary data structure, and most of them support syntactic extension through macros.

While the validity of some of my examples may have been questionable, how is Plan 9 C not in the C family? and just TRY to compile ANSI C on a P9C compiler without significant modification.

Re: A difference between Haskell and Common Lisp

#179
post #118

Earlier quoted context omitted.

Unix commands happily pass around binary streams of any format. The problem is, as you noticed, there is not schema, no uniform (let alone machine-readable) description of accepted / emitted formats. Composability is possible but not entirely trivial, often with a dose of `grep` / `sed` / `cut` between commands. It's also pretty hard to pass a function to a Unix command. Either your command supports its own syntax (g…

"It's also pretty hard to pass a function to a Unix command." On this and a few other points, it would be interesting to allow a process to call out to the shell that it's running below ("... if any" being one of several issues with the idea).

... Well, you can pass a string to exec into a command. in fact, unix has its own map like command based on this. It's called xargs.

Re: A difference between Haskell and Common Lisp

#180

Earlier quoted context omitted.

"It's also pretty hard to pass a function to a Unix command." On this and a few other points, it would be interesting to allow a process to call out to the shell that it's running below ("... if any" being one of several issues with the idea).

... Well, you can pass a string to exec into a command. in fact, unix has its own map like command based on this. It's called xargs.

You can, but that's a different thing - creating a new shell as a child of the command, rather than talking to the parent shell of the command.
Post reply on HN