Open AnthonyDGreen opened 4 years ago
About Target-typing:
There is an easy solution; we can use a special namespace (say: system.XML.Literals.Common
) to define all supported types. It can be imported by default and referred to by xml:
for example, so, we can write:
Dim control = <xml:TextBox Text="Blank" />
the namespace system.XML.Literals
can contain other namespaces for different technologies scuch as Xamarin, WPF, Html, … etc. Developers can extend this by adding there own namespaces. The basic namespaces should be used directly through thier known shortcuts, such as xmr:
, wpf:
, htm:
.
If one need to get rid of the suffix, he can use an Imports statement in the pages to specify that all the xml literals in this page uses this default name space, or uses xmln
in xml to specify the default namespace.
Any other root tag that doesn't belong to these namespaces, should be treated as usual XElement.
This will maintain backward compatibility not to break any working code.
So,
Dim x = <TextBox Text="Blank" />
Dim y = <xml:TextBox Text="Blank" />
x
will be an XElement
, and y
will be a TextBox
.
The root element will force the namespace on its noods without the need of the prefix.
Dim z = <wpf:Grid>
<TextBox Text="Blank" />
</wpf:Grid>
If this is confusing anyhow, we can use a special root for typing:
Dim x = <wpf>
<TextBox Text="blank" />
</wpf>
The root will resolve the namespace, and the first node will resolve the desired type.
The names of these roots will be reserved, and user defined namespaces can define their roots by some attribute or so.
@VBAndCs,
I'm still thinking about what you said but wanted to say thanks for being first to leave feedback!
@AnthonyDGreen Thanks for the article linked from #488 Pattern based programming will help us write dynamic code more easily than using reflectors, currentdomain, unloading assemblies etc. Yes this is a lot bigger thing.
This proposal is a follow-up to the idea introduced in #110 and in this blog post. I got the idea from Lucian, who got the idea from Jonathan, who got the idea from a VB user whose name is lost to history. Could also be part of a solution addressing #397.
VB.NET's existing XML literal syntax was added in VB2008 to provide a more productive, declarative syntax for building XML documents and fragments using the XLinq (
System.Xml.Linq
) API object model; the goal of the proposal is to generalize this syntax to be useful for building documents, fragments, and programs in other XML-based and XML-like DSLs using other object models. Rather than being tightly coupled to the XLinq object model specifically, or any fixed set of object models known by the compiler in advance, the intent is to introduce a level of indirection in the interpretation of an XML literal as well as a an abstraction sufficiently expressive that an open-ended set of APIs/DSLs could be used from the language without further modification of the compiler. This is consistent with the patterns used for LINQ and elsewhere in the language.Example 1.
In this example (taken from a prototype demo linked below), an XML literal fragment is being used in a VB code file (not a XAML file) to initialize the
MainPage
of a mobile application's navigation stack. Note that with the exception of the embedded expression, the expression otherwise uses the same XAML syntax (and XML dialect) that is used in XAML documents. Rather than produce anXElement
, this literal produces a strongly-typedContentPage
object initialized correctly with the expected descendants. This is done without run-time parsing and can benefit further from compile-time type checking. Further, the compiler has no special knowledge of the object model being targeted or intricacies of XAML such as type converters, markup extensions, or data-binding, yet achieves the same results.Table of Contents
I. Prototype, Motivating Examples, High-Level Design Description
To illustrate some potential applications I have prototyped an implementation of an straw-man design of an XML Pattern. I thoroughly expect that the final shape of any XML Pattern will and should change significantly from this initial draft (in ways that are elaborated on in the later sections of this document). However, the mechanism of the draft are sound and sufficiently broad to support realistic motivational examples. I have constructed 3 such examples and recorded a series of videos demonstrating them (each 5-6 minutes in length) and a companion blog post per video further elaborating on the demo. They are as follows:
Mobile Apps w/ Xamarin.Forms [5:42] Shows how the pattern can be applied to an existing hierarchy of types designed to be used via XAML. Companion post
ASP.NET Core VB View Engine w/ Web Controls [5:46] Shows how the pattern can be applied to a new hierarchy of types modeled to be used in and extend (X)HTML5. Companion post
Client-Side VB.NET in Browser [5:42] Shows how the pattern enables a library author to convert and execute snippets of VB code in a web browser using techniques similar to LINQ query providers. Companion post
Code for the Prototype and Demos
The Xamarin.Forms example was built and recorded using code from branch prototypes/vb-pattern-based-xml in my fork of the Roslyn repo.
The ASP.NET Core Web Controls and Client-Side VB.NET in Browser examples were built and recorded using code from branch prototypes/vb-pattern-based-xml-top-level in my fork of the Roslyn repo which includes commits from the "Top-Level Code" prototype introduced in issue #446. That proposal is not strictly necessary for any of the examples but does synergize well with it.
The demo code is on GitHub at AnthonyDGreen/vblang-prototype-demos in the pattern-based-xml folder.
NOTE: The code for all demos is in that folder even though the ASP.NET demos require a different branch than the Xamarin demo!
Further potential examples
Though I chose to record examples using HTML and XAML, as they are likely familiar and resonate with a broad set of viewers, one can easily imagine others such as:
Happy to hear ideas from others! Want to consider a broad set of potential applications.
High-level description of the pattern
Fundamentally these 6 points describe the what of the feature. How the compiler executes them can (and must eventually) be described in excruciating detail. But for the purpose of feedback (and the prototype) the translation is much simpler. A key element of the current implementation is a series of fallbacks. Meaning, the pattern can be satisfied with some more advanced helpers but in their absence will fallback to a simple convention that allows a lot of existing library code to be used without modification or extension.
Right now this causes the compiler to call the constructor of a type named
Window
(case-insensitive). The type must exist and there's no indirection for a factory method or for the type to not exist at all.Translation details
The prototype demostrates that a fairly straight-forward translation is possible but the actual details used in the prototype are minutiae at this time. Eventually they'll need to be written out but they would massively bloat this issue to detail inline. I've uploaded an example translation as a gist: What you write | What the compiler generates.
Note that for the XAML example you do need separate helper methods per attribute/property to avoid the compiler needing to understand any relation between the
Control.Text
property and theControl.TextProperty
field which is required for data-binding, dynamic resources, etc.II. Background and Discussion of XML vs Alternatives
Why XML?
The simplest answer is that XML is already in the language. At the moment it's limited and this generalization means getting far more utility out of what we already have.
Inter-operability
The next interesting answer is, in my belief, the same answer that motivates the use of XML in XAML; inter-op. Originally XAML as used by WPF was beneficial compared to the VB.NET (or C#) based code-behind imperative code used by WinForms because XML was a language of inter-operability between the tooling in Visual Studio and Expression Blend. That same inter-operability applies both because tooling can more easily understand XML than VB and because existing dialects read by humans already use XML or XML-like languages (such as XAML in Xamarin or HTML in ASP.NET Core).
I'll give two hypothetical examples of this.
Example 1. In (VS) editor rendered MathML Imagine being able to embed a fragment of MathML--an ISO standard markup language for describing mathematical notations in a VB program for some intended execution. Perhaps taking the output of tooling used by some financial analysts, mathematicians, or scientific modelers. By leaving the formulae in MathML a Visual Studio extension can render it in editor using the same rendering technology used elsewhere and the otherwise cumbersome syntax can be presented in a manner that's most readable to the developer and subject matter experts while still having a living executable artifact which can potentially be extended with code.
Example 2. Polyglot markup Combined with the top-level code feature it's very easy to imagine a file which contains only polyglot markup--a file which is both valid as VB and valid as some other markup file such as XAML. That means the same code could be valid in a VB project and still worked on using some 3rd party tooling so potentially the previewing of a
.vbxaml
file could be handled by the Xamarin, WPF, or some generic HTML previewer tooling but the run-time support could be pure VB.Advantages over XML literals as they exist today
The key advantages being pattern-based would confer beyond what XML literals can do today are:
Producing objects of types other than those in the XLinq API, e.g.
XDocument
,XElement
,XAttribute
, etc.Enabling the compiler and tooling to make use of richer type information than that provided by the XLinq API and to persist that type information at run-time.
The Xamarin.Forms and Web Controls examples listed above highlight the benefit of
#1
straight-forwardly. They construct objects that are then consumed by other systems without first dehydrating to XLinq objects and then re-parsing and re-processing the result.To elaborate on
#2
, if I write this code today:The XLinq API affords no way to tell the compiler that the target type for the content of the
<p>
element isFormattableString
and so the interpolated string won't be realized as such. Further, even if one were to manually instantiate aFormattableString
using an explicit cast the XLinq API can't hold on to an instance of that type. Instead theFormattableString
is necessarily flattened toString
type during object construction so if some processor wanted to apply HTML escaping to theuserInput
to avoid script injection, for example, it cannot do so because that information is lost upon construction of the object graph.The string interpolation example highlights a security benefit while the Client-Side VB.NET Running in the Browser example listed above highlights a functional scenario. Because the compiler has access to type information from the object-model being created it can apply an expression-tree transformation to a lambda expression and that representation is persistent at run-time thus enabling a later processor to convert the expression tree to JavaScript. As they are today XML literals would simply include the mangled name of an anonymous delegate type.
Are these XAML Literals?
No. The design challenge is to produce a pattern flexible enough to create a subset of XAML programs using the same syntax but without introducing the vast majority of XAML mapping concepts into the VB language. This is beneficial both for the simplicity of the VB language but also to ensure flexibility to address non-XAML DSL dialects such as XHTML, MathML, X3D, etc. How concepts such as type converters, data-binding, markup extensions, templating, etc are enabled without introduction to the language is delegated to the implementation of helper methods. Some hypothetical mockups showing type converters and data-binding can be examined here in the demo code.
Having said that, upon full consideration of the set of opportunities presented by this feature and design pain-points it may be the case that it is simplest to adopt at least some aspects of XAML directly (e.g. the XAML namespace itself, e.g.
x:Name
,x:TypeArguments
). The goal should be to reduce this set to the smallest size possible before considering taking such a leap, if at all.Comparison to Object Initializers
It's a logical question to ask if object initializers would be well- or better suited to this task. Languages like F#, Swift (e.g. Swift UI), and Kotlin (e.g. Type-Safe Builders) all use a syntax that's more... native to their language to achieve some declarative simplicity in expression. VB has the advantage of having an actual declarative language embedded within in but there are other reasons I feel relying on object initializers falls short.
In short, you can do some extensive and unnatural things to object-initializers to get to a place which can functionally handle the scenarios in an unsatisfying way that abstracts away none of the imperative complexities of the domain or the underlying object model, doesn't enable easy interop with other tools, samples, or documentation, and specifically for domains such as XHTML and XAML looses all resemblance to the target domain. XML is naturally declarative and, via XAML, has already been shown to be capable of expressing the needed idioms cleanly.
For a detailed analysis of these points see the V. Expanded Discussion on Object Initializers as an Alternative at the end of this document.
III. Priority Areas for Design Feedback
If I were out in Redmond today, visiting with the VB LDM, these are the topics I'd want feedback on immediately; this is how I'd spend my precious 1-2 hours of LDM-time. They're the top-of-mind issues I feel need community feedback and LDM guidance sooner rather than later.
A. What makes an XML literal pattern-based?
Here are some ideas and some of their pros/cons. It's not exhaustive and I'd love better suggestions/feedback.
1. Magic or Extended XML namespace imports
For speed and ease of implementation the prototype uses a magic namespace import inspired by XAML:
What I like about this is that it means some XAML fragments might just work correctly automatically, particularly any user-mapped namespaces because we'd be re-using the same syntax. It doesn't actually work for the "built in" namespaces in WPF for example because they use a more complex mapping. In a WPF .xaml file you'll see this default namespace declaration
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
and it's a separate mechanism in the designer that uses CLR attribution and that knows to map that XML namespace to a particular assembly and several CLR namespaces--the single namespace mapping brings bothSystem.Windows
andSystem.Windows.Controls
into scope. Perhaps a similar file- or project-level mapping mechanism could be added:This would help avoid silently changing the meaning of any existing XML literals purely because they include a recognized XML namespace.
It also makes it pretty straightforward to have separate "builders" or factories for particular XML literals by keying the builder to the namespace. So if I want to have an outer literal that's in HTML that contains an inner literal in MathML the XML namespaces will disambiguate which builder object is invoked for each.
Another thing I like about this is that if VB gets the method-scoped
Imports
directives this design will synergize well with that.My problem with this design is that there's the potential for duplication in that you need a traditional VB Import statement (or project-level import) to refer to types with simple names in VB code and potentially a duplicate one to use those same types declaratively in XML:
I wish there was a way to share that but I'm also not 100% sure on the idea that every type in scope in regular VB code should be available in an XML expression (or vice versa) as the vast majority of them won't be part of the target domain. Is there some some compromise?
2. Extended XML document header
Right now VB only accepts XML document headers like this:
The version must be 1.0 and is required, the encoding is optional, the standalone attribute is also optional but may only be "yes" or "no" if specified. Anything else won't compile.
We could extend this with some new attribute which previously didn't compile and has some meaning or perhaps a different value that wasn't legal before. The problem with this is that if you imagine a world where there are top-level only VB XML literals like in the second and third prototype example videos, those top-level XML docs may no longer be polyglot markup. That is to say, other XML tools might reject them is we deviate from the structure of well-formed XML.
The other problem with this is that it requires this document declaration on every outer-most element that appears in a file; the feature would only apply to pattern-based XML documents and not any XML literal.
3. DTDs (!DOCTYPE)
VB XML literals today don't support specifying a DTD. I think co-opting the !DOCTYPE declaration has similar pros and cons to extending the XML document header + the con of introducing even limited awareness of the concept of !DOCTYPE into the language.
The 2008 VB Team chose not to cross this bridge. I'm inclined not to do so either but listing this for fair discussion.
4. Magic XML processing instructions
XML actually supports the idea of an open-ended capability for special instructions to the tool processing it: XML processing instructions.
VB supports including such instructions today. Almost anything can go between the two question marks. It's straightforward enough to reserve some XML processing instruction target for the VB compiler itself. Might need to twiddle with parsing a bit to let you specify them before standalone elements so as not to require a document header but the approach seems inline with the intent of the design of XML.
5. Target-typing
One of the tried and true methods of making an expression do one thing sometimes and another better thing other times is target-typing. That's the idea that when an expression is in a context where it's being converted to a well-known type the compiler handles it specially. This mechanism is how array literals, interpolated strings, lambda expressions, and others handle their magic. The idea is that given this statement:
That the conversion from XML Element expression to TextBox is what triggers the pattern-based processing. There are a few problems with this approach.
First, that depending on the scenario a target type won't be available. And it's very easy for target-type context to be lost, for example when using the
If
operator.Second, the target type will likely be less derived than the type being instanced. Change the above example to:
How does the compiler know to look for
TextBox
based on a target type ofControl
? Does it look in the same namespace?It's not impossible to use this mechanism but you have to look very carefully at common usage scenarios to see if it falls apart too often.
B. What does the actual construction of an object created by a pattern-based XML literal?
Currently the prototype uses a combination of methods 1 and 2 below though I think a combination of 1 and 3 might be preferable.
1. The Compiler, Based on Convention
In this model the compiler maps XML idioms to CLR types and member operations based on conventions, a la XAML. So:
<Type />
maps toNew Type
.<Type Member="Value" />
maps toNew Type With { .Member = Value }
or similar.<Type><Inner /></Type>
maps toNew Type From { New Inner }
or similar.This approach is the simplest and puts the least burden on implementers because with the right set of conventions most APIs (i.e. WPF and Xamarin.Forms) will work out of the box for simple idioms. The compiler can have a little bit of smarts about converting attribute values to primitive types or enum members but not much else. For a simple DSL this may be sufficient but for more interesting DSLs (e.g. XAML) some set of helper methods will be required. The danger is that not every object model will fit the convention already or even be able to.
A downside to convention is just how many "conventions" you have to build into the language. So perhaps the 3 above seem reasonable but what about things like Attached Properties & Events? It's certainly possible to say
<Type X.Y="Value" />
maps toDim temp = New Type : X.SetY(New Type, Value)
but it feels bizarely XAML-specific (though the prototype does implement this convention currently for testing). We can totally decide to live with that but it's a thing to be aware of. For what it's worth, realistically all of the scenarios which require attached properties would also require advanced helper methods for type converters and data-binding so supporting the convention really just saved me time for my demos; it wouldn't be enough in the real world.This approach is "stateless" so if there's any need to "remember" anything there's no mechanism for that (more on that below).
2. Instance/Extension Methods
In this model the compiler maps XML idioms to instance or extension methods on objects being constructed. So:
<Type />
maps toNew Type
.<Type Member="Value" />
maps toNew Type With { .SetXmlAttributeMember(Value) }
or similar.<Type><Inner /></Type>
maps toNew Type With { .AddXmlContent(New Inner) }
or similar.This approach gives more flexibility because an object-model can be retrofitted to support the XML idioms after the fact in the same way that LINQ operators can be added to any type via extension methods.
My main problem with this is that all of these helper methods clutter the declaration space on the types with confusing, irrelevant, and almost always duplicate members. So in addition to having a
View.LayoutOptions
property that accepts aLayoutOptions
structure there's also aView.SetLayoutOptions
method (or extension method) that understands how to map data-binding for theLayoutOptions
property and type conversion for theLayoutOptions
structure. There's a helper method (potentially) which knows how to map adding children to anItemsControl
in addition to whateverChildren.Add
property/method are already available on e.g.Panel
. I love LINQ but I don't love how dozens of almost always irrelevant members are added to the completion list onString
or any other collection type to support it, particularly because I use query comprehensions more than lambda syntax. This is primarily a tooling problem so a tooling solution could work.I'm not too worried about name collisions but another implicit requirement of this approach is "having an instance to call it on". You also don't have a chance to intercept object construction, i.e. no way to say
<br />
maps toGetOrCreateLineBreakSingleton()
. The Roslyn API, for example, in many places doesn't expose public constructors at all, instead relying on factory methods.Like the above this model is virtually stateless--each XML "operation" is an independent method call with no real way to track a larger orchestration.
3. Builder
In this model the compiler, on either a per type or per XML namespace basis, gets or creates some kind of "builder" object and then interacts with that builder to implement the pattern idioms. This is similar to the
GetEnumerator
/GetAwaiter
design. So<Type />
could map toDim temp = builder.XmlElementType()
.<Type Member="Value" />
maps toDim temp = builder.XmlElementType() : builder.SetXmlAttributeMember(temp, Value) }
or similar.<Type><Inner /></Type>
maps toDim temp = builder.XmlElementType() : builder.AddXmlContent(builder.XmlElementInner()) }
or similar.This is the most complex/work for the implementer of the pattern (not the end-developer using the pattern). It has the advantage of relegating all the infrastructure of supporting the pattern to the declaration space on the builder type and, hypothetically, the builder can still be extended. You can potentially do some exotic things with this approach (see "5. Exotic construction patterns") and the builder can be stateful (per literal expression). So potentially it could remember things like other XML namespaces in scope (useful for data and control templating, or runtime evaluation of markup extensions) or certain optimizations. There might also be some benefits in the binder if the compiler can compute/cache certain lookups on a per XML namespace basis.
4. Fallbacks
I dislike all-or-nothing designs where if one thing needs to be tweaked, a simple requirement explodes into a massive requirement. For that reason the prototype is implemented to look for helper methods but can fall-back on conventions. So for a very simple DSL just making the object model itself might be enough. But for targeting something like XAML the capability is there. Even within XAML/WPF/Xamarin, some properties are bindable and some aren't. It's convenient to not need to write anything special if things like data-binding or other markup extensions aren't needed but to be able to add them when they are needed.
5. Exotic construction patterns
Because my initial example was Xamarin XAML the prototype design naturally makes assumptions that might not apply to all domains and it's definitely worth considering whether those assumptions should be baked in. For example:
<br />
)AddRange
) rather than one at a time.<html><head>...</head></html>
setting thehead
property or adding thehead
child?builder.AddChild()
returnsBuilderWithOneChild
instance that doesn't have anAddChild
method and so statically can't add another and so fluently tracks and enforces certain constraints transactionally.It would be great to get more feedback on the flexibility needed to support a broad set of potential DSLs.
C. Other important things to think about now
1. Open-ended XML schemas/expando-attributes (e.g. HTML)
Some DSLs enable or require the acceptance of undefined attributes or elements. There should probably be a a pattern for allowing or disallowing such, e.g.
Function XmlElement(elementName As String) As GenericHtmlContent
I don't think you always want to be open ended because that removes some of the benefits of the static typing but some support for openness would probably be great.
2. XAML Member-Element syntax vs child-elements/dotted-names
The prototype always treats
<X.Y>
as though it's initializing a child elementY
on an object of typeX
because that's what that means in XAML. Names with 2 or more dots are just forbidden for the same reason. But there are surely XML dialects and DSLs where member names of object/type names would naturally contain dots. I think Android's UI markup does this and would like to hear about more. Should there be a convention/fallback around this? How can we future proof this? What about other names with invalid VB identifier characters, e.g.command-name="Open"
?3. XML Misc (Comments, whitespace, processing, entities?)
4. Instancing generic types
Xamarin XAML makes use of generic objects, e.g.
OnPlatform
andOnIdiom
and uses thex:TypeArguments
attribute to facilitate this but it looks like that usage is deprecated.End-developers can work around it pretty easily using inheritance though. I can't think of another good way to support this without special-casing the
x:TypeArguments
attribute. Technically in XAML this namespace is reserved for XAML processors. We could take the plunge and say that in this context VB is very much that.Are there some other compelling scenarios for generics in a DSL markup language?
5. The
x:
("http://schemas.microsoft.com/winfx/2006/xaml") namespace.This namespace is reserved in XAML. All of my demos were done without special casing anything in it.
I'm trying hard to not special case anything in here but someone should make the opposition case for at least some of the attributes. I still think we should hold fast on markup extensions being opaque to the language. Might just need to settle for this feature supporting "most" XAML fragments and not "every conceivable" fragment.
D. Opportunities for better tooling, better scenario support, nicer experiences, or supplemental language features
1. Pseudo-declarations for XML "symbols".
In the prototype, some XML idioms are assumed to map to literal CLR declarations--elements to types while others may or may not map to a "real" CLR declaration. It may be preferred to introduce XML specific 'pseudo' declarations and first map the XML idioms to those abstractions and reduce those abstractions to implementation declarations at a later time. By analogy, given a tuple type
(Width As Double, Height As Double)
theWidth
andHeight
"properties" don't exist at run-time but do exist for the purpose of the language. This would let the IDE provide IntelliSense for the attributes (or even elements) even if those attributes aren't backed by CLR members but rather helper methods as well as letting us fiddle with things like F1 help or documentation. So, for example, even though theHeader
attribute is implemented calling theXmlAttributeHeader
method, F1 help would still go to theHeaderControl.Header
property.Further, for even a simple XML dialect with no backing object-model one could potentially provide limited IntelliSense by simply implementing a builder with a set of helper methods.
2.
InitializeComponent
special-caseFor desktop, mobile, and web, there are scenarios where the pattern is that the markup doesn't instance a new top-level object but rather initializes the type it appears in:
In this case, you don't want to make up a new
Window
but initialize members of the containingWindow
. One incredibly hacky way to accomplish this is by calling a helper with aByRef
parameter before any other initialization which replaces the instance being initialized with the instance passed in. But that's really hacky see example.Instead of that, I'm going to propose a much more targeted fix. If a pattern-based XML literal appears as an expression statement in an instance
Sub
namedInitializeComponent
, and the result type of the literal is compatible with the type of the instance, the compiler will apply the initializers/calls/etc of that literal to the instance. The reason this isn't as gross as it sounds are 2:InitializeComponent
methods when generating parameterless constructors.The benefit of this special case is that if combined correctly with the #446 proposal this preserves polyglot markup (a file that is both valid VB and a valid XAML file). The alternatives require some VB-specific content.
3. "Implicit-Conversion" from expression E of type T to
Func(Of T)
orExpression(Of Func(Of T))
for templating.Given this:
The nested XML fragment is processed as an expression. It's as if it were written
<%= <inner /> %>
. I'm proposing that the declarative syntax support an implicit lambda conversion so that if the helper method is typed as such this code can be interpretted as<%= Function() <inner /> %>
. This is how data-templating would logically be handled in the .NET type system and in fact the Xamarin.Forms object model uses a delegate for data templates rather than... whatever unholy things WPF does for that reason. Obviously because VB supports embedded expressions you could express the lambda explicitly but it would be nice to in this context support the re-interpretation declaratively.Of course, this should also support converting to
Expression(Of Func(Of T))
because then your templates can be examined as expression trees. Then in the ASP.NET scenario you could write a template in VB XML that's translated into the javascript producing that DOM on the client side. See prototype demo 3 - client side VB for a related example of the power of this feature in combination with expression tree conversions.I haven't prototyped any of this as of yet but I think it's really cool and really powerful and so I'm mentioning it now.
I'm also interested in supporting this conversion for embedded expressions as attribute values for similar reasons. e.g.
<web:ListBox DisplayMember=<%= context.FullName %> />
. Basically, a little more work on the translation side a lot more declarativeness for end-developers. It gives the pattern-author the power to declare whether expressions are evaluated immediately or deferred without pushing that complexity on the end-developer.4. Expression Functions/ExpressionTreeOf operator/Handles improvements?
This is well outside the scope of this feature. It's not even like secondary to it. But in looking at the scenarios touched on by the "Client-Side VB.NET in Browser" scenario I considered some alternatives to what was shown. What I showed was a lambda expression in the markup. Here are some alternatives I just want people to stew in.
Handles
to be able to... handle them by converting the body of the handling method into an expression tree?Clicked="HandlerName"
syntax and the familiar VB code-behind workflow but get the client-side converted expression tree goodness.ExpressionTreeOf
operator that did that conversion that you could use on source methods (so you didn't have to put everything inline in a lambda)?Async
, there wereExpression
Functions which returnExpression(Of <appropriate delegate type>)
?Await
that lets you call such methods from otherExpression
methods cleanly?Three different approaches to providing an object model with an expression tree representing the body of a method to enable more scenarios like what I showed in the client-side video. It's not required for this proposal but was inspired by it so I thought I'd bring it up.
IV. Design Minutiae
This is the long-tail of almost trivial questions that came up during prototyping and will eventually need to be answered. They're not necessarily hard choices, the LDM just needs to run through them eventually. Some of them are just dev notes. I was originally just going to brain-dump them there to ensure I don't forget them but for fear of bikeshedding I've extracted them into a gist for that purpose. I'll do my best not to look at them for a while and as items become more clearly defined or less trivial I may migrate some of them here.
V. Expanded Discussion on Object Initializers as an Alternative
Object initializers don't, by design, reduce any complexity. They allow you to express things in the language as expressions but don't make those expressions or patterns any less imperative. While nothing in the XML pattern approach precludes extending object initializer syntax in scenarios where doing so would be natural, it would require unnatural extensions to even begin to approach the simplicity already present in XML.
Some examples:
Doesn't abstract away "type converters"
Compare these lines:
XML:
Object Member Initializers:
The initializer doesn't abstract away the implementation detail of how to get from the descriptor "Medium" to the double value for the font-size. One still has to be aware of and interact with the
Device
class, theGetNamedSize
method, theNamedSize
enumeration, etc.Doesn't abstract away subtleties in the object models
The "How" not "What" flexibility of declarative coding means the developer is free to specify that an element is the child of some other element without specifying the particularities of the object model. Compare this example using XML:
to this one using object initializers:
Note how initializers still require one to specify the exact content property of
ContentPage
(which won't always be namedContent
) and to also specify that the content of a StackLayout be added to theChildren
collection (which might be calledItems
elsewhere).Doesn't handle events
Currently there's no way to subscribe to an event in an object initializer. That could be worked around but even a hypothetical syntax would retain some amount of ceremony:
Compared to XML:
Yes, you could come up with various schemes to remove syntax such as
New
,AddHandler
, andAddressOf
but all of the ways of doing that introduce a lot more complexity in areas that don't benefit from it.Can't initialize both members and elements
This is actually easier to deal with in VB than in C# because the
With
andFrom
syntax disambiguate intention. But for types like panels (Grid
,StackPanel
,StackLayout
) it's very common to need to initialize both the elements in the panel and some other aspect of the panel itself. You want to be able to express this:As this:
The object model can work around this limitation by pushing the "collection aspect" of itself into a child property but there's no guarantee the model will support that and it pushes that complexity onto consumers.
Doesn't handle methods (e.g. required for data-binding and attached properties/events)
Data-binding completely defies the scenarios that member initializers were built for. In this example using XML:
The result isn't setting the
Text
property at all. Rather, the imperative translation of this is:A method call passing in a shared field and a binding object. One could imagine extending initializers to support method calls but that still shifts the burden of managing all of that boilerplate and complexity onto the end-developer. Something like:
And it's even more complicated by attached properties and events because rather than invoking an instance method on the object being initialized those require invoking shared methods on a completely different type. So you'd want to further generalize object initializers to support arbitrary method calls in the initializer list, which pass in the instance being constructed, which can't actually be referred to yet (ok, technically initializers run after construction so it's fair game, I guess):
I think this example in the Xamarin.Forms docs shows where this falls apart. The author just gives up on initializers and falls back to imperative code specifically dealing with the
Grid
, though they API writers have made it easier by parameterizing theAdd
method to take the row and column spans. You could imagine code like this:Summary
As stated above in Section IV., you can do a bunch of things to object initializers to support much of the functionality required but in doing so you lose all declarative elegance and simplicity for the end-developer as well as any resemblance to the target domain (e.g. HTML, XAML, MathML, etc).