Lazy - PureScript

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

Mason Mackaman

Does anyone know of some good example uses of the Lazy functor? I'm having a hard time wrapping my head around how it ought to be used.

TheMatten

It's just Unit -> a - so if you've ever used nullary function instead of it's result, you've used Lazy (as in PureScript, it's not real, sharing "laziness" as in Haskell)

Mason Mackaman

I'm familiar with using at thunk to delay computation, but lazy is different. Using the a -> b instance, the two functions in the lazy documentations are

defer :: (Unit -> a -> b) -> a -> b
fix :: ((a -> b) -> a -> b) -> a -> b

I don't understand the point of a Unit -> a -> b? how is that any more lazy than just a -> b? In both cases b gets computed after you give it a.

Mason Mackaman

I'm probably just looking at it completely wrong because honestly, I don't even know what those functions are trying to do.

TheMatten
defer :: (Unit -> a -> b) -> a -> b
defer f = \a -> f unit a

mind position of call to f - it's after applying a, and so defer f is actually less strict than f unit - e.g. in

f unit = g <<< h

f unit a evaluates <<< after applying unit, but

defer f a == (\a -> f unit a) a = (\a -> (g <<< h) unit a) a

only after applying a

TheMatten

I mean, I guess it doesn't matter that often, but there's still a difference, and defer is sort of safeguard that makes sure that all the computation in function is deferred until application of last argument

Mason Mackaman

so we could have

f :: a -> b
f = g <<< h

which evaluates g <<< h once and then does the computations involved in computing g <<< h $ a every time f a is called
vs.

f' :: Unit -> a -> b
f' _ -> g <<< h

f :: a -> b
f = defer f'

which evaluates g <<< h along with computations involved in computing g <<< h $ a every time f a is called?

TheMatten

There's no "once" in PS, only "later" - we're talking about simple functions here (JS functions at runtime), compared to actual laziness in Haskell, which includes sharing - Haskell "thunk" is literally computation that either computes it's value and writes in in place, or returns already computed value

TheMatten

So will always evaluate <<< and function it outputs, it's just that defer can change when the former happens

TheMatten

In Haskell you can do stuff like building doubly linked lists or lazily streaming files - trying to do the same thing with Lazy in PS would result in infinite recursion and duplicated reading on parallel access respectively

Mason Mackaman

Alright so I realized I could just look at the compiled JS and decided to do that since I wasn't getting it. First off, I think the g <<< h example actually was the exact same in both cases (as far as what gets computed when), but I found another simple example where it's not

f :: Int -> Int
f = let x = bigComputation 1 in x

e' :: Unit -> Int -> Int
e' _ = let x = bigComputation 2 in x

e :: Int -> Int
e = defer e'

compiles to

var lazyFn = new Lazy(function (f) {
  return function (x) {
      return f(Data_Unit.unit)(x);
  };
});

// ...

var e$prime = function (v) {
    var x = bigComputation(2);
    return x;
};

var e = Control_Lazy.defer(Control_Lazy.lazyFn)(e$prime);

var f = (function () {
    var x = bigComputation(1);
    return x;
})();

If you look at f you can see what I meant by "once" (my intuition happened to be correct), bigComputation(1) is only executed once (to create f), whereas with e it only gets executed when e is called (but it does so every time)

Mason Mackaman

okay you actually don't even need to use let and it's even simpler. And I think g <<< h just didn't work because of compiler optimization - it did g(h(x)) instead of using compose(g)(h), which would have demonstrated the difference.

TheMatten

Ah, I see what you meant - yeah

TheMatten

I meant "once" as in on multiple access to same reference