Mavo functions
We know that naming expressions to reference them elsewhere has numerous uses. Functions are the natural next step, which allows us to parameterize these expressions that are reused.
Is this worth doing?
Authors can already define properties whose values are expressions, and this is very heavily used. This is exactly the same concept, except the expression can be parameterized and thus reused with different data.
This enables use cases like…
- Custom output formats, e.g. date formats
- Reusable data transformations, such as:
- math operations
- mapping/grouping/filtering for different lists (that e.g. share a common property name).
- Reusable data actions, such as:
- A "consolidate" action that replaces multiple list items with one
- a "soft_delete" action that moves list items to a special trash list.
Syntax ideas
Design space
Decomposing the different decisions gives us the design space.
HTML element or special function?
There were some concerns on whether HTML is the best place to extend expression syntax.
I think HTML is precisely the right place for extending expression syntax, because:
- Fundamentally, functions and operators are used for a) reactive computation, i.e. transforming a set of inputs to an output value, in which case they have no side effects and b) Data mutation actions. A function definition is neither. It would be a function with side effects, that is not an action, which seems to break every conceptual paradigm in formula languages.
- HTML is basically the "UI" of Mavo, in the same way that spreadsheets are a GUI around formula syntax. In terms of floor and ceiling, it's HTML < Formulas < JS for both. The more we can relegate to the UI, the smoother the learning curve. The downside of relegating things to the UI is usually trading off power, which does not seem to apply here (if anything, the HTML-based proposals are more powerful).
- Maybe this is not fundamental to using functions for this, but so far all proposals around using functions suffer from the same usability issues:
- No clear separation of the arguments and the body of the function, making it hard to read
- It's a function definition, but looks like a function call. What would the return value even be? What happens if you use this in the middle of another expression?
- No obvious way to have variable arguments, restrict the types of the arguments, or provide default values.
- Mandatory naming of arguments (not a big issue, but would be nice if people could get started without even having to do that).
Decision: HTML element.
What element to use?
mv-functionattribute on any element<mv-function>custom element- Specific existing element, e.g.
<meta>,<template>
Augmenting existing elements is typically an advantage when:
- the behavior produces output, and we can just direct it to said element
- it may be in contexts where there are limits on what elements are allowed
- The purpose or behavior of the original element helps us in some way
Neither of these is the case here. Furthermore, a custom element allows more flexibility, and attributes don't need to have mv- prefixes.
I think as a design principle, we should use elements for abstract constructs like these that have no output (e.g. I envision an element for components too).
One exception is the idea to repurpose groups for these, by being able to point to a group to turn it into a function, with one of its computed values as the output. In that case, using an existing element may make sense.
Decision: Custom <mv-function> element.
How to specify the return value?
- Element contents,
contentattribute,- special property name (e.g.
output,$return)
Considerations:
- Ideally we want two ways: one formula-first, and one content-first
- Ideally we want a syntax that allows optionally leveraging other Mavo features, e.g.
mv-value,mv-if, properties for helper variables etc.
Decision: TBD
How to specify arguments?
This includes things like:
- Argument names
- Argument order
- Optional metadata (e.g. default values, type, etc.)
Ideas so far:
- Predefined variables, e.g.
$arg1,input2etc. - Nested properties, with their values being default values
- Special attribute, e.g.
mv-params - ~Multiple indexed attributes (e.g.
mv-param-1)~ O(N) in terms of user effort to reorder parameters - Multiple named attributes (e.g.
mv-param-foo)
Decision: TBD
Proposals
- Original design (below):
propertyon element to name function,outputspecial property for return value, other properties define params (why not local variables??) - One liner definition:
mv-functionattribute on any element, return value via either contents or attribute, parameter specs via attributes → Argument reordering is a pain, let's not do this define()by @kargermv-parameter-fooby @DmitrySharabin:lambda()by @DmitrySharabin- New
<mv-function>proposal
Original message
As a Mavo author, I've often felt the need to define my own expression functions, a privilege currently only available to those who know JS. For example, if you have multiple date properties, that you want to share the same format. Or, an expression that generates a custom link based on data. Or transforming data so I can render them on another template using mv-value. The possibilities are endless. If I can use expression functions, I should be able to define my own in terms of expressions and HTML.
I'm thinking of something like:
<mv-function property="myDateFormat">
<meta property="output" content="[month(input)] [ordinal(day(input))], [year(input)]">
</mv-function>
or without custom elements:
<div mv-function="myDateFormat">
<meta property="output" content="[month(input)] [ordinal(day(input))], [year(input)]">
</div>
and then you can call it in other expressions like [myDateFormat(date)] (where date is a date property).
Additional arguments could be input2, input3 etc. Or we could have named arguments via properties:
<div mv-function="myDateFormat">
<meta property="date" />
<meta property="output" content="[month(date)] [ordinal(day(date))], [year(date)]">
</div>
OR what if every group with an output property creates such a function? Then people don't need to learn ANY new syntax! Like this:
<div property="myDateFormat">
<meta property="date" />
<meta property="output" content="[month(date)] [ordinal(day(date))], [year(date)]">
</div>
Thoughts?
So I see the appeal of being able to define your own functions. But I'm uncertain about the right syntax direction. There's a slippery slope here. Obviously once I start defining functions like yours, I'm going to discover I need conditionals. And then I may need some named variables to hold the value of common sub-expressions, etc. Before you know it you've re-implemented scheme, only with < > instead of ( ) .
There is a simple alternative---let them define a javascript function in a script tag. it immediately becomes part of the mavo namespace and callable from a mavo expression, right? It's not clear to me that the additional syntax they'll need to write (elementary) functions in js is more complicated than what they'd learn to write them in mavo.
For the closest analogy, looking at writing functions for spreadsheets: they're called macros, and it's considered a super-advanced black art (regular users never do it). And it's done in an entirely different language, VBA.
Obviously once I start defining functions like yours, I'm going to discover I need conditionals. And then I may need some named variables to hold the value of common sub-expressions, etc.
Which you already have, as part of normal Mavo syntax! That's the beauty of it.
There is a simple alternative---let them define a javascript function in a script tag.
That means they need to know how to write JS, which is not the same as utilizing Mavo expressions. E.g. the expression for the date format above would become in JS:
function myDateFormat(date) {
return Mavo.Functions.month(date) + " " + Mavo.Functions.ordinal(Mavo.Functions.day(date)) + ", "
+ Mavo.Functions.year(date);
}
Oops, wrong issue number
For the closest analogy, looking at writing functions for spreadsheets: they're called macros, and it's considered a super-advanced black art (regular users never do it). And it's done in an entirely different language, VBA.
Well, some people think it should be easier: https://dl.acm.org/citation.cfm?id=1370867 :)
I like the analogy with Excel spreadsheets. We all know that Excel is a great prototyping tool for small applications. It would be great if Mavo could allow users to write dashboard applications with "spreadsheet functions".
I think Mavo.Functions should include or let users include other simple utility libraries such as https://lodash.com/, https://vocajs.com/, http://momentjs.com.
For the design, have a look at lowdb. It is an interesting example, it is a file-based DB that exposes all the functions that are in lodash.
From the user point of view: "Oh, I know how to solve that problem with arrays. It is the same if I want to do it with my database".
For the design, have a look at lowdb. It is an interesting example, it is a file-based DB that exposes all the functions that are in lodash.
We need to keep in mind who Mavo's target audience is and what kind of apps we want them to build with this tool. You're assuming that people who use Mavo would also use or be comfortable using a database or any other analogy to more complex programming and I think that's not the point.
It is a very slippery slope in terms of the functionality that you require these functions to have. I'd much rather look at how can we surface some existing functionality that does the same thing than libraries or if we can improve what's already there.
If the user really needs lodash, voca or the full moment llibrary then I don't see a problem with them dropping to Javascript to use them. It goes beyond what someone in the target audience for Mavo would normally do when building an app with Mavo.
@caraya is making excellent points here. Also, whoever knows JS and wants to use lodash can just include them and then do $.extend(Mavo.Functions, _) and done. Similar for the rest.
Please keep in mind that this thread is about how Mavo authors (who do not necessarily know JS) can define their own functions and whether this is useful. Can we stay on that topic, please? :)
For other feature suggestions, you can always open a new issue.
In my opinion. It is easier to add a JS function if the Mavo user knows the concept of a function, than to hack HTML tags. You already require the user to know HTML and CSS, which are already "hard".
If you look at the Mavo.Functions object in functions.js you already have utility functions that are covered with lodash, voca and moment.
Questions arise when looking at the code:
- Why did you select to implement those specific ones?
- Are they well tested and optimal?
- Are they documented?
As a project maintainer, you should just include a string, number and date utility library and wrap those calls instead of reinventing the wheel. In the mavoscript documentation, you could just say "advanced user who need more functions can use lodash calls" like lowdb library does in their documentation.
If you let the user plug a popular library (you don't even need to tell them to it is lodashand to learn it) you are sure to cover most usecases, similar to Excel having a large panel of utility functions that don't require you to write VBA to solve common problems. The libraries I mentioned also don't require you to be proficient in programming if you want to use one of their methods.
Check also formula.js which is a JS implementation of all Excel function.
In Excel, you don't need to write functions, just do plenty of cells with intermediate values, which you can already do using <input property="..."> as variables. You can also use <input type="hidden" property="..."> to hide intermediate values.
So for the initial problem,
<time property="birthday" datetime="2014-06-01"></time>
<input type="hidden" property="formated_date" value="[month(birthday)] [ordinal(day(birthday))], [year(birthday)]">
Basically when building Mavo, you are investigating the same design problems that a spreadsheet application has. Knowing this, retro-engineering the design of Google Sheets could be a great idea.
It is not off topic, you need to consider all the facts.
PS. I am trying to make a clone of some functionalities of Bloomberg Watchlist app using your framework. It is an spreadsheet-style application that lets you track stocks you invested in. You can have a look in this repo: https://github.com/mycaule/portfolio
As a project maintainer, you should just include a string, number and date utility library and wrap those calls instead of reinventing the wheel. In the mavoscript documentation, you could just say "advanced user who need more functions can use lodash calls" like lowdb library does in their documentation.
Basic functionality is already available and documented:
All Javascript functions are available unprefixed to Mavo
I think the most important part is that you, as the user who needs them can add them to your application... If we follow your logic then Lea would have to add whatever library a user wants because it's easier for them to use; I personally prefer Backbone rather than Underscore. Where do maintainers draw the line?
If I'm reading Mavoscript versus Javascript correctly, Mavoscript will jump to Javascript for advanced expressions. The one doubt I have is whether the automatic Javascript parsing includes external libraries or only the functionality built into the browser's JS engine.
As far as Mavo's design principles, it has more to do with easing entry into web design and development, not a programming tool. The spreadsheet analogy only goes so far in explaining the purpose of Mavo. I'll defer to Lea (who create Mavo to being with) to speak more about his.
@mycaule There are a number of reasons why the default set of functions does not come from a library:
- Different target groups. The target group of utility libraries is typically JS developers. Our target group is anyone who can write HTML. This affects many design decisions.
- Mavo Functions need to work well with all the possible data types that might be returned from Mavo properties.
- Mavo Functions need to avoid side effects.
- Mavo Functions need to preserve each object’s
Mavo.toNodeproperty as much as possible, which will become important once we implement Custom Data Actions
Are they well tested and optimal?
This is part of the testsuite: https://test.mavo.io/functions.html Of course more tests would be better, but that applies to everything.
Are they documented?
https://mavo.io/docs/mavoscript Of course more docs would be better, but that applies to everything.
As a project maintainer, you should just include a string, number and date utility library and wrap those calls instead of reinventing the wheel.
Is that an RFC 2119 “should”?
"advanced user who need more functions can use lodash calls"
That’s the thing, it shouldn't just be advanced users that can use expressions. In our user study we observed that most HTML authors with no programming experience could use simple functions. This is a major factor in designing Mavo’s functions.
You can also use to hide intermediate values.
Or <meta property="..." content="..."> which is the recommended way. But hidden inputs work too.
PS. I am trying to make a clone of some functionalities of Bloomberg Watchlist app using your framework. It is an spreadsheet-style application that lets you track stocks you invested in. You can have a look in this repo: mycaule/portfolio
Exciting! Would love to see the final result!
Bottom line is, I mentioned above that you can use any utility library you want in Mavo expressions, all you have to do is include it. You can even use its functions with the same syntax as native Mavo functions (i.e. no _.) by running one line of script to add them onto the Mavo.Functions object. For anyone aware of moment.js or lodash, a line of JS should be no problem. Therefore, I’m really not sure what's the issue: Is it that what I said above is unclear? Is it that you want this mentioned in the docs? Is it that you want everyone to use lodash? Is it that you don't like the default Mavo functions and you want them to disappear? What is it?
And I'm sorry, but it's still off-topic. The default set of functions is orthogonal to whether Mavo authors can create their own functions and how. This has nothing to do with which utility library we use, even if we used lodash or whatever, people might still want to re-use their expressions, just like people want to do this in spreadsheets (just google "custom excel functions" for an idea of how many).
Most of what @caraya is saying is correct (thank you!).
If I'm reading Mavoscript versus Javascript correctly, Mavoscript will jump to Javascript for advanced expressions. The one doubt I have is whether the automatic Javascript parsing includes external libraries or only the functionality built into the browser's JS engine.
It includes everything that would be available to normal JS. It don't even need to jump to JS for that, if the function exists it will be used. It's syntax-level stuff that it will jump to JS for, e.g. array.map(x => x + 1), because MavoScipt doesn't support function definitions.
As far as Mavo's design principles, it has more to do with easing entry into web design and development, not a programming tool.
"Easing entry into" implies that we see Mavo as a temporary, educational solution until people are ready to move into "real coding". That’s not the case, though of course it could work that way and it does for many people. Our vision is that Mavo (+ HTML & CSS obvs) eventually will be sufficient for creating most data-driven web applications that people need to make, and learning JS and backend stuff will mainly be needed for specialized or high performance tasks, just like the other abstractions that we're used to today. E.g. we don't see C++ as a stepping stone to Assembly and we don't see CSS as a stepping stone to WebGL. Of course, we're still quite far from that vision, but it does guide our decisions :)
These are just suggestions by the way. Your do your product management as you like.
I also that one with Mavo (spreadsheet type application with visualisation). https://github.com/mycaule/can-we-row
Had to write server side code, but realized in the end I could package all the HTTP calls client side with parcel bundler like I did in the portfolio app.
Some observations with those two similar apps:
- HTTP calls to REST APIs cannot be written easily with Mavo, you have to use a JS transpilation technique, the link to Mavo properties are tricky
- currently complex charts cannot be declared only with HTML tags (I used Baidu's
echartslibrary) - I still have to refresh the page in some cases where the dataset changes.
@mycaule I would love to look into these problems, but you will need to open new issues for them.
Revisiting this, since it becomes even more needed with data formatting I think this thread has overcomplicated it, which is why the feature stalled. I literally cannot count the number of times I've needed this in the simplest way, and I had to escape to JS, which is harder than it looks because the values Mavo passes around are not the primitives one would expect, but complex objects to preserve references. Mavo expressions hide this complexity automatically, but once you're in Javascriptland, all of it is exposed.
We should add something very simple, like:
<div mv-function="functionName" mv-parameter-1="arg">[doStuff(arg, propertyName)]</div>
Any element can be used. E.g. <meta> might make more sense:
<meta mv-function="functionName" mv-parameter-1="arg" content="[doStuff(arg, propertyName)]">
Anything that requires more complex stuff can be done via JS.
Things to be decided:
- Any identifiers not named via
mv-parameter-*are evaluated as properties. But in which group? The group the function is defined in or where it's used? - Does this need to be live? Is there any way these might change? It's certainly far easier to implement this if not.
- How to specify default values for parameters? A different attribute or something like
mv-parameter-1="arg = defaultValue? - With this syntax, if one has many parameters and wants to change one's position, they would need to rename many attributes. It would be better if there was a syntax to specify parameters where one only had to make one edit in that case. OTOH, if we don't require the numbers to be contiguous, one could leave gaps between them (e.g.
mv-parameter-10followed bymv-parameter-20etc), similarly to how one specifiesz-indexin CSS. Though that's a bit ugly. Perhaps they could all be specified via one attribute, like `mv-params="arg1, arg2 = defaultValue2, arg3, ...", but then are we breaking the HTML paradigm too much?
Thoughts @karger @DmitrySharabin?
I want to be sure I understand what you are proposing. This is a way to define functions in mavo? So your example defines a function named functionName that takes a single argument "arg", and is evaluated by plugging arg into dostuff?
It seems a little odd that we are using html/angle bracket syntax to define functions that get used in js/parens syntax. Would it be more self consistent to have some kind of "define" operator that gets used in expression evaluation contexts? e.g. [define(functionName(arg1), expression-on-arg1)]
I recommend reading this paper for insight. They are tackling a parallel problem, of letting users define new functions in spreadsheets.
Here's one question from thinking about what they did. Your syntax above lets you define functions with one-line bodies, but what if you have a much more complicated function that e.g. needs to define some intermediate values that are then combined into the final result? It would be nice if we could e.g. use mv-value elements inside the function body (which therefore would need to incorporate html, not just a mavo expression).
so for example a syntax like this:
<div mv-function="functionName" mv-arg="arg1" mv-value="ret">
<meta property="computed" mv-value="2*arg1">
<meta property="ret" mv-value="1+computed">
</div>
The intent of this syntax is to say "the function, when invoked, binds the local arg1 to the first named parameter of the function, then returns as its result the value of the "ret" property."
But this approach still mingles html with what fundamentally seems like an expression-language issue. I could still see an argument for just having a javscript-like representation---perhaps one that elides the difference between the primitives the author expects and mavo's "complex objects to preserve references" that you mention above.
I'll also bring in the somewhat related issue of templates. Just like we write functions to avoid repeating ourselves, sometimes we want to display the same kind of information the same way in different places on a page. Mavo makes this easy in some cases---e.g., we write the template once for a collection which then gets replicated for each item. But what if I want to display same item same way in multiple locations. Wouldn't it be nice if I could define the presentation once and use it both places? We already do this for editing templates with mv-edit; how about for viewing as well with e.g. an mv-template attribute? Which would say to use the same data model for the template as well as the same presentation? This would make it much easier to e.g. present hierarchical structures such as a family tree (or a threaded comment list?).
I am sorry for being so slow in response (I was a bit busy preparing my first talk about Mavo).
I read all the thread carefully, and here are some of my thoughts:
- What if for defining parameters we use this syntax:
mv-parameter-parameterName="defaultValue"? If the corresponding parameter has no default value, we can simply write:mv-parameter-parameterName. That would let us use the idea that Apple has already implemented in the Swift language: when we call a function, we simply name the parameters and give them their values (like so:parameterName1: value1, parameterName2: value2, ...orparameterName1 = value1, parameterName2 = value2, ...). For example,
<div mv-function="sum" mv-parameter-first mv-parameter-second="0">
[first + second]
</div>
...
<p>10 + 15 = [sum(first: 10, second: 15)]</p>
...
It would let us change the order of the arguments without breaking the function logic by doing like so: <p>10 + 15 = [sum(second: 15, first: 10)]</p>.
We can also let users don't specify the names of the arguments. In that case, we interpret arguments passed to a function in the order they were defined. So we consider sum(10, 15) and sum(first: 10, second: 15) having the same meaning.
I think that would let us solve the problem Lea mentioned:
...if one has many parameters and wants to change one's position, they would need to rename many attributes. It would be better if there was a syntax to specify parameters where one only had to make one edit in that case.
- I enjoyed the article David shared with us! Thank you very much. That was really useful. And I also have some ideas which correlate with David's ones.
- If we define a function with the help of a non-empty element (like
<div>) do we really need to use square brackets as if it was an expression? What if we omit them as we do so in themv-valueandmv-actionattributes? I think that won't break our logic but will let us use square brackets in a function body if we need it. So we would have a syntax which is a bit simpler:
<div mv-function="functionName" ...>
doStuff
</div>
...
- I agree that it would be nice if we could use intermediate values in a function body, but I'm a bit confused with the amount of HTML elements I need to use to write a rather simple function. What if we could interpret a function body as a paragraph text. To be more precise:
- every statement is a separate line of code
- if we want to write multiple separate statements on a single line, we must use a separator (semicolon as in Swift or Python or colon as in Basic or VBA or any other we decide to use)
- we can define local variables merely writing their names and giving them values (as in Basic or VBA), so they can be used later in the code as if they were defined as function arguments.
- I also agree that we need to define somehow the returned value. I think we could use a syntax similar to that we use to define parameters:
mv-return-retName.
Combining points 2 and 3 we can rewrite David's example like so:
<div mv-function="functionName" mv-parameter-arg1 mv-return-ret>
computed = 2 * arg1
ret = 1 + computed
</div>
I'd like also mention that if we didn't define the return value explicitly, the last statement of the function body (the last line) could be interpreted as the function value:
<div mv-function="functionName" mv-parameter-arg1>
computed = 2 * arg1
1 + computed
</div>
Defining a function's return value (variable) explicitly would let us define functions that return more than one value (like in Swift). For example,
<div mv-function="stats" mv-parameter-data mv-return-avg mv-return-minimum mv-return-maximum>
avg = average(data)
minimum = min(data)
maximum = max(data)
</div>
We could get these values using, e.g., dot notation: [stats(list(1, 3, 12, -8)).avg].
I'm not really sure about use cases. Just my thoughts. :)
- Last but not least.
Any identifiers not named via mv-parameter-* are evaluated as properties. But in which group? The group the function is defined in or where it's used?
Do you think we need to define impure functions? I think they are a bit difficult to maintain for inexperienced programmers. No?
If you implemented it as stated, would it be possible to document it within a Mavo? In other words, can you turn it off so you can show examples, inside a Mavo?
There is one more thing I was thinking about. Wouldn't it make sense to recommend users to define functions, which body is built out of more than one line, by using the <template> element? Of course, if we decide to implement this feature.
@DmitrySharabin Interesting. Why the <template> element? What's the reasoning?
Well, the main reason (as I see it, I might be wrong) is the analogy with the <meta> element:
- its content is not rendered by the browser (by default)
- inside the
<template>element we can define functions and not adding new semantics to the existing elements (if we define a function inside one of them instead) - if a function is written with the help of more than one line, by putting it inside a non-empty element (between the opening and the closing tags) would increase its readability.
Why an analogy with the <meta> element and not a <meta> element itself? 😀
The <meta> element is perfectly fine for most cases. I agree. 😅 I was just thinking about some complex functions David mentioned. Like this one:
<div mv-function="functionName" mv-parameter-arg1 mv-return-ret>
computed = 2 * arg1
ret = 1 + computed
</div>
Is there a way to define these kinds of functions in the <meta> element? We need a way to separate statements that define the function body, no?
Thinking of another issue we have, I came up with an idea of another way of defining custom functions—via a special function.
Microsoft and Google added the LAMBDA() function to their function language for quite some time. So, we might follow their way.
Suppose we added to Mavo the lambda() (we might want to name it some other way) function, which has the following signature: lambda(par1, par2, ..., parN, computations), where computations is an expression depending on par1, par2, ..., parN. All parameters, except computations, are optional. The result is a function that expects N arguments to perform computations and is ready to be invoked in other expressions.
In that case, we might have something like this:
<meta property="my_function" content="[lambda(x, y, x + y)]" />
...
[my_function(1, 2)] <!-- 3 -->
[my_function(list(1, 2, 3), list(4, 5, 6))] <!-- list(5, 7, 9) -->
The lambda() function might also be used inline, like so: [lambda(x, y, x + y)(1, 2)]. It's not the most readable expression, though.
In any case, the lambda() function is not the most straightforward concept for authors to absorb. The same is valid with spreadsheets as well.
Another issue with this approach is that we might not use the property attribute as the authors used to. I'm unsure if something like “attribute overloading” applies here. 🤔 Or should we define functions using the earlier proposed mv-function attribute?
@DmitrySharabin Nice thinking, how other products solve this problem is definitely very useful.
However, I’m not a fan of the ergonomics of this syntax. Remember "make common things easy and complex things possible"? This seems to do neither.
- No clear separation of the arguments and the body of the function, making it hard to read
- It's a function definition, but looks like a function call. What would the return value even be? What happens if you use this in the middle of another expression?
- No obvious way to have variable arguments, restrict the types of the arguments, or provide default values.
- Mandatory naming of arguments (not a big issue, but would be nice if people could get started without even having to do that).
But thinking about this again after all these years, I think I have a…
New proposal
A <mv-function> component that works as follows:
nameattribute defines the name of the functioncontentattribute (for formula-first syntax) OR element contents (for content-first syntax) define the output- Params can be defined either via a
paramsattribute (list of names with optional default values) OR, for more complex things, via<mv-param>children (which would allow types etc). - A special
$argsproperty would include all supplied arguments (whether some were named or not)
Examples
A bit formulaic due to lack of time — also we already have a hypot() function as we adopt all Math functions, but let's ignore that briefly).
In some I’m assuming dot notation is implemented (see #1009).
Compact syntax
Without naming arguments:
<mv-function name="hypot" content="sqrt(pow($args[0], 2) + pow($args[1], 2))"></mv-function>
Naming arguments:
<mv-function name="hypot" params="a: 1, b: a" content="sqrt(pow(a, 2) + pow(b, 2))"></mv-function>
Specifying the body in the contents:
<mv-function name="display_repo_name" params="repo">[
if(repo.startsWith("leaverou/"), repo.from("leaverou/"), repo)
]</mv-function>
Variable number of arguments
Beyond dot notation, this also uses a map() function that works with a special property for the current value (see #1010):
<mv-function name="hypot" content="sqrt($args.map(pow($value, 2)).sum())"></mv-function>
Extended param syntax
<mv-function name="hypot" content="sqrt(pow(a, 2) + pow(b, 2))">
<mv-param name="a" required type="number"></mv-param>
<mv-param name="b" default="1" type="number"></mv-param>
</mv-function>
Combined with contents:
<mv-function name="display_repo_name" params="repo">
<mv-param name="a" required type="number"></mv-param>[
if(repo.startsWith("leaverou/"), repo.from("leaverou/"), repo)
]</mv-function>
Questions
- Maybe also
$argas a shortcut for$args[0]to make single-arg functions even easier? - Should we introduce any special handling rules for whitespace? (see the "contents in the body" examples, without putting the braces like that we'd also get whitespace as the result)
- What happens if both
contentand element contents are specified? Does one override the other (presumablycontentwins) or are they composed somehow? - Can we nest
<mv-function>s? (presumably not in the MVP, but could be useful later)
I'm not an expert on JS or Mavo, but I'm a programmer, and I can't tell what your examples do. The square brackets are confusing, the elements that start with mv- look like custom HTML elements instead of part of Mavo, and I can't see whether you are defining a function or invoking it inline. Also, how would one specify a string literal in an attribute?
This all seems way too complicated. I would rather just use JS if I have need of a function. Perhaps all that is needed is an interface to the Mavo object or the this pointer? (sort of like jQuery)
@joyously
I'm not an expert on JS or Mavo, but I'm a programmer, and I can't tell what your examples do. I would rather just use JS if I have need of a function. Perhaps all that is needed is an interface to the Mavo object or the
thispointer? (sort of like jQuery)
I think there may be a misconception here. While we generally try to cater to both, Mavo is primarily a language for people who don't know JS or other imperative programming languages, and novices are above programmers in the priority of constituencies, since there are so many other tools that cater to programmers.
And it's not uncommon to see that syntax that works for novices confuses programmers and vice versa. As an example, in the first Mavo study, novices found the idea of adding mv-multiple on an element to make it a collection natural, while programmers were trying to figure out how to iterate.
The square brackets are confusing, the elements that start with
mv-look like custom HTML elements instead of part of Mavo, Also, how would one specify a string literal in an attribute?
These are syntactic constructs that apply far more broadly to Mavo.
- You specify strings in attribute expressions all the time
mv-is a prefix used all over Mavo (on classes, attributes, etc.) and for constructs defining a new entity, I think elements are very appropriate (e.g. we can also use<mv-component>for- Brackets are how you define an expression among content
and I can't see whether you are defining a function or invoking it inline. This all seems way too complicated.
I would love to see some non-complicated syntax that makes it the distinction more obvious!
novices are above programmers in the priority of constituencies
Yes, I understand that, but I think the novice would not make any sense at all out of those examples. I barely could, even with you explaining it. Reinventing the wheel...why?
Brackets are how you define an expression among content
That was not obvious. It was confusing.
You specify strings in attribute expressions all the time
Yes, but you are suggesting to put code into an attribute, which is delimited by quotes. What if the code needs quotes for a string? I think the novice will have trouble and not know to put the code as content (and also put square brackets around it).
mv-is a prefix used all over Mavo (on classes, attributes, etc.)
So far, it seemed that it was only attributes, not elements, which makes it much easier to work with.
I would love to see some non-complicated syntax
So would I. I don't really see where this is needed, so a better use case would be nice, so I can understand if you define a function where you use it or in a separate file you reference, or what. If it's inline, why make a function? (maybe reread older previous comments above)
Yes, I understand that, but I think the novice would not make any sense at all out of those examples. I think the novice will have trouble and not know to put the code as content (and also put square brackets around it).
What novices find confusing is something we determine via user testing, not speculation.
The output-value-as-content is mainly useful when concatenating one or more expressions with regular text (i.e. the brackets would not encompass the whole content), for expressions like those in the examples, I think content is a better choice.
I barely could, even with you explaining it.
As I explained above, it doesn't follow that if a programmer cannot understand something, a novice also won't. Programmers are not the target group for this feature, as they can define JS functions with only a little more friction, and more power.
Yes, but you are suggesting to put code into an attribute, which is delimited by quotes. What if the code needs quotes for a string?
Again, this is exactly how Mavo works already. There are directive attributes (like mv-value) whose value is always interpreted as an expression, and regular attributes, which can contain expressions in []. In both of these cases, including strings in these expressions is common, and is typically done by inverting the type of quote.
Contrived example that showcases both:
<span mv-value="sum(numbers)" style="color: [if(foo, 'red', 'green')]">0</span>
So far, it seemed that it was only attributes, not elements, which makes it much easier to work with.
It's also used for custom properties (and JS events). It has always been the plan to use it on custom elements as well, when it made sense for the use case.
I’m really not sure what makes <mv-function> harder than <div mv-function> except that it's a new pattern (that we plan to use for other things too).
So would I. I don't really see where this is needed, so a better use case would be nice, so I can understand if you define a function where you use it or in a separate file you reference, or what. If it's inline, why make a function?
I’ve started a list in the first post, and we can expand it as we stumble on more use cases. But basically, it's the same concept as a property whose value is an expression; it just allows you to parameterize the expression. For example, one of the most common use cases is defining custom formats to output data, e.g. date formats.
(maybe reread older previous comments above)
I actually spent some time today to re-read the whole thread. Here’s some high level comments to things that have been said:
- Proposals seem to overlap a lot, I tried to decompose the design space in the first post, so we can look at the options individually.
- Some concerns that HTML is not the best place to extend expression syntax (@karger). I disagree, and I explained why here. Also, HTML is basically the "UI" of Mavo, in the same way that spreadsheets are a GUI around formula syntax. The more we can relegate to the UI, the smoother the learning curve. The downside with relegating things to the UI is usually power, which does not seem to apply here. Also, even conceptually using a function doesn't seem like a good idea. Fundamentally, formulas are used for a) reactive computation, i.e. transforming a set of inputs to an output value and b) Data mutation actions. A function definition is neither. It would be a function with side effects, that is not an action, which seems to break any conceptual paradigm in formula languages.