Live data from Hacker News

A difference between Haskell and Common Lisp

chrisdone.com

151–160 of 203 posts

Re: A difference between Haskell and Common Lisp

#151

Earlier quoted context omitted.

Wrong. You can build an entire Haskell implementation as a thin, transparent layer on top of Lisp and then do anything Haskell can do.

My post literally says "you can build a Haskell interpreter which interacts with Lisp".

And this is irrelevant. Interpreters suck and are not interesting at all.

I am talking exactly about transforming Lisp into Haskell. Fully or gradually, it does not matter.

Re: A difference between Haskell and Common Lisp

#152
post #116

Earlier quoted context omitted.

Nobody told Dr. Tarver[1]. He built a series of lisps with tight integration with the underlying Common Lisp platform featuring a strong typing system rooted in the propositions of Sequent Calculus[2][3]. Type delcarations are only on top level forms with all locals inferred. There seems to be work to improve this in Shen Professiona's new compiler. Shen offers tight integration with the underlying platform (although…

I guess I wasn't clear in my post. Usually when people say "you can do X in Lisp", they mean that you can integrate a feature into the language. Don't have list comprehensions? Write a macro. Don't have "thrush" syntax? Write a macro. Don't have lazy evaluation? Write a... err. You can't write a macro[1]. You can't write something which completely changes the entire calling semantics of the language. What you can do…

No, you can easily write a macro to implement lazy semantics. You can implement an entire STG as a macro. Same with type inference.

Writing compilers is exactly what macros are for.

Re: A difference between Haskell and Common Lisp

#153

Earlier quoted context omitted.

Got to this late, but other commenters have already pointed out why this is wrong. While this is unfounded (I have no sources), LISP is quite possibly the most flexible higher level language ever invented, I would caution against betting against it in the future. Saying that it's the most flexible higher language ever invented might not actually amount to much -- I'm sure there's actually a small subset of features/p…

Your argument doesn't add anything really. You can make anything in any reasonable language. When people say "you can add X to Common Lisp" or "you can make X in Common Lisp", they usually mean that you can integrate such a feature in Lisp. If you mean "you can write a compiler in Lisp", then, obviously. But you can do that in C++ too. Not a single reply has demonstrated that you can effectively and reasonably add th…

You cannot write a compiler that integrates into your language in C++ or Haskell. And it is trivial in Lisp, just wrap a compiler into a macro.

For example, my typical workflow heavily depends on Prolog blended into Lisp. I also use an ML implementation, also blended into Lisp seamlessly, and a subset of a Haskell-like language (lazy and strictly typed), also tightly integrated with Lisp, to a degree that it can access Lisp or ML local variables defined around the Haskell code block.

You cannot do this in any language which does not have proper macros.

Re: A difference between Haskell and Common Lisp

#154
post #150
post #133

Earlier quoted context omitted.

But effects do need to satisfy the laws if the "natural" way of writing them is to make sense, so I'd rather that were enforced (compare the confusing results one gets when using Set in a scala for/yield, because it doesn't obey the monad laws). Unless you're restricting the "interpreters" such that the implementation of a given action is necessarily monadic? (Have you seen the "freer monad, more extensible effects"…

> But effects do need to satisfy the laws if the "natural" way of writing them is to make sense, so I'd rather that were enforced (compare the confusing results one gets when using Set in a scala for/yield, because it doesn't obey the monad laws). Unless you're restricting the "interpreters" such that the implementation of a given action is necessarily monadic? I haven't thought too much about it, but I believe that…

> I haven't thought too much about it, but I believe that if you structure effects as continuations, then they are monadic by construction (though I may well be wrong) because the continuations themselves are monadic. But I got interested in continuations because I don't like Haskell's purity, and I wondered if there is an imperative construct with the same expressive power as the PFP monad, and it turns out that not only that there is one, but that it composes much better than monads.

I don't see it as imperative - if anything passing continuations around is more functional. They were originally a scheme thing, no?

How does the stack aspect work? It's a continuation style so you never return, so if you keep the stack frames around it'll overflow very quickly, right? The usual solution to that is tail call elimination, but then you don't have even the immediate stack trace. Are you doing some kind of dynamic "keep the last x stack frames"? (I can imagine that being useful for a trampolined monad style too).

I think that's the really interesting part. As you say the styles are equivalent (even clearer when one's using something like scalaz-stream), so it's a question of which is easier to read/understand. I do think the monadic style as implemented in scala has a big runtime-inspectability problem (one idea I keep toying with is a free monad style where you represent every step with a named object or class, purely so that debuggers handle them correctly). I find call/cc style incomprehensible at the code-reading level - I'd rather have the control flow captured in a value I can inspect in a debugger than as what are effectively gotos in the code text. Maybe if you're more used to macro-based programming then it makes more sense.

Re: A difference between Haskell and Common Lisp

#155
post #154
post #150

Earlier quoted context omitted.

> But effects do need to satisfy the laws if the "natural" way of writing them is to make sense, so I'd rather that were enforced (compare the confusing results one gets when using Set in a scala for/yield, because it doesn't obey the monad laws). Unless you're restricting the "interpreters" such that the implementation of a given action is necessarily monadic? I haven't thought too much about it, but I believe that…

> I haven't thought too much about it, but I believe that if you structure effects as continuations, then they are monadic by construction (though I may well be wrong) because the continuations themselves are monadic. But I got interested in continuations because I don't like Haskell's purity, and I wondered if there is an imperative construct with the same expressive power as the PFP monad, and it turns out that not…

> They were originally a scheme thing, no?

Sure, but scheme is an imperative-functional language (as is OCaml).

> It's a continuation style so you never return

No, I'm talking about delimited continuations. No need for TCO. The continuation contains only a portion of the stack (off the top).

> As you say the styles are equivalent

They're only equivalent in the sense that they have the same expressive power. It's been proven that continuations can be implemented with monads and that any monad can be expressed as a continuation, but continuations really do compose better (unless you use Kiselyov's effect handlers).

> I find call/cc style incomprehensible at the code-reading level

Right, but we're talking about delimited continuations, i.e shift/reset, not call/cc. You can simply treat them as threads. They're not at all like gotos (you can read my blog post). My claim is that they fit much better with imperative (including functional-imperative) languages than monads.

Re: A difference between Haskell and Common Lisp

#156

I'm not entirely sure that this is due to philosophical differences. The fact that Haskell is lazily evaluated makes writing functions that do only one thing much easier, since there is no performance hit for writing code like: take 5 . filter (not . p) . drop 3 In a strictly evaluated language. This would involve iterating over the list three different times. (Kind of not really, since take 5 isn't going to be that…

> there is no performance hit for writing code like:

> take 5 . filter (not . p) . drop 3

That doesn't seem to be the case in my testing. Writing this out as an explicitly recursive function sped things up 3x on GHC -O3. (Dropping 10^8, taking 10^7.)

FWIW, I also tested on Rust, Python (PyPy3) and Java (OpenJDK) with iterators, iterators and streams, respectively. Python and Java were about as fast as the manually recursive version, and Rust was two orders of magnitude faster than that. And half of the time in Java was spent boxing, because Java can't reify types away like Haskell.

It's true these are all using opt-in laziness, unlike Haskell's lazy-by-default, so it's not a fair comparison. But I also see little reason to believe that there's "no performance hit", given that that's exactly what I saw.

Re: A difference between Haskell and Common Lisp

#157
post #155
post #154

Earlier quoted context omitted.

> I haven't thought too much about it, but I believe that if you structure effects as continuations, then they are monadic by construction (though I may well be wrong) because the continuations themselves are monadic. But I got interested in continuations because I don't like Haskell's purity, and I wondered if there is an imperative construct with the same expressive power as the PFP monad, and it turns out that not…

> They were originally a scheme thing, no? Sure, but scheme is an imperative-functional language (as is OCaml). > It's a continuation style so you never return No, I'm talking about delimited continuations. No need for TCO. The continuation contains only a portion of the stack (off the top). > As you say the styles are equivalent They're only equivalent in the sense that they have the same expressive power. It's been…

> No, I'm talking about delimited continuations. No need for TCO. The continuation contains only a portion of the stack (off the top).

I don't think that resolves it. It's quite normal to want to chain together a few thousand or million monadic operations even within a single method (e.g. making a number of network calls, writing lines if you're managing I/O monadically). If doing that results in a stack overflow then that severely limits the utility of this approach.

> Right, but we're talking about delimited continuations, i.e shift/reset, not call/cc. You can simply treat them as threads. They're not at all like gotos (you can read my blog post). My claim is that they fit much better with imperative (including functional-imperative) languages than monads.

I did read it; I'm sorry if I've got the terminology wrong. I found e.g. the "class Foo" example really hard to follow the control flow; in that sense it seemed goto-like to me. I find threads really hard to reason about, so "it's as easy as threads" doesn't really appeal.

I've not seen the term "functional-imperative" used that way, and usually the two are contrasted - can you clarify what class of languages you're talking about?

Re: A difference between Haskell and Common Lisp

#158
post #157
post #155

Earlier quoted context omitted.

> They were originally a scheme thing, no? Sure, but scheme is an imperative-functional language (as is OCaml). > It's a continuation style so you never return No, I'm talking about delimited continuations. No need for TCO. The continuation contains only a portion of the stack (off the top). > As you say the styles are equivalent They're only equivalent in the sense that they have the same expressive power. It's been…

> No, I'm talking about delimited continuations. No need for TCO. The continuation contains only a portion of the stack (off the top). I don't think that resolves it. It's quite normal to want to chain together a few thousand or million monadic operations even within a single method (e.g. making a number of network calls, writing lines if you're managing I/O monadically). If doing that results in a stack overflow the…

> If doing that results in a stack overflow then that severely limits the utility of this approach.

There is no stack overflow. A chaining of monads translate to a blocking of the continuation. The familiar imperative sequence of blocking operations is an instance of the continuation approach, and it isn't susceptible to stack overflows.

> I find threads really hard to reason about, so "it's as easy as threads" doesn't really appeal.

Let me make it clearer: it's not "threads", as in multiple threads, but "as easy as threads" as in the simple, familiar blocking thread. There is no concurrency involved. If you can understand:

   a = read()
   print("hello " + a)
you understand how to work with delimited continuations (that code is actually written as a continuation, which blocks on read()); there's nothing more to it. It is simply a formalization of what it means to block a thread, and a mathematical name for what we've always done.

When you block a kernel thread, the OS takes care of all the continuation stuff -- but it does exactly that (the OS does run your single-threaded program in a continuation). Just as the OS mechanics of continuations don't get in the way of your understanding of the above code, language-level implementation of continuations shouldn't either. It is completely transparent to you -- your thread blocks, some handler does something, and then your thread resumes.

> I've not seen the term "functional-imperative" used that way, and usually the two are contrasted - can you clarify what class of languages you're talking about?

Well, it's sort of my made-up term that I use because "functional" doesn't really have a definition (try to come up with a definition that encompasses all of: Lisp, ML, Haskell, and Erlang but none of Java, Ruby, Smalltalk, JS and you'll see why), so I use the term to denote languages which we call functional (whatever exactly that means) which aren't pure, so: the Lisps, MLs, Erlang.

Re: A difference between Haskell and Common Lisp

#159

Earlier quoted context omitted.

That's wrong in just about every respect. Shen is a PLATFORM INDEPENDANT (not clisp based) language with an emphasis upon functionalism, a novel and very powerful type system based on sequent calculus, and OPTIONAL type checking, IF you want it. It is platform independant because it is built upon an incredibly simple lisp that you can build an interpreter for on top of almost any platform, so long as you can guarante…

The point to type checking is protecting abstractions, and it being "optional" reduces its value to zero.

Wrong. Sometimes type checking is uneeded. In addition it can make prototyping easy: Write the code, add the annotations later. Of course, you need some discipline to make it work, but of what good practice isn't that true?

And if you really hate it, just type (tc +) at the start of your code. Magic!

Re: A difference between Haskell and Common Lisp

#160

Earlier quoted context omitted.

That's nonsense. Lisp in the functional style vs. Haskell is two different implementations of what are at their essence the same ideas. However, lisp is multi-paradigm, which complicates the comparison somewhat...

What are the essential features that they share?

They're both primarily based on lambda calculus. Although haskell on typed lambda calculus. They both emphasize purity over mutations. They both are designed to make higher-orderisms idiomatic.

But the point isn't so much the features they share. I'd be the first to admit that Lisp and Haskell are very different. But functional programming in both is very much based on the same ideas. Claiming that they're as different as OO vs imperative is like saying that washing dishes by hand is as different to using a dishwasher as football is to baseball.

Post reply on HN