Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntax #4

Open
cmontella opened this issue Jul 19, 2016 · 146 comments
Open

Syntax #4

cmontella opened this issue Jul 19, 2016 · 146 comments

Comments

@cmontella
Copy link
Contributor

RFC Link - https://github.com/witheve/rfcs/blob/master/proposed/syntax.md

@Eucalyptus2013
Copy link

In the chat app, you use freeze all.

What does it mean?

Thanks

@cmontella
Copy link
Contributor Author

cmontella commented Jul 19, 2016

https://github.com/witheve/rfcs/blob/master/proposed/syntax.md#freeze-all

Good catch, I forgot to include a section on that. Right now, all is used to specify that the subsequent objects are available globally. By default, facts you add to Eve are visible only to your session. In the chat app, we need those messages to be available to other users, so we use the all keyword.

Honestly, we're not too thrilled with the keyword all (maybe global makes more sense?), so suggestions here are appreciated.

@RubenSandwich
Copy link

Considering the call for a new name for Eve's Objects:

(object is a pretty generic and overloaded term, so let us know if you have ideas for what to call these guys)

The name Object is problematic for a variety of reasons. You mention the obvious one of Objects in Software is usually referring to a unit in a OOP model. This is problematic not only for the person who is familiar with OOP but also for the person who has heard of OOP, and also for the learner of Eve who after Eve might want to dip their toes into other forms of programming. Furthermore the term Object is a vague and crude term in that anything can be an object.

So I suggest the name Records. You describe objects as:

key-value pairs attached to a unique ID

This falls in line with the concept of Records in Elm and Databases, and somewhat close to other functional programming languages concept of Records. But more importantly Records is a term already known well to mean a collection of data about a specific thing to the general public.

@bertrandrustle
Copy link
Contributor

Given that "Eve" is a homophone of "EAV" (Entity-Attribute-Value) and that EAVs are Eve's core abstraction under the hood, it seems more appropriate to refer to Eve's objects as entities. entity.attribute = value is more conventional than object.attribute = value.

Record is also a sensible alternative.

@shamrin
Copy link
Contributor

shamrin commented Jul 20, 2016

 guest = if p = [#friend busy-dates: not(party.date)] then p
         if [#friend spouse busy-dates: not(party.date)] then spouse

And later:

In the above example, we add guests to the list if they are a friend and not busy, or if they are a spouse of a friend and not busy.

It seems to be an inconsistency here. If I'm not mistaken, "if they are a spouse of a friend and not busy" should be written as if [#friend spouse: [busy-dates: not(party.date)]] then spouse. Currently you've implemented "if they are a spouse of a non busy friend".

@cmontella
Copy link
Contributor Author

Pascal pointed out some deficiencies in the example on the mailing list. We wrote it a couple weeks ago when the runtime still couldn't run the code. I'm currently rewriting the program to make it correct.

@Eucalyptus2013
Copy link

How long does a session last?

After the session is closed, is the data stored in the Eve DB deleted?

@cmontella
Copy link
Contributor Author

cmontella commented Jul 21, 2016

@Eucalyptus2013: The Eve DB is a distributed database, so it's a little nuanced, since there are actually multiple DBs (we call them "bags", a better word is appreciated here too).

When you start an Eve server, it starts with an empty global bag. When I start a session on the server (which lasts as long as I am connected to it i.e. the websocket is open), then I'm given my own instance bag, which is empty. But the facts available to me are the union of my instance bag and the global bag.

So every time I say freeze or maintain, the subsequent facts are added to my instance bag. When I leave, I take all my facts with me.

When I say freeze all, then I'm no longer talking about my instance bag, I'm talking about the global Eve bag. In that case, when I leave, my facts are still there.

In the context of a chat app, it uses freeze all to put the messages in a global bag. Thus if I'm in a conversation and my session disconnects, my messages don't suddenly disappear from the terminal. Similarly, if my friend is messaging me while I'm not disconnected, I will see the messages as soon as I connect, because those messages will be in the global bag, and the global bag is unioned with my instance bag.

Does that make sense?

@Eucalyptus2013
Copy link

If I want to store a fact in the global bag (by using freeze all), because I want to access this fact after the session is closed, but I don't want the other users to access this fact, how can I do this?

@cmontella
Copy link
Contributor Author

Right now, our permissions story is incomplete. Eventually, we will have a notion of a user, and be able to stick facts into bags with restricted access.

Until then, you can create your own users, and parameterize the facts with a user attribute. The you can join the restricted facts against the current user. The chat example does this in a way.

@cmontella
Copy link
Contributor Author

Okay, I've updated the RFC with a better example program. Right now, there are still some execution issues with it, so the result if you run it isn't correct. But we'll work on those more today.

I also removed a section on reusable code. We had a discussion about user defined functions yesterday, and that whole area will be going through a redesign. I'll add that new design to the proposal here as a pull request when I've gotten it together.

@bertrandrustle
Copy link
Contributor

After further consideration, being as objects are employed not only as literal representations of Eve DB facts but also as patterns for matching against such facts, I don't believe records is a suitable replacement term for objects; and while entities still seems applicable, I now prefer the term forms.

@rainhead
Copy link

First off, how stable are you trying to get your syntax? You mention under "Risks" that you don't want to turn people off, which is reasonable, but Eve is not at risk of becoming hoon. I'd worry less about turning people on or off and more about gently introducing them to the semantics. Let's allow ourselves to learn how to write Eve programs before we get confident about syntax.

That said, the biggest thing I'd expect to cause people to "not get it" and be turned off is the fact that although a block looks like a procedure, you're actually building a single query. let … in … and similar constructs frame this well in e.g. Haskell, but I'm not sure how to give an imperative programmer the right expectations.

The second-biggest thing are the set semantics. This still throws me off sometimes after writing SQL for 10-15 years. I really don't know what to recommend, but I feel like the syntax has to communicate a programmer's expectations around cardinality. Perl's @ sigil is the only real thing that comes to mind.

On objects: I agree, records or entities would be fine. "Record" is maybe more friendly.

On phases: I think of "prepare" and "commit" phases, if we are talking about them. Consider "do" and "commit", but I'm not confident I understand my intention when using freeze and freeze all. I think there's a sense of making a commitment to others, of making a statement that is hard to retract, so: "publish"? Manipulating the state of bags is surely not what I'm thinking about.

if guest = [@Arthur] What's going on here? Is = testing for set membership? For set intersection?

guest = [#invited name]: The [#invited] syntax seems to reinforce the idea that you're dealing with a set, but then you're assigning that set to a singular guest. Again, how does the programmer express expectations around cardinality?

sum(burgers given burgers, guest) I read , as having lower precedence than given, and didn't understand what was going on here until I read the explanation.

What is [#session-connect] for? Does it mark the beginning of a serializable transaction? Is it necessary? Why does it share tags' syntax?

People who are 50 year old
  [tag: "person" age]
  age = 50

Regarding age: this auto-binding feels much too subtle. You're also discouraging people from using identifiers that communicate something about the intention of the block, or tie together multiple queries. This feels more confusing than destructuring does in ES6, but maybe my feelings would change with practice.

How do you fit predicates other than equality into that record query syntax?

Sorry, I'm at a disadvantage here: you've spent a couple years programming in this paradigm, and I haven't. I can tell you what's confusing to me, but without that experience of solving actual problems in Eve, it's hard for me to suggest concrete improvements.

@cmontella
Copy link
Contributor Author

cmontella commented Jul 22, 2016

Peter,

Sorry, I'm at a disadvantage here: you've spent a couple years programming in this paradigm, and I haven't. I can tell you what's confusing to me, but without that experience of solving actual problems in Eve, it's hard for me to suggest concrete improvements.

Actually, you're exactly the kind of person we want commenting on this; we need the perspective of an outsider because ultimately we want more people using Eve than just us :)

First off, how stable are you trying to get your syntax?

I think we're happy with the overall direction of the syntax, so we wouldn't want to switch to s-expressions or a c-style curly brace syntax. That said, we still see this syntax evolving as we discover more about how to write good programs this way. Since we posted the RFC, we already have new ideas for how to do user defined functions.

That said, the biggest thing I'd expect to cause people to "not get it" and be turned off is the fact that although a block looks like a procedure, you're actually building a single query.

One of our decisions we hope will mitigate this perception is that the blocks are not really named, and there's no real concept of "calling" a block, so without those a procedure really can't exist. Once upon a time, we thought about wrapping the blocks in a query {} just to delineate them, but it seemed redundant and unnecessary.

Another way we hope that our code doesn't "look" procedural is that we're trying push the idea that eve files are actually prose with code interspersed, rather than code with comments interspersed. Thus, visually, eve code will look more like a document than code.

Finally, I think that while the statements could look procedural, the overall feel of the syntax stresses that there’s something different going on. If we had a c-style curly brace syntax, I think that it would be a much bigger concern.

The second-biggest thing are the set semantics.

Yes, we see this as a source of confusion too, but we hope to mitigate it in several ways. For the most part, you can actually code without thinking about cardinality, because our evaluation is row by row. Just consider everything as a scalar, and most of the time things just work when they’re not. You really only have to pay attention to cardinality when you’re using an aggregate, or mutating.

With a little tooling though, we can actually provide the cardinalities of your variables as you code, so this is one way we can help here. Another might be to try and enforce a convention, that plural variables should be used for sets and singular ones should be used for scalars. But that would be purely convention. Regardless, we'll see if this is an issue or not.

if guest = [@Arthur] What's going on here? Is = testing for set membership? For set intersection?

guest = [@arthur] is saying that guest is equivalent to an object that has a name @Arthur.

Again, how does the programmer express expectations around cardinality?

Any time the programmer uses a tag selector, then there's the potential for cardinality greater than one. Regarding the plurality of the variable, maybe this is something we try to encourage through convention. If the variable can be plural, write it as a plural.

sum(burgers given burgers, guest) I read , as having lower precedence than given , and didn't understand what was going on here until I read the explanation.

Commas for us are whitespace. They have no syntactic meaning. The use of the comma here was in the english sense to delineate a list. It should be read "sum the burgers given burgers, guest". However, we’re thinking about new ways to express this to avoid any confusion.

What is [#session-connect] for? Does it mark the beginning of a serializable transaction? Is it necessary? Why does it share tags' syntax?

It has a tag because it’s a normal object, like any other. When I connect to the server, Eve puts a #session-connect in the DB that exists for one tick of the executor. For that one tick, the object [#session-connect] exists, and I have the opportunity to do something to the DB. So this is a good place to initialize any data with a freeze. Using a maintain here would destroy the data on the next tick, because [#session-connect] no longer exists.

This is something we might sugar over in the future though.

Regarding age : this auto-binding feels much too subtle. You're also discouraging people from using identifiers that communicate something about the intention of the block

You can bind any variable to create an alias. For example: [#person age: age-of-a-person]

How do you fit predicates other than equality into that record query syntax?

The same way you do equality. For instance: [#person age > 50]

Thanks again for your comments; you've raised some important points that I need to think about some more. Let me know if you have more ideas/opinions

@bertrandrustle
Copy link
Contributor

When an Eve program is run, are the blocks evaluated in top-down sequence as they appear in the source file?

When a block is evaluated, is the collect phase evaluated eagerly, or is it evaluated when its objects are first referenced in the mutate phase?

Blocks with a maintain clause appear to keep the queries in the collect phase alive. Is there syntax for terminating these open queries programmatically?

Can a single block contain a maintain clause and a freeze clause?

@cmontella
Copy link
Contributor Author

Bertrand,

When an Eve program is run, are the blocks evaluated in top-down sequence as they appear in the source file?

No, block order should not matter. Typically, line order should not matter at all for Eve programs. However, there are two instances when order matters:

  1. In every block, you can only query the Eve DB before a mutation fence (freeze or maintain). So any selects, aggregates, etc. need to come before the fence. After the fence, you can only really use mutation operators.
  2. Arms in an if-else block are evaluated sequentially. I point out that in the party planning example, if a #friend is #vegetarian and #hungry, then she will actually get 2 hamburgers instead of 0, because she satisfies the condition of the hungry arm before the vegetarian arm.

When a block is evaluated, is the collect phase evaluated eagerly, or is it evaluated when its objects are first referenced in the mutate phase?

We eagerly evaluate it. For instance, consider this block:

a block
  x = [#tag1]
  y = [#tag2]
  maintain
     y := [#tag3]

If there are no objects with #tag1, then y will never get #tag3, even though we never talk about x in the mutate phase.

Blocks with a maintain clause appear to keep the queries in the collect phase alive. Is there syntax for terminating these open queries programmatically?

The way you could achieve this is by putting a #switch object in the collect clause, in the fashion that I demonstrated above. When the #switch is present, then the block is active. When the #switch is gone, then the block does nothing.

Can a single block contain a maintain clause and a freeze clause?

Yes, blocks can also contain multiple freeze clauses e.g. the chat example uses a freeze and freeze all clause in the same block.

@yazz
Copy link

yazz commented Jul 25, 2016

I just found this comments thread. I think a tutorial would be great for Eve. I can write one myself, but would like to know, how long before the first version of the syntax will set set in stone as it were?

@bertrandrustle
Copy link
Contributor

Thanks for nailing down those loose edges for me Corey. I've learned conceptually esoteric languages like Haskell without much difficulty, but in learning Eve I'm having to bury pretty much all of my assumptions and a good number of my intuitions.

There's one important aspect of Eve that remains opaque: I haven't seen public discussion of Eve's temporal logic underpinnings yet. Can you address this and how it might be exposed to programmers through the developer syntax?

@cmontella
Copy link
Contributor Author

Zubair,

The syntax probably won't be set for a while, so a tutorial might be out of date as soon as you write it. I'm working on one myself (well a couple), which will help in learning the language. However, maybe writing about your experience in learning Eve would be a worthwhile exercise.

@ibdknox
Copy link
Contributor

ibdknox commented Jul 26, 2016

@ZubairQ the main thing is we don't want to set anything too into stone until we see more usage from folks outside of us. That being said, there's only one biggish change that I can think of at the moment in terms of the syntax, which is a slightly different syntax for function calls. Beyond that, unless we get some drastic feedback, it's likely just wording changes and such.

@bertrandrustle That's interesting, our language is easily an order of magnitude simpler than something like Haskell. What intuitions and assumptions are being challenged? Do you think there's something we could do to help facilitate that understanding?

At the moment, we don't expose general time traveling in queries, but that's probably the simplest and least semantically important part of the temporal logic. Our semantics are based on dedalus, which has the notion of talking about "now" and "next". This shows up for us as part of the difference between maintain and freeze - maintain specifies that the mutations are bound to their query and that they happen instantly, whereas freeze freezes the mutations at a point in time and asserts them in the next "tick". The reason for this is that to be order-independent our rules are run to fixed-point and there are some things that need to be stratified in order for them to terminate. For example, let's say I wanted to update a counter every time a button is clicked:

increment my counter
  [#click element]
  element.name = "my-cool-button"
  freeze
    element.current-count := element.current-count + 1

If freeze asserted that change "now", it'd never reach a fixed-point because each loop would increment the counter and you'd run again. This query is cyclic - it relies on a thing it changes. By asserting the change in the next tick though, the first loop of the evaluation would generate current-count + 1 and the second loop would regenerate that same value, meaning it's done. For the most part, the only place this comes up is when reacting to events - you want to freeze the value at a point in time and make sure you don't subsequently create an infinite number of other values by looping back on yourself.

@cmontella
Copy link
Contributor Author

@RubenSandwich suggested changing "freeze" to "commit". The thought here was that "freeze" implies that the object is frozen in time, forever unchanging, which seems wrong since a frozen object can be mutated.

Thoughts?

@bertrandrustle
Copy link
Contributor

@ibdknox

That's interesting, our language is easily an order of magnitude simpler than something like Haskell. What intuitions and assumptions are being challenged? Do you think there's something we could do to help facilitate that understanding?

The choices Haskell makes on what to expose and what to abstract away closely complement my natural cognition, whereas Eve brings one of my greatest challenges to the fore, temporal reasoning.

I'm more fluent in structure than process. My brain models systems as a complex of visual-spatial impressions where each part can be shifted within a concrete-abstract spectrum to facilitate relations with other parts or systems. This is pretty effective for structural pattern recognition, but a major weakness is the lack of a temporal sense, and so I often resort to modeling temporal things as spatial/structural things, somewhat like the Bret Victor demos in which he models time using geometry. With Eve a visual interface may prove more natural for me than a textual syntax.

In the Haskell world almost everything is lazy and immutable by default and time rarely makes an explicit appearance. Programs are fundamentally structured around timeless, unchanging functions which are devoid of side effects. These properties make it easy for me to reason about things. Haskell is a largely frozen world.

Superficially the Eve world is declarative as with Haskell. But Eve programs are structured around movement of state in temporal dimensions. A block essentially describes a discrete interaction of state in time, the duration of which is determined by a two-phase process over state: observations of state (queries) matching certain criteria in the first phase determine whether or not state will be changed in the second phase, and the nature of change in the second phase can be snapshotting (freezing in time) of state or manipulation and continued observation of state (maintenance of state in an open-ended "now"). Also in contrast to Haskell, side effects appear to be Eve's primary method of computation.

I'm not sure any of that makes my perspective relatable or even understandable. As for improving the textual syntax, I don't have anything useful to propose yet; I need more experience with it. But in light of how my brain works I concede I might be an outlier the textual syntax probably shouldn't try too hard to accommodate. ;)

@bertrandrustle
Copy link
Contributor

@cmontella "freeze" is a good choice from a time-oriented viewpoint, and "commit" is a good choice from a database-oriented viewpoint. If Eve is a temporal query language on top of a relational database, which viewpoint should prevail?

The time viewpoint is consistent with the abstraction presented by Eve's temporal semantics. (Although the way I conceive of it, the term "snapshot" captures the meaning even better than "freeze.")

The database viewpoint is closer to the underlying implementation, where it's just a standard database transaction, something already very familiar to developers. The term "commit" itself is also common to source control tools and has much the same meaning there.

@benjyhirsch
Copy link

Some thoughts:

  • As an alternative to “objects”, have you considered calling them “patterns”? It seems like it would more clearly that there might be multiple matching entities in the Eve DB. I also like “forms” as @bertrandrustle suggested for the same reason.
  • Is there any advantage to allowing the syntax identifier.attribute += value in addition to identifier += [attribute: value]? It seems like the former could cause confusion given its similarity to syntax in other languages with very different semantics (i.e. someone writing order.total-price += item-price probably doesn’t expect total-price to be a set containing all the distinct item-prices, and probably meant instead to aggregate something in the collect phase). Also, the following minimal pair might be confusing:
Assume that there's an entity matching [b: [c: 1]] in the DB (and therefore an
entity matching [c: 1]), but no entity matching [c: 2].

The following results in there being an entity with [b: {[c: 1], [c: 2]}] in the DB
(i.e. it creates a new entity matching [c: 2], distinct from the one matching [c: 1]):
  a = [b: [c: 1]]
  freeze
    a.b += [c: 2]

Whereas the following results in there being an entity with [b: [c: {1,2}]] in the DB
(i.e. it adds 2 as another value of the attribute c on the same entity that matches [c: 1]):
  a = [b: [c: 1]]
  d = a.b
  freeze
    d += [c: 2]

If the syntax in the first block weren't allowed, here's how you would write it:
  a = [b: [c: 1]]
  freeze
    a += [b: [c: 2]]
which at least somewhat more clearly might mean something different from the second
block.
  • If I understand correctly, the syntax [attribute: value] means substantially different things in different contexts:

    • In the collect phase, it queries for entities of the given shape in the DB, and binds the retrieved values to the specified identifiers so as to make the asserted equivalences true.
    • In the mutate phase, on the top level of the right hand side of +=, -=, or :=, it specifies that these are the attributes that we are going to modify on the entities that are bound to the identifier on the left (and these are the values we will add/remove/set on those attributes).
    • In the mutate phase, either on its own or nested within an attribute on the right hand side of += or :=, it is a literal entity that gets created in the DB.

    One possible alternative to at least distinguish between second and third within the mutate phase and explicitize when we create new entities in the DB:

++[attribute: value]
++[attribute: value, other-attribute: ++[nested-attribute: nested-value]]
identifier := [attribute: ++[nested-attribute: nested-value]]

This clears up the confusion of the earlier minimal pair even more clearly:

This clearly creates a new entity in the DB:
  a = [b: [c: 1]]
  freeze
    a += [b: ++[c: 2]]

Whereas this clearly doesn't (assuming the above syntax for creating new DB entities):
  a = [b: [c: 1]]
  d = a.b
  freeze
    d += [c: 2]
  • Some possible alternatives to “collect”: “lookup”, “bind”, “bindings”, “find”, “query”, “get”, “define”, “definitions”, “match”, “matches”, “observe”, “observations”, “declare”, “declarations”, “ask”, “questions”, “learn”.
  • Some possible alternatives to “mutate”: “change”, “changes”, “modify”, “modifications”, “update”, “updates”, “edit”, “edits”, “set”, “mutations”, “delta”, “deltas”, “effects”, “do”, “act”, “tell”, “say”, “statements”.
  • The notion of absolute versus relative might be useful in naming and/or explaining "freeze" versus "maintain"
  • I agree with @bertrandrustle that "snapshot" is potentially more intuitive than "freeze".

@bertrandrustle
Copy link
Contributor

It seems likely the keywords maintain and freeze will continue to be confused with notions of mutability. Both terms connote keeping something as it is by preventing or undoing change. Compounding the problem, the keywords serve a secondary purpose in Eve's syntax: signifying the beginning of a block's so-called mutate phase. Terms which are more explicitly timelike and storage-related could increase their conceptual distance from mutability.

  • For maintain I propose refresh, which connotes keeping up to date something that may also change, just as a DRAM chip keeps live data by refreshing its cells, or a computer display keeps a live image by refreshing its pixels. Another alternative could be sync, if that isn't deemed too overloaded.
  • For freeze I again propose snapshot, a concept many developers employ on a daily basis in dealing with things such as filesystems, Docker containers, and virtual machine images. A snapshot captures the state of a dynamic system at a particular instant, and can be used as a stable branching point from which to explore future states.

In addition to renaming maintain and freeze, the "mutate" phase could be renamed. Indeed, the names of both block phases may benefit by reflecting underlying database semantics. (See also the suggestions by @benjyhirsch above.) Borrowing from high-level SQL terminology (not necessarily actual syntax):

  • collect phase becomes query phase
  • mutate phase becomes manipulate phase. SQL's data manipulation language operators mirror Eve's mutate operators: INSERT (+=), UPDATE (:=), DELETE (-=).

I'm aware that Eve's relational database doesn't use SQL, but SQL is widely familiar to people with database experience, and I couldn't identify terminology for a comparable two-phase dichotomy in the domain of Prolog/Datalog/Dedalus.

@bertrandrustle
Copy link
Contributor

I thought of another perspective on replacement candidates for maintain and freeze.

maintain and freeze are imperative verbs which feel out of place in a declarative syntax. Nouns and adjectives are more orthodox in declarative programming and may improve conceptualization in this case.

Objects fenced with maintain represent dependent data that's responsive to changes; the data continually reacts to its supporting queries. Objects fenced with freeze represent standalone data that's passive; after the data is asserted it is at rest with respect to its supporting queries.

With those considerations, I propose renaming:

  • maintain to reactive
  • freeze to passive

@ibdknox
Copy link
Contributor

ibdknox commented Aug 1, 2016

First off, thanks for the awesome and thoughtful feedback @benjyhirsch and @bertrandrustle ! I spent a bunch of time last week and this weekend thinking about the comments here and have a few proposals that I think help address a lot of the feedback.

Most fundamentally, we should probably adjust the way we talk about Eve's paradigm in general. Instead of evoking logic or relational languages, it seems like the most straightforward way of describing Eve is as a pattern matching language. "When you see this pattern, do this." Amusingly enough, that's where we very first started with the madlib version of Eve. To that end, I propose the addition of the match keyword to mark the beginning of a block:

invite friends who are not busy
  match
    [@"my party" date]
    friend = [#friend busy-dates != date]
  maintain
    friend += #invited

I tested a few variants of maintain/freeze with people and have come to bind/commit as being the most familiar while accurately representing the semantics of each. They also fit the pattern matching description pretty well; "match this and bind it to...", "match this and commit these..." So that would become:

invite friends who are not busy
  match
    [@"my party" date]
    friend = [#friend busy-dates != date]
  bind
    friend += #invited

I also suggest we change commit all to commit global. To your point @bertrandrustle, in this scheme we could call the parts match and action, which seems pretty straightforward. This is still using verbs here, but I've found that trying to reinforce that the semantics are declarative hasn't really been beneficial in getting people to understand the model. At some level you can think of these as instructions to Eve - e.g. "match these things and do this stuff when you do" - so verbs fit nicely. Fixpointing we could describe as matching the rules until no new actions are fired.

@benjyhirsch you brought up some really good points about the confusing semantics of the mutation operators and I spent the most time trying to figure out something consistent for them. It took a bit, but I think we've got something that's a good deal more sensical than what's there now. The biggest change is that any object in the action part of a rule is a new object. Also, instead of set (:=) behaving like a merge in some cases, there would be an explicit merge operator (<-). :=, +=, and -= would all behave much more like standard assignment in an imperative language, except you have to specify an attribute (which #foo and @joe do implicitly):

// compile error, no attribute is specified and setting an object to 
// another object doesn't make sense
foo := [bar: 3]
// set the bar attribute of foo to 3
foo.bar := 3
// add 4 as another bar attribute of foo (foo.bar is now 3 and 4)
foo.bar += 4
// remove 5 as an attribute of bar on foo (if there was a 5, it would be gone)
foo.bar -= 5
// merge the new object into foo
foo <- [bar: 3]
// add/remove a tag or name to foo
foo += @joe
foo -= #cool
// remove foo
foo := none
// create a brand new object
[#cool bar: 3]
// create an object and get a reference to it
foo = [#cool bar: 3]
[#some-other-object baz: foo]

match
  foo = [#foo]
  baz = foo.bar
bind
  // this would be a compile error as no attribute is referenced
  baz := [a: 3]
  // set a to 3 on the objects of foo.bar, if baz is not an object, that would be a runtime error
  baz.a := 3
  // this would merge the new object into baz, setting a to 3
  baz <- [a: 3]

One thing I'm not certain about is what baz := none should mean. It would remove all the attributes attached to baz (thus removing baz), but what about the references to baz? In this case, foo.bar has a value equal to that object's uuid, should it also be removed? what about other references?

Finally, unrelated to this part, we want to change the way "functions" are called to reinforce that they are themselves really just objects that are created on demand. We also want to make the code a lot more readable by using keyword arguments:

// old syntax
x = sin(foo)
// new syntax
x = sin[angle: foo]

One big advantage besides being self documenting is that optional/alternative parameters are much more straight forward in this scheme:

x = sin[radians: π/2]
x = sin[angle: 90]

Putting the name outside is really just sugar for:

[#sin #function angle: 90, return: x]

Which means that defining your own functions would be something like so, where ? at the end of an identifier means it's an input:

My crazy mathematical formula
  match
    return = (value? + 30) / π
  bind
    crazy-math[value?, return]

In reality you need functions in our language very rarely, but this is a very powerful system. For example if I wanted to support an entirely different coordinate system for sin, I could do this:

coolest coordinate system ever
  match
    return = sin[angle: value? / 2 + 30]
  bind
    sin[chris-degrees: value?, return]

use my sin
  match
    answer = sin[chris-degrees: 40]
  bind
    [#div text: answer]

@ibdknox
Copy link
Contributor

ibdknox commented Aug 2, 2016

All of the above proposals have been implemented in the match-syntax branch.

@bertrandrustle
Copy link
Contributor

@ibdknox Your changes seem reasonable, but I'm fuzzy on a few things regarding the changes to function syntax.

Now that the match (formerly collect) phase is explicitly fenced, is anything actually being matched in the match clause of your example code here?

My crazy mathematical formula
  match
    return = (value? + 30) / π
  bind
    crazy-math[value?, return]

That looks like a simple equality statement involving primitives and attribute variables but no objects. Conceptually, I believe simple equalities and other statements which don't query the database, filter a query, or affect any statements which do those things, should go before any match/bind/commit clauses, at the first code indentation level.

From a practical standpoint, because a non-querying, non-filtering equality rule such as this is, I assume, always true, its presence in the match clause seems to rule out the possibility of constructing a subsequent bind clause that conditionally terminates the block via dependency on other rules in the match clause.

Finally, would the availability or application of the crazy-math function behave differently if bind were changed to commit in that example?

@skybrian
Copy link

skybrian commented Aug 4, 2016

Instead of "object" I suggest "selection". We could say that each query defines selection variables in the "collect" phase. This makes it clear that a selection is not immutable - the value of each selection variable (its "current selection") actually changes over time.

Then it would be natural to rename the "collect" phase to the "select" phase.

@yazz
Copy link

yazz commented Sep 14, 2016

@cmontella Actually I would say the opposite, in that saying "database" as singular is unusual. In Pharma we use products that talk to multiple databases all the time, products like Qlik Sense and SAS so it is actually unusual to talk about database in the singular most of the time at my work. I think developers making webapps usually talk to ONE database, so I agree that for them it may be a little weird for them.

But I like database and datebases !

@RubenSandwich
Copy link

RubenSandwich commented Sep 14, 2016

I'm glad people are finding my naming questions helpful. 😄

@cmontella Your answer for naming question #3 for Context makes sense.

I'm ok with the word Database/Databases and am not bothered by the plurality of it as it is very common for apps to have both a client side and server side persistence store which to me fits the concept of "Database" even if some implementations of these two stores do not fit the actual technical meaning. However one thing that does bother me is how technical sounding the word is which I fear could scare people completely new to programming off. (Devils Advocate: If a beginner gets to the part about worrying about multiple Databases in Eve they might have already gotten over the 'hump' so to speak, but maybe not depending on how central of a concept it ends up becoming.)

@RubenSandwich
Copy link

RubenSandwich commented Sep 14, 2016

Switching gears here from the naming of Bags.

One wart I currently see in the Eve syntax is the use of the not operators parenthesis.

For example here is the not operators usage in the documentation:

friends not invited to the party
  friends = [#friend]
  not(friends = [#invited])

These parenthesis seem ugly to me because this is the only operator that uses them. The only other place parenthesis are used in Eve, to my knowledges, is for the tuples. I can see why they are required for nested queries, but I think for consistency this operator should use the function syntax. This removes what I believe to be a gotcha and makes the parenthesis always mean tuple.

Function syntax not operator:

friends not invited to the party
  friends = [#friend]
  not[friends = [#invited]]

Alternative Haskel like syntax:

friends not invited to the party
  friends = [#friend]
  friends =/= [#invited]

@nmsmith
Copy link

nmsmith commented Sep 15, 2016

@cmontella @ibdknox I think databases could be a reasonable name, but I'm confused about what role databases will have. It seems like they might have two purposes:

  • Modularity: You can add and remove databases from anywhere on the web to your app (they function both as data repositories and code libraries).
  • Scoping: You can prevent clashes in attribute names, which are going to be rampant in non-trivial programs.

But I feel like you really want a separate scope for every single "relation"/"table"/"collection of objects" that conceptually appears in your program, otherwise what can I expect to get when I try to match on some simple patterns?

  • [n]
  • [id]
  • [value]
  • [return]

I'll get a whole lot of values of semantically-different attributes (that happen to be named the same) from a whole lot of unrelated entities. This seems like a substantial flaw in the language as it stands. From what I can see this would never be desirable.

Maybe this is what #tags (or names) are supposed to be used for. But if you're meant to use a tag to make sure you only access the subset of an attribute that has the right semantics, then it seems like tags should be mandatory for every record, because tags are really defining a context too: the context of the attribute names. Of course a record can have more than one tag, so this implies the contexts can be overlapping. One thing is clear though: a record should certainly belong to at least one context (have one tag). A tag/context assigns meaning to a collection of records.**

So is it safe to say that tags are really used to give context to a record and its attributes? If so we might want to mandate that all records have at least one tag. And with this understanding of tags, it definitely makes more sense to call bags databases, because they'd be much less about scope/context and much more about modularity.

** Aside: I really want to say "object" or "entity" here. To me, records are homogenous rows in a table.

@wtaysom
Copy link

wtaysom commented Sep 15, 2016

@ecl3ctic Really good points.

purposes ... Modularity ... Scoping

And does a module as such add things to just one database or potentially many?

Maybe [scoping] is what #tags (or names) are supposed to be used for.

Might adding a #tag (or a @name for that matter) be just the same as using a context? So that:

match @eve-land
  person = [#person]
  friend = [#person friend: person]
bind @browser
  [#div children:
    [#div person text: person.name]
    [#div text: "has the following friends"]
    [#div friend text: friend.name]]

is basically the same as:

match
  person = [@eve-land #person]
  friend = [@eve-land #person friend: person]
bind
  [@browser #div children:
    [@browser #div person text: person.name]
    [@browser #div text: "has the following friends"]
    [@browser #div friend text: friend.name]]

or even incendiarily as:

match @eve-land #person
  person = []
  friend = [friend: person]
bind @browser #div
  [children:
    [person text: person.name]
    [text: "has the following friends"]
    [friend text: friend.name]]

I really want to say "object" or "entity" here. To me, records are homogenous rows in a table.

Word.

@ibdknox
Copy link
Contributor

ibdknox commented Sep 15, 2016

@ecl3ctic As you identified that is more or less what tags are for, but I don't think we should make them mandatory. One simple counter example is wanting to select Chris [@chris] - I shouldn't need to know what tags he has to do that. We could extend the suggestion to you must either have a tag or a name, but that's starting to get arbitrary. What if I want to query for all the records, regardless of type that were created by bob? Or all the records this week that have some relationship with [@chris]? Similarly, if we're dealing with data from outside of eve, there may not be obvious tags or names for it.

@RubenSandwich so far, (..) has come to mean an ordered list of expressions, which makes sense for not - you're stating the list of expressions should return no "rows". So if we think of not as a modifier, I think it at least sort of makes sense. I'm also not a huge fan of it, but using not[...] breaks function records which currently only have keyword args and I'm not sure the haskell approach could handle something like:

not( x > y, [#foo z: x])

Right now we have a few uses of parens:

// return multiple values from if
(x,y) = if .. then (3,4)                 

// say that the object must have x: 1 and x: 2   
[#foo x: (1,2)]                              

// define the projection
sum[value: x, given: (a,b,c)]        

// multi return
(token, index) = split[text: x, by: "/"]     

// instead of filtering on no results, pass on no results
not( x = [#bar] )                                      

// instead of filtering, return as a boolean
is(x > y)                                                  

// Add/remove/set multiple values as an action 
a.x += (1,2,3)                                          

They seem decently consistent to me, but I definitely agree there may be better ways to spin it. We could keyword arg is/not:

is[test: (x > 5)]
not[any: (x = [#bar], y < 5)]

Or something like that (really dislike those attribute names lol).

@wtaysom
Copy link

wtaysom commented Sep 15, 2016

@ecl3ctic As @ibdknox said, maybe sometimes you are looking for all the records with an id attribute. Do we have a way to match an attribute with a particular value? For instance, suppose our #person is populated with all sorts of relations: mother, father, cousins. Given two people, what are all their relations?

@ibdknox
Copy link
Contributor

ibdknox commented Sep 15, 2016

@wtaysom That's where I started myself it just seemed like you could accomplish pretty much everything you want by specifying more tags, but it's not quite the same. One example is that having separate "databases" allows you to fork the data. There's also a semantic difference in the examples given if you take multiple databases into account:

// find any #zomg in foo and any #zomg in bar
match (@foo, @bar)
   [#zomg]

// find any #zomg that is named *both* @foo and @bar
match
   [#zomg @foo @bar]

You also end up with the CSS selector problem where you're trying to make more and more specific patterns to ignore things you don't care about. Instead, this way you can put them in a different box.

Given two people, what are all their relations?

We don't talk about it much right now, because we don't like it and it's slow. In the current implementation there's a magic thing that does:

[#eav entity: joe attribute value: jane]

We eventually arrived at this as a better thing, but it's not implemented (yet!):

lookup[entity: joe, attribute, value: jane]  // there's also tick, user, node, bag...

@cmontella
Copy link
Contributor Author

Hey everyone. I just wanted to point out that we've made a couple changes to the language and so I'd like to get some feedback here.

First, as we mentioned earlier, bags/contexts are now called "databases". This doesn't have any other effect that word choice.

Second, we've removed the @ shortcut for the name attribute. The primary motivation for this was to use @ exclusively when referring to databases, making them first-class citizens of Eve. This means you can assign a database to a variable and use it as you would any other. A secondary motivation for removing @ sugar was that semantically, it doesn't mean what people expected. In places like Twitter, @ refers to a unique user. In our world, multiple things can have the same name, so if you expect uniqueness when using @ you may be unpleasantly surprised when rouge data starts showing up in your application.

In place of @, you can use # with much of the same effect. But if you want to use name you'll need to use it explicitly.

Finally, we're renaming match to search. I'd like to hear more thoughts here, but our feeling is that "search" is easier to explain to novices. We still want to talk about pattern matching, but in the context of searching. You search for records using a pattern, and everything that matches the pattern is returned.

@nmsmith
Copy link

nmsmith commented Oct 5, 2016

I agree with the decision to remove @. I also think search is an improvement; it's a more human-oriented word. One name that still doesn't gel with me is bind. Bind means to affix, hold something in place, glue/tie together. A binding contract is one which someone commits to for the agreement period. I don't feel like it's getting the message across.

The way I would describe bind to a human is that it derives new values from the ones found by the search. When the search results change, the derived values are updated to reflect that. Just a thought on another possible name.

@cmontella
Copy link
Contributor Author

cmontella commented Oct 5, 2016

Thanks for the thoughts Nick! I'm glad you think search is an improvement, we haven't tested this out too much yet, so it's good to hear some other perspectives.

We've actually had a lot of success describing bind, because it has a very explicit meaning in this context that other developers are familiar with: https://en.wikipedia.org/wiki/Data_binding

Data binding is a general technique that binds data sources from the provider and consumer together and synchronizes them.

So that's the meaning of "bind" we're going for. In a sense, the meaning of "bind" you put forth is still applicable here; what's being tied together are the records themselves. That's something that's unchanging. When you search for a record and then bind a record in response, those records are forever intertwined. It's the values of attributes that change. But a bound record cannot exist without its supporting records.

But I can definitely see how this might be confusing to people unfamiliar with this usage. I think when we launch to a wider audience this month, we'll see what the general perception is and adjust accordingly.

@wtaysom
Copy link

wtaysom commented Oct 6, 2016

@nmsmith In any domain, some words should stand out and others should be as plain as possible. In Eve, I say "bind" should definitely stand out, and should lead any curious person to the notion of data-binding. In contrast, "match" is needlessly more obscure than "search". Likewise, an essential attribute used in basically every Eve file merits a sigil. Use sigils for less prominent purposes, and you end up with Perl, or worse: APL.

@cmontella One downside of "search" is the old "search" vs "find" debate. (One incarnation http://stackoverflow.com/questions/480811/semantic-difference-between-find-and-search.) My main qualm is the usage, "you search a place for the thing you want". So it is natural to say "find this, bind that to it" but not so much "search for this, bind that to it". Others will, no doubt, have very different feelings.

@cmontella
Copy link
Contributor Author

@wtaysom I personally prefer search, because I think it might be a common thing to search a particular database. e.g. search @event. find @event doesn't have the same ring to it. I think that both have positives, however they are close enough in meaning that it might not matter one way or the other in terms of clarity of purpose. We'll keep an eye on general opinion of this, though.

@yazz
Copy link

yazz commented Oct 7, 2016

Instead of "search" how about "when"?

@cmontella
Copy link
Contributor Author

@ZubairQ "when" does evoke the "if-this-then-that" kind of workflow that is similar to what's happening in Eve. But it kind of loses out in the context of databases:

when (@db1, @db2)
  [#foo]

bind @browser
  [#div text: "Bar"]

versus:

search (@db1, @db2)
  [#foo]

bind @browser
  [#div text: "Bar"]

In my mind, search fits nicer with what is actually going on here. Thoughts?

@wtaysom
Copy link

wtaysom commented Oct 8, 2016

The word "search" does work well when you have a database context. Don't see the point of having the extra punctuation though. I mean why "search (@db1, @db2)" instead of just "search @db1 @db2". -- Of course how to use punctuation is the most superficial programming language issue second to tabs vs spaces.

@ibdknox
Copy link
Contributor

ibdknox commented Oct 8, 2016

That's fair, originally I was thinking it might have the same parsing ambiguity issues that if has, but it doesn't here. I'll run removing the need for parens by the guys.

@yazz
Copy link

yazz commented Oct 8, 2016

@cmontella Actually I thought about it more after reading your comment and you are right. Also, non programmers also know better what "search" means. Also "When" would not have been the right term. I guess I should have suggested "whenever" to indicate something that occurs on a continuous basis, as one of the issues with "search" as a word is that it tells me in my head that this only happens once... but "search" the best for now, I agree

@frankier
Copy link

frankier commented Oct 8, 2016

I feel like "search" doesn't capture the semantics as well as "match". One thing I don't like about search I feel like a search can come back with no results where as something matches or it doesn't. Match captures the Boolean aspect of whether the second clause fires at all.

With "search" the reason the second clause doesn't fire is "there were no results" which intuitively feel like "okay fair enough -- now you tell me that's what you meant" where is with "match" the reason the second clause doesn't fire is "um, because it didn't match" which feels more like "well yeah that's what we agreed".

I can see how search might be more approachable though and work well with the "database" name too.

One thing I wanted to bring up at this point was about the optional commas. I feel like things like pep8, gofmt and clang_format are trying to get things to a place where equivalent programs that only have superficial differences have a preferred form. Personally I think it makes it a lot nicer for multiple people to work together. In the aforementioned projects the language communities have converged on one coding standard. I wonder if it might be better to avoid allowing the choice of commas or not entirely since it's basically one less thing to have to normalise for a possible future gofmt like tool.

@wtaysom
Copy link

wtaysom commented Oct 9, 2016

I strongly agree that tools like gofmt go a long way toward normalizing style.

@cmontella
Copy link
Contributor Author

@wtaysom For what it's worth, short of a tool like gofmt, we realize that many new users will copy the style that they see us using, so we're trying to make our code consistent. We've started a style guide (still a draft) that aims to make our style explicit: https://github.com/witheve/docs/blob/draft/src/guides/style.md

Until we have a tool like gofmt, we'll try to encourage a certain style in these ways.

@jink
Copy link

jink commented Dec 15, 2016

How about "require" as an alternative to "search"?

require
person = [#person name address]
x = 10
x = 20

Requires the person record/pattern be in the database. OTOH, x = 10 = 20 is a requirement that cannot be met.

@cmontella
Copy link
Contributor Author

Hm, haven't thought of that one before! It's nice because it gives the impression that if the requirements aren't met, the block cannot run. But at the same time, we really haven't had any complaints so far about "search", so I don't know if changing it adds any more clarity. Just curious, what was your impression about what "search" did?

@jink
Copy link

jink commented Dec 16, 2016

I mentioned this just in case "search" does cause some confusion with initiates. My understanding is the search clause is setting up prerequisites prior to commit/bind. Search/finding/matching against a database makes sense. Two more possibilities: "assert" and "facts". Yet another: "when".

Here's an example where "search" does not seem such a good fit. From the CRM app:

search
t1 = 1479258949716
t2 = t1 + 1000
t3 = t2 + 1000
t4 = t3 + 1000
t5 = t4 + 1000
t6 = t5 + 1000
time-string = "5:15 PM"

@wtaysom
Copy link

wtaysom commented Dec 16, 2016

For me, "search" always feels a little awkward. When I talk or write, I'm liable to use "match". @frankier pointed out when "match" feels funny. "require" has nice connotations. I've encountered "given ... assert ..." before, which, though precise, feels mathy. There's also Inform's curious use of "instead ... try ...".

@owenoak
Copy link

owenoak commented Dec 18, 2016

I agree with @RubenSandwich that not(xxx) seems inconsistent with the use of [ for functions, eg: I'd prefer not[xxx] and is[yyy] so I don't have to think which separator to use -- just always use [.

I also feel that sometimes allowing a single unnamed argument to a function, in addition to additional named arguments, leads more elegant API. For example, having to always pass text in the split function split[text:"x/y/x"] feels clunky to me, and split["x/y/z", by:"/"] doesn't feel confusing at all.

Swift has a nice API Design Guidelines doc which may be worth taking a look at.

@owenoak
Copy link

owenoak commented Dec 18, 2016

The use of := for assignment in a commit clause seems clunky to me. Is there a reason that it can't just be =, given that we're unambiguously modifying things?

@owenoak
Copy link

owenoak commented Dec 18, 2016

In the docs, it says that : and = are equivalent when specifying record selectors, etc, but that : is preferred inside records. Why?

In

search
  students = [#student name: "John", grade >= 11]
....

the use of : to mean equals exactly seems awkward next to >= to mean equal to or greater than. Why not just use = consistently inside records as well?

@jink
Copy link

jink commented May 31, 2017

Can't resist one more alternative to search. How about when/commit and while/bind pairs?

@cmontella
Copy link
Contributor Author

cmontella commented May 31, 2017

@jink

I like the way your suggestion reads, and it actually fits with the change we made in v0.3 -- to forbid the use of bind and commit in the same block. However, I think having two different names for "search" belies the fact that in each case, the search/when/while portion of the block works the same way.

@owenoak

I'm sorry for the extended delay in getting back to you! Somehow your posts slipped under our radar. To answer your thoughts:

not(xxx) seems inconsistent with the use of [ for functions, eg: I'd prefer not[xxx] and is[yyy] so I don't have to think which separator to use -- just always use [.

Square brackets signify a record, and contain a list of attribute/value pairs. This is true for bare records, and functions (which is why we used square brackets for functions instead of parens like other languages -- we want to draw attention to this duality). not() isn't a function or a record, so we use parens here to differentiate it. Instead of containing a list of attribute/value pairs, not() contains a list of records.

I do agree having square brackets would be more consistent, and we had considered this at one point with the goal of minimizing the number of grouping characters we used, but in the end I think reserving square brackets for records exclusively is important enough to add another character to the syntax.

I also feel that sometimes allowing a single unnamed argument to a function

I agree, this is something that's tempting, especially for functions with one self-descriptive argument. If we choose to allow anonymous arguments there are two immediate effects:

  1. There's now a caveat to argument ordering: instead of arguments being unordered, they are unordered except in the case of a required first argument. So we lose a bit of consistency.
  2. We potentially lose some readability; explicit argument names are an opportunity to self-document Eve code.

Number 2 might be okay, since as you demonstrate split["x/y/z", by:"/"] reads fine. However, this might not be the case in an Eve syntax localized for another language; just because this reads well in English doesn't mean it will read well in Japanese. Moreover, I think I would avoid going this route because of number 1; being able to explain unequivocally that arguments are named and unordered is a great benefit. Adding caveats here make things more confusing, I think.

I thank you for the link to the Swift API guidelines, as I think they actually explain our general philosophy pretty well:

Clarity is more important than brevity. Although Swift code can be compact, it is a non-goal to enable the smallest possible code with the fewest characters. Brevity in Swift code, where it occurs, is a side-effect of the strong type system and features that naturally reduce boilerplate.

This is more or less one of the principals we outlined in this RFC.

The use of := for assignment in a commit clause seems clunky to me. Is there a reason that it can't just be =, given that we're unambiguously modifying things?

In our syntax, the = symbol signifies equivalence. This is distinct from the :=  operator, which is a composite add (+=) and remove (-=). Put another way, = is an assertion while := is an operation.

In the docs, it says that : and = are equivalent when specifying record selectors, etc, but that : is preferred inside records. Why?

This was a style choice. In other languages : is commonly used in record-like data-types to represent binding, so people are familiar with this. Another reason is that records are common in our syntax, and : is less visually impactful than =. An Eve document is much more readable with : instead of = inside of records.

Again, sorry for the long delay in responding, I'll try to monitor RFCs a little more closely from now on :)

Corey

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests