Live data from Hacker News

A difference between Haskell and Common Lisp

chrisdone.com

61–70 of 203 posts

Re: A difference between Haskell and Common Lisp

#61
post #30

Actually Common Lisp supports a gazillion of different programming styles. The version with small functions, similar to the Haskell version: (subseq (remove-if (complement #'numberp) (butlast list 3)) 0 5) In above Common Lisp code, we use four different functions which do one task: * subseq sequence start &optional end => subsequence * remove-if test sequence => result-sequence * complement function => complement-fu…

> Check out the UNIX man for tail, grep, ... to see how strange above quote about 'unix philosophy' is.

The "UNIX philosophy" is good, it's just that UNIX doesn't really follow it.

The C and shell programs that make up UNIX commands are actually UNIX's own little programming language, complete with ad-hoc data types.

Look at `cat` for example. It might as well be `str + str...`. Or look at `grep`, it's basically `filter` or `reduce` at heart. But it has to do this per-line, and it has no metadata about the line, it literally just works on string contents. The `head` and `tail` commands aren't much different than `take` and `drop`. Heck there's even a `sort` command.

That's why shells that actually use a programming language (e.g. eshell) don't seem so crazy to me. To be honest, I use eshell way more than I use bash, and half the functions I use there are written in lisp rather than bash or C.

Re: A difference between Haskell and Common Lisp

#62
post #52

Common Lisp has these kitchen-sink functions and macros because it was a standard developed by a committee whose goal was to incorporate several popular implementations of Lisp that each had several decades worth of of cruft. Lisp was big enough business at the time that having several mutually incompatible versions of Lisp was making knowledge sharing and business difficult. And having a standard was important for m…

> standard developed by a committee You know that Haskell was developed by a committee and was designed to bundle the various research streams on lazy/statically typed/ purely functional programming languages? From the Haskell history: > ... to discuss an unfortunate situation in the functional programming community: there had come into being more than a dozen non-strict, purely functional programming languages, all…

As always, thank you for the clarification. My 6 month old is teething and waking me every 40 minutes starting around 3am. I'm mostly recalling from hazy, caffeine fueled memory. I like your posts -- so informative.

I admit to knowing little about Haskell's history. I had assumed it was an outgrowth of ML -- itself having an interesting history intertwined with the LCF theorem-proving system. Thanks for the clarification.

>> it was a standard developed by a committee

> That's basically nonsense.

In my much-abridged comment, yes I understand it could sound that way. I was referring to the entire process but mainly the ANSI committee. I was also under the, apparently mistaken impression, that there was more than one Lisp involved.

  In April 1981, after a DARPA-sponsored meeting concerning the splintered Lisp community, Symbolics, the SPICE project, the NIL project, and the S-1 Lisp project joined together to define Common Lisp. Initially spearheaded by White and Gabriel, the driving force behind this grassroots effort was provided by Fahlman, Daniel Weinreb, David Moon, Steele, and Gabriel. Common Lisp was designed as a description of a family of languages. The primary influences on Common Lisp were Lisp Machine Lisp, MacLisp, NIL, S-1 Lisp, Spice Lisp, and Scheme. Common Lisp: The Language is a description of that design. Its semantics were intentionally underspecified in places where it was felt that a tight specification would overly constrain Common Lisp esearch and use.
Quite right.. it's all there in ye old Hyperspec. Thank you for pointing that out.

Cheers.

Re: A difference between Haskell and Common Lisp

#63
post #14

Haskell has strong typing and lazy evaluation, which makes it easy for functions to take only one argument at a time. Although a function could take a tuple parameter, it's usually rewritten to take each component of the tuple as a separate parameter, which makes the strong typing and built-in currying simple, higher structures like monads possible, and a syntax to suit this style. Lisp functions and macros OTOH must…

> Lisp functions and macros OTOH must be variadic to enable the homoiconicity of the language.

That makes no sense at all. Homoiconicty is a syntactic concept, variadicity is a semantic concept. They have nothing to do with each other.

The feature that enables Haskell's programming style is laziness, not currying. Lisp can be trivially curried, but it cannot be trivially made lazy, so idioms like "take 5 . filter p . drop 3" -- i.e. (drop (filter p (take 5 arg)) 3) -- are much harder to compile efficiently.

Re: A difference between Haskell and Common Lisp

#64

You have to be clear about which Lisp. Clojure and Scheme are Lisps, and their focus is very much towards simplicity and the use of combinators. The complex, do-it-all-in-one-huge macro is a Common Lispism, not a Lispism. Secondly, Clojure, like Haskell, is focused on sequence abstractions, not lists. Sequences can include collections, streams, observables, sockets and many other kinds of process that can be modelled…

> Clojure and Scheme are Lisps

Basically new languages with strong Lisp influence.

> very much towards simplicity

Scheme maybe until the early 80s. Later it grew to Common Lisp size and beyond. See Scheme R6RS which is a complex language with tons of features and still underspecified.

> The complex, do-it-all-in-one-huge macro is a Common Lispism, not a Lispism.

Not really. Macros appeared in Lisp in the early 60s and many Lisp dialects have used it in complex ways. For example the 'famous' LOOP macro of Common Lisp actually was developed in Interlisp in the early 70s (as a part of 'Conversational Lisp' / CLISP), redesigned in Maclisp/Zetalisp and then brought into Common Lisp. The original Common Lisp in 1984 did not even have that macro in the language description, it was standardized several years later - after a search for a better alternative failed.

Re: A difference between Haskell and Common Lisp

#65
post #59
post #51

Earlier quoted context omitted.

> higher structures like monads possible What is it about the features that you mentioned that makes monads possible? Lisp (or at lease Scheme and Clojure, which I'm familiar with) make monads trivially possible -- just as they are in Haskell. They're not as useful because those languages have other mechanisms that make monads largely unnecessary, but they're no less easy to express.

Well you can't have implicitly resolved typeclasses without a static type system. You could pass around explicit dictionaries with all your values or some such, but most of the value of explicitly sequencing relatively minor effects is only there if you have an extremely low-overhead way of doing so, and a system that can verify the correctness of that sequencing at compile time.

> Well you can't have implicitly resolved typeclasses without a static type system.

Yes, but that wasn't the feature mentioned in the GP comment. He mentioned lazy evaluation, with currying, static typing and monads as a consequence. But if monads are just a consequence of the type system, then I understand what he meant.

> but most of the value of explicitly sequencing relatively minor effects is only there if you have an extremely low-overhead way of doing so, and a system that can verify the correctness of that sequencing at compile time.

Well, extremely low-overhead of just about anything is certainly achievable with modern JITs[1].

> and a system that can verify the correctness of that sequencing at compile time.

Why is that necessary? You might as well presuppose the necessity of types :) or, alternatively, say that you need a type system to verify that your monads are truly monads (i.e. obey the monad laws) at compile time, something Haskell doesn't do, either.

[1]: https://twitter.com/ChrisGSeaton/status/619885182104043520

Re: A difference between Haskell and Common Lisp

#66
And us schemers would express it like this:

(cut take 5 (filter (drop 3 )))

or, without the cut macro:

(λ (pred list) (take 5 (filter pred (drop 3 list))))

And yes, in most schemes, you can use λ as a synonym for lambda. Sometimes you have to define it first, though. Anyways, that looks a lot like the Haskell to you, doesn't it? It doesn't have the laziness, but other than that...

Oh! Oh! I almost forgot! you can also use the thrush combinator, if you use the clojurian package:

(λ (list pred) (->> list (drop 3) (filter pred) (take 5)))

And yes, I think this is all the ways you can do this in scheme. CHICKEN specifically. With some various macros packages.

Oh, wait! I forgot we have function composition, too, but you'd have to use lambda or cut constantly to make the thing work because we don't have currying. So these are all the ELEGANT ways to make this in chicken scheme. With the cut SRFI. Or clojurian. And srfi-1, which is basically standard. and it's better than the examples given, because pred and list aren't pre-specified.

Re: A difference between Haskell and Common Lisp

#67
post #52

Common Lisp has these kitchen-sink functions and macros because it was a standard developed by a committee whose goal was to incorporate several popular implementations of Lisp that each had several decades worth of of cruft. Lisp was big enough business at the time that having several mutually incompatible versions of Lisp was making knowledge sharing and business difficult. And having a standard was important for m…

> standard developed by a committee You know that Haskell was developed by a committee and was designed to bundle the various research streams on lazy/statically typed/ purely functional programming languages? From the Haskell history: > ... to discuss an unfortunate situation in the functional programming community: there had come into being more than a dozen non-strict, purely functional programming languages, all…

> > it was a standard developed by a committee > That's basically nonsense. > The core of Common Lisp was designed by mostly five people

How is that different from what the OP said? It certainly seems fair to me to call a group of five people "a committee."

Re: A difference between Haskell and Common Lisp

#68
post #30

Actually Common Lisp supports a gazillion of different programming styles. The version with small functions, similar to the Haskell version: (subseq (remove-if (complement #'numberp) (butlast list 3)) 0 5) In above Common Lisp code, we use four different functions which do one task: * subseq sequence start &optional end => subsequence * remove-if test sequence => result-sequence * complement function => complement-fu…

> Check out the UNIX man for tail, grep, ... to see how strange above quote about 'unix philosophy' is. The "UNIX philosophy" is good, it's just that UNIX doesn't really follow it. The C and shell programs that make up UNIX commands are actually UNIX's own little programming language , complete with ad-hoc data types. Look at `cat` for example. It might as well be `str + str...`. Or look at `grep`, it's basically `fi…

> The "UNIX philosophy" is good, it's just that UNIX doesn't really follow it.

It never did, beyond some examples in beginner books.

> Look at `cat` for example. It might as well be `str + str...`. Or look at `grep`, it's basically `filter` or `reduce` at heart.

Then look at the options of `cat`. On my Linux system cat has a -n option, which numbers the output lines. If it were following the 'UNIX philosophy', this option would not exist.

Re: A difference between Haskell and Common Lisp

#69
post #51
post #14

Haskell has strong typing and lazy evaluation, which makes it easy for functions to take only one argument at a time. Although a function could take a tuple parameter, it's usually rewritten to take each component of the tuple as a separate parameter, which makes the strong typing and built-in currying simple, higher structures like monads possible, and a syntax to suit this style. Lisp functions and macros OTOH must…

> higher structures like monads possible What is it about the features that you mentioned that makes monads possible? Lisp (or at lease Scheme and Clojure, which I'm familiar with) make monads trivially possible -- just as they are in Haskell. They're not as useful because those languages have other mechanisms that make monads largely unnecessary, but they're no less easy to express.

(1) Monadic programming gets really hard to understand if you don't have a strong type system correcting you when you screw up the difference between `x`, `m x`, and `m (m x)`. The tendency in weak type systems is to autopromote `x` to `m x` with `return`, but this can make it impossible to see if you need to deconstruct `m (m x)` to `m x` with `join` in any given application, unless you just auto-flatten everything, which sometimes (especially in the list monad!) is not what you want.

(2) Monadic programming gets hard to understand if you can't cleanly split out functions into having just one argument and one output.

Translation for people who don't know what a "monad" is: occasionally there are type-adjectives (I'll use the example "blue") where there is a canonical way to make any object into a blue object (called `return`), and a canonical way to make a blue blue object into a blue object (called `join`), and a canonical way to take some function from objects to objects and turn it into a function from blue objects to blue objects (called `fmap`).

One example is the adjective "either an X or a ____", which is written in Haskell as `Either x`. The canonical way to take a Y and `return` it into "either an X or a Y" is to say "it's a Y -- the type on the right." In Haskell we'd say that `data Either x y = Left x | Right y` and that this `return` function is `return y = Right y`.

Similarly the canonical way to map a function `y -> z` over such an `Either x y` to get an `Either x z` is "if the value is an X don't do anything, otherwise do the function to the value and collect the end result." This is written `fmap zy exy = case exy of Left x -> Left x; Right y -> Right (zy y)`. Finally, the canonical way to take an "either an X or an (either an X or a Y)" into "either an X or a Y" is just: if you've got an X, it's an X; if you've got a Y, it's a Y:

    join ex_exy = case ex_exy of 
      Left x -> Left x
      Right exy -> case exy of 
        Left x -> Left x
        Right y -> Right y
So it's adjectives which are (a) universal [we can apply them to anything], (b) collapsible [we can take an `m (m x)` and turn it into an `m x` and (c) mappable [we can take an `x -> y` and apply it to an `m x` to make an `m y`].

Haskell became obsessed with them because this structure describes computer programs really well. Suppose you build in a data type for "a program which produces a...". Then the basic "program composition" operator looks like this:

First, take two inputs: (1) a program which produces an X, (2) a function which takes X and produces a program which produces a Y. Then, yield a program which will (when run) produce a Y by first running program (1), then using the X generated as an input to the function (2) to compute a program which produces a Y (3), then runs (3) to produce the y.

The type signature here is `(IO x, x -> IO y) -> IO y`. This curious function is called `bind` and it can be more easily thought of as a combination of two separate steps: `fmap` the `x -> IO y` on the `IO x` to produce an `IO (IO y)`, then `join` the programs to produce an `IO y`. "A program which produces a ..." is thus a special case of a monad, an adjective which supports these three operations (`return` is "produce a program which does nothing and yields this Haskell value").

And you care about all of this because being all indirect -like about computer programs allows you to metaprogram up an impure program in a pure language, which is how Haskell does I/O.

So the translation of the above points is that (1) this whole process gets confusing if you don't know the difference between a wagon, a blue wagon, and a blue blue wagon at compile time, and the tendency in a Lisp is to say "I saw a wagon, I wanted a blue something, so let's just quietly use the `(paint-blue wagon)` subroutine to keep going," and "I saw a blue blue wagon, I wanted a blue something, so I am happy." You can force things to always use some `(coalesce-blue-paint ___)` function to always flatten blue-blue Xs into blue Xs, but this is usually counterintuitive. (2) You can't even clearly articulate what `fmap` does unless you can cleanly say "I have a function from wagons to wheels, and I want the corresponding function from blue wagons to blue wheels." If your functions are taking a bunch of extra parameters, like a wheel-index of "front-left, front-right, back-left, back-right" then it gets hard to say "here's the wagon input that I want to become a blue wagon input, please leave my wheel indexes alone!"

Re: A difference between Haskell and Common Lisp

#70

Earlier quoted context omitted.

Lisp is far from any lambda calculus, even the untyped one. Lambda calculi don't have variadic functions, or any means to inspect their own syntax. Lambda abstraction is called abstraction for a reason - you can't inspect the expression inside. Lisp is really its very own kind of thing, which can be both very interesting (if you care about extensibility) and very irritating (if you care about abstraction). Racket, a…

Yeah... I tried to be soft in use of tend to , in that it's not exactly Lambda Calculus. You're entirely right.

If you're interested in exploring the relationship of Lisp and the lambda calculus you might find this interesting:

http://www.flownet.com/ron/lc.html

https://www.youtube.com/watch?v=8qC1iZN5ozw

Post reply on HN