lazy+impure language - General

Welcome to the Functional Programming Zulip Chat Archive. You can join the chat here.

Chris Wendt

Are there any lazy impure languages? What consequences would lack of purity have on a language like Haskell? A couple come to mind:

  • The order of effectful operations would be undefined
  • You wouldn't be able to distinguish between an effectful operation returning a value and the value itself (oof, that sounds quite limiting)
Nadrieril

If the language is too lazy, entire effects might be dropped if you're not very careful. Typically, modifying a mutable reference returns (), so you'd need to evaluate the () for the effect to happen

Chris Wendt

Could that be solved with some language construct that says to "pass by value" rather than "pass the effectful value"?

  • print (readFile "a.txt") passes the effectful operation returning a value
  • print *(readFile "a.txt") passes the value itself
Nadrieril

I'm not sure I see the difference: in both cases, when trying to evaluate the argument to print you'd evaluate the effect of readFile

Nadrieril

Unless you mean something different

Chris Wendt

I think it would be different if you called printtwice (appendFile "a.txt" "blah"\n) (edit: changed to appendFile)

Nadrieril

An alternative would be that ; ensures that the effects all get evaluated when you evaluate the whole expression, which feels closer to what Haskell does. Not sure if WD want that

Nadrieril

Oh I see, so call by name vs call by need

Nadrieril

I definitely imagined that, like in Haskell, when you evaluate an expression its effect gets run and afterward it doesn't and the value is cached

Nadrieril

Essentially my mental model is more or less what we currently get with Haskell+unsafePerformIO. I wonder what other possibilities could be sensible

Chris Wendt

(technicality) I believe unsafePerformIO is not quite call-by-need because the action might be run multiple times, not 100% sure about that, though

Nadrieril

Ah yeah I heard that too. But you get the idea

Chris Wendt

Hypothetical strategy:

  • Call-by-name print (readFile "a.txt") (runs effect n times, default)
  • Call-by-need print *(readFile "a.txt") (runs effect once)

Perhaps call-by-need is more frequently desired, and if so then that could be the default instead

Hazem

This might be relevant (I didn't read it)
https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.35.1663
and
https://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.47.5271

[edit: removed duplicate link]

CiteSeerX - Document Details (Isaac Councill, Lee Giles, Pradeep Teregowda): In this paper we argue for the importance of lazy state, that is, sequences of imperative (destructive) actions in which the actions are delayed until their results are required. This enables state-based computations to take advantage of the control power of lazy evaluation. We provide some examples of its use, and describe an implementation within Glasgow Haskell. 1 Introduction There has long been a tension between functional programming languages and their more traditional imperative counterparts. On the one hand, functional languages are commonly more expressive and easier to reason about than imperative languages, but on the other hand, certain algorithms and interactions seem to rely fundamentally on state-based computation. It is clearly worth attempting to combine the strengths of each. Some languages like Scheme and ML have incorporated imperative actions as side effects. This approach only makes sense in a call-by-value language where the order of evaluation is statically de...
Chris Wendt

Paraphrasing from the first:

Nondeterministic order of evaluation of side-effecting expression rules out the general use of lazy evaluation in an imperative language.

I'm actually curious if this wouldn't be so bad if there were a way to explicitly sequence evaluation (i.e. something akin to Haskell's do notation)

Chris Wendt

Asking the Twittersphere https://twitter.com/ChrisMWendt/status/1264417442421329920

Hazem

Consider asking in the type theory channel on FP Slack

Hazem

@Chris Wendt the reply to your tweet reminded me of #lang lazy in Racket:
https://docs.racket-lang.org/lazy/index.html

I'm actually curious if this wouldn't be so bad if there were a way to explicitly sequence evaluation (i.e. something akin to Haskell's do notation)

like this?

Strict functionality is provided as-is: begin, I/O, mutation, parameterization, etc. To have your code make sense, you should chain side effects in begins, which will sequence things properly. (Note: This is similar to threading monads through your code—only use begin where order matters.)

Chris Wendt

Lazy Racket sounds like exactly what I'm looking for! I'll check it out

Chris Wendt

The second paper was more relevant, but I think it's outdated because unsafeInterleaveIO exists now and achieves the same outcome much more easily (i.e. make effects lazy)

Chris Wendt

Lazy Racket is indeed lazy+impure, thanks :grinning: I wonder what influence laziness has on its usefulness/popularity compared to strict Racket, and whether or not the addition of purity can explain the difference in popularity compared to Haskell. (strict Racket is ~1000x more popular based on GitHub code search)

Hazem

In terms of popularity, I suspect a lot of #lang Racket(strict Racket) users use it because it's the standard version of Racket, and because many of them are introduced to Racket via textbooks like HtdP https://htdp.org/