Implementing "exports" in C#
The other day i was musing about improving readability by liberating verbs from the containing objects that own them. Similar things are accomplished with mix-ins or traits in other languages, but I wanted to go another step further and allow for very context specific mapping, rather than just mixing ambigiously named methods from other objects. Since the exports
construct looked a lot like a map of expressions, I decided to see what replicating the behavior with existing C# would look like.
To review, this is what I want (in C#-like pseudo code + exports)
:
class PageWorkflow {
UserService _userService exports { FindById => FindUserById };
PageService _pageService exports {
FindById => FindPageById,
UpdatePage(p,c) => Update(this p,c)
};
AuthService _authService exports {
AuthorizeUserForPage(u,p,Permissions.Write) => UserCanUpdatePage(u,p)
};
UpdatePage(userid, pageid, content) {
var user = FindUserById(userid);
var page = FindPageById(pageid);
if(UserCanUpdatePage(user,page)) {
page.Update(content);
} else {
throw;
}
}
}
And this is C# implementation of the above:
public class PageWorkflow {
private readonly Func<int, User> FindUserById;
private readonly Func<int, Page> FindPageById;
private readonly Action<Page, string> Update;
private readonly Func<User, Page, bool> UserCanUpdatePage;
public PageWorkflow(IUserService userService, IPageService pageService, IAuthService authService) {
FindUserById = (id) => userService.FindById(id);
FindPageById = (id) => pageService.FindById(id);
Update = (page, content) => pageService.UpdatePage(page, content);
UserCanUpdatePage = (user, page) => authService.AuthorizeUserForPage(user, page, Permissions.Write);
}
public void UpdatePage(int userid, int pageid, string content) {
var user = FindUserById(userid);
var page = FindPageById(pageid);
if(UserCanUpdatePage(user, page)) {
Update(page, content);
} else {
throw new Exception();
}
}
}
As I mentioned, it's all possible, short of the context sensitive extension method on Page
. Lacking extension methods, I was going to name the imported method UpdatePage
, but since it would be a field, it conflicts with the UpdatePage
workflow method despite functionally having different signatures.
All in all, the public workflow UpdatePage
is pretty close to what I had wanted, but the explicit type declaration of the each exports
makes it boilerplate that is likely not worth the trouble of writing, and, no, i won't even consider code generation.
This exercise along with every other language feature I've dreamt up for Promise does illustrate one thing quite clearly to me: My ideal language should provide programatic access to its parser so that the syntax of the language can be extended by the libraries. Internal DSLs are a nice start with most languages, but often they fall short of being able to reduce default boilerplate and just create new boilerplate. Sure, designing the language to be more succinct is desirable, but if anything, that only covers what the language creators could imagine. Being able to tweak a language into a DSL for the task at hand, a la MPS, seems a lot more flexible.
Is this added flexibility worth the loss of a common set of constructs that is shared by all programmers knowing language X? It certainly could be abused to become incomprehensible, but I would suggest that even knowing language X joining any Team working on a project of sufficient complexity adds its own wealth of implicit patterns and constructs and worse than a language whose compiler is extended, these constructs are communicated via comments and documentation that are not part of language X, i.e. the compiler and IDE lack the ability to aid someone learning these constructs.
Considering this, I really need to do a survey of languages that already offer this capability as well as take a closer look at MPS and Nemerle to see if the language I want is just a few parser rules a way from an existing meta programming language.