Live data from Hacker News

A difference between Haskell and Common Lisp

chrisdone.com

181–190 of 203 posts

Re: A difference between Haskell and Common Lisp

#181
post #139
post #125

Earlier quoted context omitted.

You can have currying in a dynamically-typed, strictly evaluated language.

But when that language has variadic parameters, you must explicitly invoke the currying to distinguish it from a normal function call that uses up all the variadic parameters (just as you can't specify different types for the variadic parameters). In a language where each function has only one parameter, the currying can be implicit (just as all parameters defined in the program can be statically typed).

You can have "implicit" currying behaving as you described in a dynamically-typed, strictly evaluated language as well.

Re: A difference between Haskell and Common Lisp

#182
post #181
post #139

Earlier quoted context omitted.

But when that language has variadic parameters, you must explicitly invoke the currying to distinguish it from a normal function call that uses up all the variadic parameters (just as you can't specify different types for the variadic parameters). In a language where each function has only one parameter, the currying can be implicit (just as all parameters defined in the program can be statically typed).

You can have "implicit" currying behaving as you described in a dynamically-typed, strictly evaluated language as well.

I should have written "You can, but..." in front of my comment above because you weren't disagreeing with me. In my original comment I italicized "must be variadic" which was my original emphasis.

But if you eliminate variadicity and macros from a dynamically-typed, strictly evaluated language, you might as well add optional static typing so you can extend that implicit currying into monads, etc.

Re: A difference between Haskell and Common Lisp

#183
post #177
post #176

Earlier quoted context omitted.

> 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…

> foo(); bar();

The whole point of composition is that foo(bar()) is foo(x) where x = bar(), which means that you can understand the combined program by understanding foo and bar independently. If you can't represent the result of bar() as a value x then that breaks down, in which case I don't see what this whole project is about? I mean being able to implicitly pass down a handler is cool, and there's some value in being able to swap out e.g. a "real" and "test" version, but it sounds like the effect sequencing is still directly coupled to the code operation.

> 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.

You only don't need to care if the effects commute - and in that case you can actually automatically move them past each other in the monad world (my scalaz-transfigure library does this, at least at the proof-of-concept level). For noncommutative effects you can do the same thing if you're willing to fix the nesting order (e.g. say that an exception always trumps a later log, or always doesn't).

Re: A difference between Haskell and Common Lisp

#184
post #147
post #145

Earlier quoted context omitted.

> It's far more difficult to define macros when you don't have variadicity. Why? > A large number of syntactic constructs you'd want to define would even be impossible. Like what?

Many functions and macros in Clojure rely on reducing variadic parameters using recursion, e.g. (defn max "Returns the greatest of the nums." ([x] x) ([x y] (. clojure.lang.Numbers (max x y))) ([x y & more] (reduce1 max (max x y) more))) You'd be limited to 2 params if you didn't have variadicity. Some macros like switching, which in Clojure is called `condp`, would lose their elegant single-parens appearance if vari…

Again, you are confusing syntax and semantics. All you need to do to have the exact same surface syntax with single-argument functions is to stipulate that the arguments to a macro expander are passed in as a (single) list.

Re: A difference between Haskell and Common Lisp

#185
post #183
post #177

Earlier quoted context omitted.

> 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…

> foo(); bar(); The whole point of composition is that foo(bar()) is foo(x) where x = bar(), which means that you can understand the combined program by understanding foo and bar independently. If you can't represent the result of bar() as a value x then that breaks down, in which case I don't see what this whole project is about? I mean being able to implicitly pass down a handler is cool, and there's some value in…

> which means that you can understand the combined program by understanding foo and bar independently. If you can't represent the result of bar() as a value x then that breaks down

Can you not understand this program by understanding both statements separately?

    print("hi") ; print("there")
Why is that more understandable if the result of print is a value? (obviously, the result of read() is). In any case, describing everything as a value is a PFP idea (note that neither the Lisps nor the MLs do it), that some people like and others (like me) don't. In fact, I find the notion of treating every computation as a value (rather than possibly wrapping it as one), a not too great idea. Values are equational, computations are not (even pure ones). It's a nice abstraction to have at your disposal, but enforcing it everywhere is too much.

> You only don't need to care if the effects commute - and in that case you can actually automatically move them past each other in the monad world

You could, obviously, but when working with continuations, i.e. imperatively, or with Kiselyov's freer monads (which are inspired by continuations) you don't have to.

Re: A difference between Haskell and Common Lisp

#186
post #171

Earlier quoted context omitted.

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.

Significant modification is something different than complete rewrite.

The Lisp languages share a common core language, code, ideas, literature, community.

Derived languages like Logo, Dylan, Clojure, Racket, ... have their own core language, their own code, their own ideas, their own literature and their own community.

Re: A difference between Haskell and Common Lisp

#187
post #185
post #183

Earlier quoted context omitted.

> foo(); bar(); The whole point of composition is that foo(bar()) is foo(x) where x = bar(), which means that you can understand the combined program by understanding foo and bar independently. If you can't represent the result of bar() as a value x then that breaks down, in which case I don't see what this whole project is about? I mean being able to implicitly pass down a handler is cool, and there's some value in…

> which means that you can understand the combined program by understanding foo and bar independently. If you can't represent the result of bar() as a value x then that breaks down Can you not understand this program by understanding both statements separately? print("hi") ; print("there") Why is that more understandable if the result of print is a value? (obviously, the result of read() is). In any case, describing…

> Can you not understand this program by understanding both statements separately?

Maybe that one - I/O is a bad example, I don't really see the value of monads for I/O and I don't tend to use an I/O monad in my programs. Programs I can't understand by understanding statements separately are something like:

    openTransaction()
    writeToDatabase(1)
    rollbackTransaction()
or

    val myLog = Buffer()
    myLog :+= "step 1"
    myLog :+= "step 2"
    doSomethingWith(myLog)
and these cases (database transaction, writer) are things I do find it useful to manage with a monad.

> Values are equational, computations are not (even pure ones). It's a nice abstraction to have at your disposal, but enforcing it everywhere is too much.

You need some way to encapsulate/isolate the relevant state of a program partway through running - I think imperative programmers agree that global mutable state is bad. Mostly I see monads as a way to turn global mutable state (whether in the form of global variables, control flow, or something external to the program) into local state, so that you can see which bits of state you need to understand to understand a given line of code.

I think that state has to be equational if we're to have any hope of being able to understand programs - if you can't say whether this time at line 20 the program is in the same state it was last time at line 20, or a different state from last time, how can you even begin to debug?

> You could, obviously, but when working with continuations, i.e. imperatively, or with Kiselyov's freer monads (which are inspired by continuations) you don't have to.

But will those approaches stop me from tripping myself up when effects don't commute? In scalaz-transfigure I do that with the Distributive typeclass - if one effect distributes over another then there will be an instance and you can freely move them past each other, if not then you get a compile error when you try and have to clarify what you want to happen.

Re: A difference between Haskell and Common Lisp

#188
post #186

Earlier quoted context omitted.

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.

Significant modification is something different than complete rewrite. The Lisp languages share a common core language, code, ideas, literature, community. Derived languages like Logo, Dylan, Clojure, Racket, ... have their own core language, their own code, their own ideas, their own literature and their own community.

Yes, but they also share a history and a set of syntax and ideas... Well, except Dylan, syntax-wise. But ultimately this is an argument about terminology. And the majority of people would say that scheme is a lisp. A lisp. Not a language derived from lisp, that is independent. A LISP. in the LISP family, because that is a thing. And if you want proof that that is what most people think, you need only check Wikipedia: https://en.wikipedia.org/wiki/Category:Lisp_programming_lang...

Re: A difference between Haskell and Common Lisp

#189

Earlier quoted context omitted.

... 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.

Why would you want to do that? I'm kind of curious. I mean, I'm sure you have an application, I just can't think of one. Blub effect, I guess.

Re: A difference between Haskell and Common Lisp

#190

Earlier quoted context omitted.

unix commands may not be the pinnacle of minimalism, but the most important thing UNIX taught is about composability. Yes, I did recognize the irony of the fact that the common lisp example was much more like a unix command line than the haskell example, but if you're talking about composing (relatively) small commands, unix is still a pretty good comparison.

I can't find anything to support that in the IEEE POSIX ISO spec from the Austin WG, but the GNU toolset definitely supports your assertion. I think it's important to recognize that POSIX is not SCO UNIX (UNIX(tm) is effectively a trademark and nothing more at this point) which is not GNU/Linux which is not LSB(Linux Standard Base). POSIX itself is a huge PCB of bodge-wires due to legacy compliance. On the upside for…

The bell guys are so much with me that they created plan9, the system I'd rather be running. It's very elegant, but garnered no support, and it's following is INSANE, in that they are incredibly strict in their interpretation of the UNIX way, and think that anything that isn't is awful. I like composability and small systems as much as the next man, but sometimes, you just want LISP.
Post reply on HN