concordusapps / inquire.js

Generate advanced query strings.
http://concordusapps.github.io/inquire.js/
MIT License
11 stars 2 forks source link

Support iterating the tree #11

Closed joneshf closed 10 years ago

joneshf commented 11 years ago

Recently ran into a case where I wanted to modify just one predicate. The way inquire is currently implemented would've required string parsing, and I'm not down with that. There should be some way to iterate what is returned, maybe even returning bits of the tree with a find method or something.

joneshf commented 10 years ago

Much of this is going on in the formalize branch. However, I think it's getting too formal. Rather than this thing being a bi-everything, it should just be the singular version of everything. Primarily because it would be next to impossible to support things like filtering in a key-value way, unless we use the monad. I feel this too over the top for something this simple. It should be able to be done with just foldable, or maybe traversable.

So the question to ask is, How do you represent each predicate? For instance, when using map, how should you get the values? I only see two possible ways: an array, or an object. With an array you might get it as [key, op, val] or [op, key, val], with an object you might get it as {key: someKey, val: someVal, op: someOp}.

So, if you wanted to uppercase all the values for some reason:

Array version

I = require \inquire
p = I.and (I.eq \cat \good), (I.ne \dog \bad)
p.map ([op, key, val]) -> [op, key, val.toUpperCase!]

Object version

I = require \inquire
p = I.and (I.eq \cat \good), (I.ne \dog \bad)
p.map ->
  it.val .= toUpperCase!
  it

There are issues with both. With the array style, you've got to remember the order of three things. Which might not seem like much, but I think that's the limit of what I want to care about. With the object style, you've got to work with objects, which in and of itself is less than fun. But it also forces us to expose the internals or an api wrapping the internals in order to work with it.

I'm leaning towards the array style, and having it be [op, key, val] simply because I'm not sure how this will work out for not and the whole WrapBool, but I think it would make it easier to work with.

I think it'd be good to have some thoughts here. @taystack I think you deal with this most, so what would you suggest?

joneshf commented 10 years ago

Actually, i think we have another option! Let a function like map work only on the values, then give other implementations that work on the keys, or the whole shebangabang, like map-keys, map-all or whatever.

This way, users don't have to worry about unboxing arrays and stuff, and you can still write readable, simple code like:

I = require \inquire
p = (\cat `I.eq` \good) `I.and` (\dog `I.ne` \bad)
p.map (.toLocaleUpperCase!)

But if you want, or need, to go whole hog on the thing, you can still do that.

taystack commented 10 years ago

Honestly, I dig object notation. When coding in Livescript I tend to use dot notation when dealing with arrays anyways. Either way when I get what you dish out I'll just make it feel like an object. From a distance, it looks like it would be easier to access members of an object than indices of an array, but we're back to figuring out the lesser of two evils with that discussion.

joneshf commented 10 years ago

So this is pretty much taken care of in https://github.com/concordusapps/inquire.js/tree/v0.4.0

It's a Functor, so you get map for just the values. It's a BiFunctor so you get bimap for the keys and the values. It's a Foldable so you get foldr and friends for the values, it's a BiFoldable so you get bifoldr and friends for the keys and the values. It's a Traversable so you get traverse and friends for the values. It's a BiTraversable so you get bitraverse and friends for the keys and values.

If you'd like more granularity, there's a Zipper so you can traverse each element one at a time (or go directly to it if you know the exact path in the tree), modify it, then zip it back up and return the modified thing. We probably need a few more combinators for the Zipper though in order to make it less boilerplate-y to work with, but the functionality is all there. I think something like findHole :: k -> InquireZ k v -> InquireZ k v might be very helpful in this specific case.

joneshf commented 10 years ago

The takeaway here is that now that we have Functor, Foldable, and Traversable, we can do just about anything to this thing in terms of one of their primitives. We all know how powerful foldr is, so we should also see how great these three things together are.