Mutable variables in FQL

Is there a reason FQL doesn’t let you mutate variables? This would be a very useful feature.

You can shadow variables? What is the usecase for wanting mutability?

You can… if you really want to… provide the same variable multiple times to a Let.

And shadowing/scoping is indeed SUPER useful in building deep hierarchical queries. This is just using the same variable in a Let inside the previous Let.

I’m writing a library that compiles TypeScript to FQL, and it would be a lot more straight-forward if variables could be mutated. Perhaps the best solution would be a variant of q.Let that shadows a variable in the current closure, instead of creating a new closure.

q.Do(
  q.Let({ foo: 1 }),
  q.Var('foo')
)
=> 1

Are you trying to compile any valid typescript (javascript) statement to FQL? How are you handling loops and switch statements?

I ask because it’s not straight forward for all cases to do by hand. I don’t know if you’ll find a one-size it’s all solution — so you’ll have workarounds or bounds around what parts of javascript are compatible. If you’re doing that, require typescript to only declare with const, which is really not a frightening requirement. FQL is functional in nature, so many imperative algorithms are either better suited to or have to be performed in more functional way. E.g use Map and Reduce, rather than loops.

In any case if you are compiling, then the resulting variable names don’t need to match. You could make the output provide a different name each time a variable is mutated. Or if you are intent on reusing the same variable more than once, use the array version of Let bindings that I already shared, which shows how to reuse the same variable name multiple times in the same Let function.

Probably just a subset will be supported, though it would be cool if FQL eventually could support a 1-to-1 mapping. Loops will likely be limited to Array methods like .forEach

Also, my compiler needs to track when documents are updated (for pub-sub purposes). I was hoping to do that by updating a local object and returning it from the query. It seems that will be tricky (maybe impossible?) without a way to mutate (without creating a new closure) the variable holding which documents have been updated.

I know Fauna recently introduced document streaming, but I’m thinking having a compiler track the updates would be a cheaper approach (though not 100% sure on that). Also, there’s no “new document” streaming, right?

New document stream would be “collection streams” not out but said to be coming soon.

What your talking about sounds fascinating. What do you mean by this?

I mean something like this (using my suggested q.Let feature):

q.Do(
  q.Let({ changes: [] }),
  // ...
  q.Update(ref, { data }),
  q.Let({ changes: q.Append(q.Var('changes'), [ref, data]) }),
  // ...
  q.Var('changes')
)

Are you completing a list a tasks that not homogeneous? I can certainly see a desire to have more flexibility with trying to do different kinds of operations at the same time.

Your example only shows one Update operation, but the more the data/operations are similar the more straight forward operating on them in an “FP” way.

If your tasks are more complicated, then sorry, I realize I’m answering a different question and I’m not helping much. BUT, if you can work more with homogeneous data, then the more you can rely on Map and Reduce.

Map(
  Paginate(Match(Index('might_need_update'), 'by_value')),
  (ref) => Let(
    { 
      needsUpdate: Select(['data', 'needs_updated'], Get(ref)),
      result: If(Var('needsUpdate'), /* do something */, null)
    },
    [ref, Var('result')]]
  )
)

// Or if not performing update on every item, use Reduce

Reduce(
  (changes, ref) => Let(
    { 
      needsUpdate: Select(['data', 'needs_updated'], Get(ref)),
      result: If(Var('needsUpdate'), /* do something */, null)
    },
    If(
      Var('needsUpdate'),
      Append(changes, [[ref, Var('result')]]),
      changes
    )
  )
  [],
  Paginate(Match(Index('might_need_update'), 'by_value'))
)