mustache / spec

The Mustache spec.
MIT License
361 stars 71 forks source link

Add lambdas for nix #189

Open valodzka opened 4 months ago

valodzka commented 4 months ago

Implementent lambdas for nix. Interpolation - Multiple Calls skipped because nix is pure functional language so lambda cannot have state / return different result.

valodzka commented 4 months ago

How does nix implement pseudorandom number generators?

From what I found used approach is to generate sequence.

but are you sure there is no way to implement Interpolation - Multiple Calls?

I'm not nix expert so I asked for help, may be someone more knowledgeable than me know the solution.

valodzka commented 4 months ago

@jgonggrijp Based on the response to my post on the Nix Discourse forum and also on this similar question

would require something akin to a monadic context But that’s not a thing.

It seems that implementing something similar to a monadic context would be necessary. However, such a concept doesn't currently exist in Nix.

jgonggrijp commented 4 months ago

@valodzka Thanks for sharing those links. The next reply to that similar question (comment by AndersonTorres) lists several links with instructions on how you can implement monads yourself. I visited the first one, which illustrated the concept for Scheme. Looking at the Scheme code, I see nothing that is specific to Scheme or that requires variable modifications or other special facilities. I think you could also implement them in C or Nix.

Concluding, I still think it should be possible to implement Interpolation - Multiple Calls in Nix. Please let me know whether you want to have a go at it, otherwise I will just merge it as-is.

jgonggrijp commented 4 months ago

By the way, Wikipedia also has code examples:

https://en.wikipedia.org/wiki/Monad_(functional_programming)

valodzka commented 4 months ago

@jgonggrijp after more discussion it looks like it can be implemented but only with a kind of breaking interface. It would require that:

  1. library user provide lambda that returns special structure, not plain string / primitive
  2. library handle such structures in special way (use result for rendering, not returned value itself and pass returned value to parameter to lamda call)

So lamda will look like this:

s: 
  let 
     state = if s == null then { result = 0; nextState = 1; } else s; 
  in 
    { result = state.nextState; newState = state.nextState + 1; }

I am not sure if this is worth it, but if you think it's necessary I can update the pull request.

jgonggrijp commented 4 months ago

@valodzka That comment by rhendric is gold, please pass them my appreciation.

You are right that any stateful lambda will necessarily require interface changes in your Mustache implementation, if your implementation is to pass that test. It is entirely up to you whether you want to invest that effort.

That being said, you can write some monadic form of the lambda for this test, without making any changes anywhere else. This will not break the spec itself, and the only consequence for your implementation is that it will fail this particular test. Which is exactly as it should be; the spec expects implementations to support stateful lambdas, and yours does not (at least not yet).

In other words, please write a monadic form, but do not feel obliged to pass it. I recommend a lambda form that assumes a more generic state monad, so that you can easily add support for other types of stateful behavior later. Something like this:

v: state @ { count ? 1 }:
    let
        next = count + 1;
    in
        { result = count; state = state // { count = next } };

If you later decide to actually support stateful lambdas, the part of your implementation that interpolates them can detect whether a lambda returned a bare value or a monad, and wrap it in a monad with unit/return in the first case. In this way, you and your users won't need to rewrite stateless lambdas. The corresponding unit/return and bind/>>= implementations would look something like this:

unit = value: state: { result = value; state = state };
bind = previous: func: state:
    let
        intermediate = previous state;
    in
        func intermediate.result intermediate.state;
RaitoBezarius commented 4 months ago

I didn't understand you wanted to do Mustache stuff in Nix.

Here's a lead on how to perform "monadic-style" stuff in Nix, though I highly don't recommend relying on this, this is not supposed to be public interface and may be removed in a future version of Nix:

let next = rec { state = 0; __functor = old: _: old // { state = old.state + 1; }; }; in next {} {} {} {} {}   

__functor enable an attribute set to act as a function.

jgonggrijp commented 4 months ago

@RaitoBezarius Thanks for chiming in. Assuming that __functor disappears, would you recommend something more like what I suggested, or would you still recommend a different approach?