Martin Fowler loosely defines a fluent interface thusly: "The more the use of the API has that language like flow, the more fluent it is." If you detect a whiff of skepticism here, you're right: I've never seen this work. Computer languages aren't human languages.
Let's look at a concrete example from Joshua Flanagan. Here's how we define a regular expression in the standard way:
<div\s*class="game"\s*id="(?<gameID>\d+)-game"(?<content>.*?) <!--gameStatus\s*=\s*(?<gameState>\d+)-->
Here's how we'd define that same regular expression in Joshua's fluent interface.
Pattern findGamesPattern = Pattern.With.Literal(@"<div")
.WhiteSpace.Repeat.ZeroOrMore
.Literal(@"class=""game""").WhiteSpace.Repeat.ZeroOrMore.Literal(@"id=""")
.NamedGroup("gameId", Pattern.With.Digit.Repeat.OneOrMore)
.Literal(@"-game""")
.NamedGroup("content", Pattern.With.Anything.Repeat.Lazy.ZeroOrMore)
.Literal(@"<!--gameStatus")
.WhiteSpace.Repeat.ZeroOrMore.Literal("=").WhiteSpace.Repeat.ZeroOrMore
.NamedGroup("gameState", Pattern.With.Digit.Repeat.OneOrMore)
.Literal("-->");
So we're replacing a nice, succinct one line regular expression with ten lines of objects, methods, and named enumerations. This is progress?
I'll grant you that I am probably unusually familiar with regular expressions, even by developer standards. There's a reason they have a reputation for being dense and inscrutable. I've definitely seen some incredibly bad regular expressions in my day. But in my professional opinion, that regex was a well written one. I had no problem reading it. Adding a ton of hyper-dense object wrappers to that regex makes it harder for me to understand what it does.
The new syntax Joshua invented is great, but it's specific to his implementation. Although it may seem like a good idea to use these kinds of training wheels to "learn" regular expressions, I'd argue that you aren't learning them at all. And that's a shame, because regular expression syntax is a mini-language of its own. Once you learn it, you can use it anywhere; it works (almost) the same in every environment.
The Subsonic project attempts to do something similar for SQL. Consider this SQL query:
SELECT * from Customers WHERE Country = "USA" ORDER BY CompanyName
Here's how we would express that same SQL query in SubSonic's fluent interface:
CustomerCollection c = new CustomerCollection(); c.Where(Customer.Columns.Country, "USA"); c.OrderByAsc(Customer.Columns.CompanyName); c.Load();
I've mentioned before that I'm no fan of object-oriented rendering when a simple string will suffice. That's exactly the reaction I had here; why in the world would I want to use four lines of code instead of one? This seems like a particularly egregious example. The SQL is harder to write and more difficult to understand when it's wrapped in all that proprietary SubSonic object noise. Furthermore, if you don't learn the underlying SQL-- and how databases work-- you're in serious trouble as a software developer.
But I can see the rationale behind these types of database code generation tools:
I definitely sympathize with the desire to produce less code, and that's the whole point of database code generation tools. Personally, I would argue that most of these benefits could be realized with smarter IDEs that actually understood native SQL strings (or regular expressions), rather than relying on a slew of generated code and complicated, proprietary object syntax.
But let's take a step back and think about what's really happening here. In both cases, we are embedding one language inside another. SQL is a language. Regular expressions are a language. Wrapping those languages inside a bunch of mega-verbose fluent interface ObjectJunk-- just so we can pretend we're writing code in our primary language-- is a total cop-out. Fluent interface object wrappers feel like a nasty hack to me.
Why can't we embrace the language-inside-a-language paradigm, rather than running and hiding from it? These domain specific languages exist because they are optimized for processing strings and data efficiently. Avoiding them is counterproductive.
Perhaps the ultimate solution is to redefine the underlying language to incorporate the features of another language.
Consider how Perl integrates the regular expression language:
while (my $line = <IN>) {
while ( $line =~ /(Romeo|Juliet|Mercutio|Tybalt|Friar \w+)/g ) {
my $character = $1;
++$counts{ $character };
}
}
Here's how C# 3.0, with LINQ, integrates the SQL language:
var c = from Customer in Customers where Customer.Country == "USA" orderby Customer.CompanyName select Customer;
Note the conspicuous lack of ObjectJunk. No explosion at the parens and periods factory. No MassivelyLongTextEnumerations to deal with. There's nothing but code that looks like exactly what it does. And that's a beautiful thing.
Embrace the idea of languages inside languages. In The Land of Strings, we speak regular expressions. In The Land of Data, we speak SQL. Oh sure, you can pretend those languages don't exist, and hide out in the Kingdom of Nouns-- but you're only cheating yourself out of a deeper understanding of how things really work in those other places. Fluent interface object wrappers may seem like a helpful convenience, but they're actually an ugly hack, and a terrible substitute for true language integration.
I wasn't questioning your ability to read reg-ex's, just making the point that some things need to be understood in order to be used. I've met people who used GUI SQL builders because they didn't understand SQL. The result? Garbage. One guy thought that if he got a result, any result, the SQL call was correct. Of course it wasn't even close because he didn't understand how a "group by" worked. Programmers need to understand code. People who don't understand code shouldn't be programmers. Code isn't easy to read, that's why it's called code. ;)
Steve on October 31, 2007 1:52 PMJeff,
There is a difference between adding features to a language or "ObjectJunk" libraries.
If you add native SQL features to the core language, then the core language gets polluted with all these specialized keywords, and you can throw backward compatibility out the door, if code written for language 2.5 is not compatible with prior releases. And if you continue the trend with native features, you will get to a point where namespace gets polluted...e.g. orderby keyword referring to a list or a database?
However if you added SQL features to an "ObjectJunk" library, you should be able to use the library with older versions of the languages (provided the library itself can run on older language releases!).
Language designers are always fighting the battle between native vs library.
The "sql is different across platforms" argument is pure nonsense. If you stick to the ANSI syntax it will work across MySQL/Oracle/DB2 and most of the others.
If you use something like Oracle avoid decode() and the (+) operator for outer joins - the standard syntax will work.
Stick to the standards and you'll be fine.
I code in Ruby on Rails these days and use ActiveRecord for most of my database needs. Interestingly, the design goal for this was to make the simple stuff really easy (finding things, getting sets of child records etc.). But you can still put raw SQL in for more complex tasks that can't be expressed in Object crud. This works really well.
Ruby's metaprogramming makes it really easy to implement ActiveRecord, it has a method_missing method that allows it to intercept things like
Person.find_by_first_and_last_name "fred", "smith"
and turn it into SQL (and also add this method into the class on the way so there isn't any runtime penalty after the first invocation). You can write really fluent-looking code that reads well, and also dive into SQL for grouping and so on (or proprietary hacks) if you must.
I can't go back to Java and have never had to work with its child C#. I can just write code that reads like English. If I were going back I'd use Hibernate though - again it does the 95% really well and you don't have to care about your database engine.
Regular expressions are part of the Ruby language and are first-class members of the object hierarchy. Working with them is really easy and they have the standard syntax. Again ... Java ... C# ... shudder.
Francis Fish on November 1, 2007 2:24 AMI am not advocating the "one right way" to do anything. Use whatever approach you feel works best for what you're doing.
This post is meant to incite discussion and thought on the topic. Consider what you're doing, why you're doing it, and what the alternatives really are. E.g., if you really need to talk to 5 different databases, then by all means, use an ORM that can help you do that. But also think about the limitations of your toolset and why those limitations exist. Don't blindly accept the status quo because your toolset forces you to.
I believe in the inherent power and flexibility of domain specific languages, and that's exactly what SQL (for data) and Regex (for strings) are. Every developer should be conversant in these languages. The idea that all code should be in one, and only one, language is obsolete. The world is now a patois of different domain-specific languages. Don't fight it with object wrappers. Learn to embrace it and love it.
Jeff Atwood on November 1, 2007 3:47 AM@Jeff,
I understand the need to incite discussion about this, however, I don't understand why I chose to attack SubSonic. However you may wrap it and explain it, that's what it is. Your example can be represented in one line. In fact, I easily identified with it, because it's what Ruby and Rails do almost everywhere. I liked it, just for that reason.
So, why SubSonic? Why hang it out like that?
Srdjan on November 1, 2007 4:12 AMI think it's funny that so many people wrap SQL in the name of "database independence".
"Database independence" is the reason SQL exists.
All you're doing is abstracting the abstraction.
Target SQL92 and it'll work in a wide variety of databases.
Sean on November 1, 2007 11:35 AMI'm with you Jeff.
In fact, I've gone so far as to post a complete re-working of Joshua's example. The re-worked example is concise and readable, but NOT fluent.
I've also proposed an explaination for why the fluent style may be necessary in Java, but not in C#.
John Rusk on November 1, 2007 1:10 PMHi Jeff,
I've been coding for 10 years and have stubbornly kept RegExs at arms length - dealing with them clumsily and only when I needed to.
You've put forward a great arguemtn and I've really started to 'get' RegExs. Indeed I spotted this article which demonstrates commetnign of RegExes, which I didn't know of. Yes it's verbose, and yes I can now read the whole Regex without it now, but it looks like a great way of leaving a trail for others to follow. Anyhoo... http://msmvps.com/blogs/jon.skeet/archive/2007/11/02/i-love-linq-simplifying-a-tedious-task.aspx
Ian Pender on November 2, 2007 7:06 AMQuote: I would argue that most of these benefits could be realized with smarter IDEs that actually understood native SQL strings (or regular expressions)
Well, gvim syntax highlighting for PHP can highlight regular expressions. I think it doesn't highlight SQL (yet). Better than a slow bloated IDE.
Nicolas on November 2, 2007 8:16 AMIt seems, that people are continuing invent new approaches...
forgetting (and not using) the best from old ones.
"If I'm not mistaken, in the .NET world, SQL is best handled by leaving the queries on your SQL Server and using parameter calls to prevent injection for best security and performance."
This has not been the case unless you are using MS-SQL Server predating MS-SQL 2000 in a client/server environment.
Using stored procedures still leaves you open to SQL injection, and stored procedures are no faster then the other SQL commands. Actually in the way most people use stored procedures for CRUD they are slower than parameterized queries because of all the coalesce or isnull are very very slow.
For another way of using the power of your programming language to produce SQL that is known to be valid at compile time, take a look at this solution in OCaml:
http://eigenclass.org/hiki/addressing-orm-problem-typed-relational-algebra
Fluent Interfaces are adaptation to languages to make them 'feel better'. SQL, RegExp and LINQ are Domain Specific Languages and they're not directly related to Fluent Interfaces.
By using a fluent interface you give up your language syntax a bit to get something more readable. By using a DSL you use a language specialized on the problem being solved.
The problems of getting a nice DSL and transforming it on tons of lines of a General purpose Language are not related to Fluent Interfaces.
Phillip Calado Shoes on November 4, 2007 11:29 AM"First show a regex expression which is completely unreadable to anyone not versed in regex's, compared it to a test interface that makes it into something readable by any competent programmer."
Two mistakes. A competent programmer nowadays know regepxs. And it is much more efficient to make a practical solution than let everybody wade through something that verbose. (For educational purposes write a regexp-to-verbose-objects converter and use that to understand the code.)
Or just imagine how a language would look like if it were designed for competent programmers that are just not well versed in OO and where every method invocation would explain what it does...shudder.
"...your one line of sql starts becoming several lines of conditionals, parameters, and concatenations."
Not with proper interpolation at hand. Unfortunately, user-defined string interpolation doesn't exist and can't be implemented in most current languages.
"And you are still embedding sql in the middle of your app."
So what? Code is code. :-)
Andreas Krey on November 4, 2007 12:38 PM"But in my professional opinion, that regex was a well written one."
Even if it matches things that it obviously shouldn't match, like divclass="game"id=..., uhm? (and it uses grouping constructs without any useful purpose besides making it look more complicated than it actually is)
marcelo on November 6, 2007 2:57 AMThe problem with wrapper code of any kind is that it HAS TO BE TRANSPARENT. If you loose some of the capability of the base language, or if it introduces new bugs and complexity that the base language didn't have then it's a complete failure.
My personal pet peeve is slightly differing regexp syntaxes between programs. Especially you, emacs.
engtech on November 9, 2007 8:56 AMI was thinking about this and had to search for this post but surely generics would count as a language in a language? It's like a preprocessor language and pretty tidyly implemented (in C# atleast).
Scott on March 13, 2008 2:23 AMI think the point of frameworks like SubSonic is not to save lines of code in how I write a SQL Sentence, but in how I express the sentence in an environment that will handle the Impedance Mismatch between the SQL World and the objects world, saving code.
These two lines of code:
--------------
SELECT * from Customers WHERE Country = USA
ORDER BY CompanyName
--------------
will not handle the way I move the results to an object world. (If necessary). Connect to the DB (hopefully abstracted of the DB Engine), query the DB, get the result set, disconnect and parse it in objects.
These four lines of code will:
--------------
CustomerCollection c = new CustomerCollection();
c.Where(Customer.Columns.Country, USA);
c.OrderByAsc(Customer.Columns.CompanyName);
c.Load();
--------------
I agree that LINQ to SQL is much more clean and readable, but it doesn't mean that this comment is valid:
why in the world would I want to use four lines of code instead of one?
It only misleads.
grumlin on September 4, 2008 7:42 AMI hadn't looked at it that way. I thought the effective use of fluent interfaces was to get closer to a domain specific language, rather than just to rewrite regex in objectese. So rather than call 8 separate variable tweaks on an object to perform an action, I can chain them into code that expresses what a customer might think.
Programmer steve = new Programmer().hasName("Steve")
.hasOOPExperience()
.doesntget("FluentInterfaces")
.dockPay(200%);
Come on, tell me I'm not the only one who read that as "flatuant interfaces"? :)
Simon Roberts on February 6, 2010 10:13 PM(I obviously can't type either): flatulent
Simon Roberts on February 6, 2010 10:13 PMFirst of all, if you want to make an apples to apples comparision and you're looking for terse code, you shouldn't use the fluent interface:
IDataReader rdr=new Query("Customer").WHERE("Country", "USA").OrderByAsc("CompanyName").ExecuteReader();
As Rob pointed out (and we talked about on Saturday), there's a big difference between your simple SQL statement and what SubSonic's doing. A big difference is database independence.
The point of multiple database support isn't that you'll be moving a single application between databases, but that once you get good at SubSonic, you can easily write robust data access code that works on SQL Server 2005, SQL Server 2000, Oracle, MySQL, SQLite, etc. Today on a biz-dev call, the client mentioned MySQL support and I didn't flinch, because even though it's been a few years since I wrote a SQL statement in MySQL, I know I can do anything I'd need with MySQL right now without worrying about how it handles paging, sorting, datatypes, etc. SubSonic's MySQL provider was written by a member of the MySQL team and has been tested by thousands of users, while you're writing new MySQL queries from scratch.
That's a selling point for Linq, too - instead of using a different syntax (SQL or object) to query everything, we use standard Linq syntax which works everywhere.
A bigger issue is the use of untyped datareaders. I never use an untyped datareader in SubSonic. I use a strongly typed collection:
CustomerCollection col = CustomerCollection("Country", "USA").OrderByAsc("CompanyName").Load();
Now I can use a strongly typed collection of Customer objects, while you've got a dumb, anonymous blob of data.
I see your point in a "blue sky / wouldn't it be nice" academic argument, but it's not useful beyond that. You can't really be suggesting that we embed SQL statements in our code (SQL injection alone is a good enough counterargument there). It's just not a systainable way to write real world applications. I've got plenty of battle scars from poorly written data access code built on embedded SQL.
So, then, we need to write some sort of data access utility. Once we're doing that, does it make sense for every developer in the world to write their own, untested data access code - especially when you've established that a lot of developers have trouble with FizzBuzz? I'd prefer to work with a data access system written by some of the best developers I know.
And, really, recommending embedded SQL (in if it's just to make an academic point) without some heavy disclaimers is kind of irresponsible. How many developers do you estimate will take this as best practice advice and move to (or stick with) embedded SQL? If it's one, it's too many, and my guess is that it's a lot more than that.
Jon Galloway on February 6, 2010 10:13 PMOh, and the Linq thing. It's true that it looks prettier, but it's only because of compiler magic. I could make SubSonic queries look really pretty if I owned the compiler, too.
Under the hood, you're working with object which look a lot like SubSonic query objects:
IEnumerableCustomerTuple locals =
customers.Where(c = c.ZipCode == 91822)
.Select(c = new CustomerTuple(c.Name, c.Address));
The compiler and IDE allow you to express that as:
var locals = (from c in customers
where c.ZipCode == 91822
select new { c.Name, c.Address})
However, it's important to not that you're still calling through objects and fluent interfaces, it's just hidden behind the scenes.
http://msdn.microsoft.com/msdnmag/issues/07/06/CSharp30/
Jon Galloway on February 6, 2010 10:13 PM"In The Land of Strings, we speak regular expressions. In The Land of Data, we speak SQL."
Haha, I love this quote. Mind if I steal it?
LINQ is awesome. I've been all over that since the first CTP, and now that Orcas actually provides Intellisense for it, I can say pretty confidently that it blows every other ORM tool out of the water. I do wish it had slightly better support for the UD in CRUD, and it would be nice if XLinq was as clean in C# as it is in VB, but eh, nothing's perfect.
And as Eric says, you can use these SQL-like operations on any IEnumerable. So if - for example - you're getting data from a web service instead of a database, you can treat it almost exactly the same way. That is a true "fluent interface", making PROPER use of object-orientation to make an actual abstraction rather than a useless wrapper. Although I suppose it's a little easier to do this when you also make the compiler. :-)
Aaron G on February 6, 2010 10:13 PMJeff,
Never forget that SQL itself is merely another example of what LINQ is doing - it embeds the Relational Calculus in a data management language. And Codd is on record that it does it badly.
Ross Patterson on February 6, 2010 10:13 PMI can see a need for abstraction of the database for some solutions as they will need to be database agnostic, but as as whole, the more abstractions the harder the code will be to debug and follow, even if it is in "API" like syntax. And, it will be *slower*. Plus it is very easy to implement different database versions with an interface and implementations of the interface.
Interface GenDB
Abstract GetCustomers()
Abstract GetSales()
SQLServer : GenDB
GetCustomers()
//SQL Server SQL to Get Customers
Oracle : GenDB
GetCustomers()
//Oracle SQL to Get Customers
Pretty simple. Add another DB, implement the interface.
If I use SQL, I can debug the code to where the SQL is being generated and cut and paste the SQL in to a query analyzer (toad, etc) type of tool and check to make sure the SQL is working as expected. LINQ, where's the SQL? It's in LINQ somewhere, so now another layer to figure out where the problem is. The problem will be that everyone will start using LINQ instead of just issueing a query to the database.
There definately seems to be an "abtraction movement" to generalize everything, but in my experience abstractions can lead to a very unclear picture to what is *really going* on in the code. Some of us would like to know what is going on rather than relying on faith of an API call.
The dumbing down of programmers continues. Programmers of the future will not be known as software engineers, thier titles will be "object abstraction monkeys".
Jon Raynor on February 6, 2010 10:13 PMI realize this is a general post on how great it is to throw untyped strings into your code, but I it's worth pointing out reason for the bias many people have against a data access layer: you tried one before and you didn't like it. It didn't add value, it got in the way, and you ended up wishing you could just get at your data instead of messing with some wacky wrapper. Your data access layer caused extra work, and didn't really help.
Here's something you might not have considered: it's not data access layers, it's you. You screwed up - you built or picked a dumb data access layer, then decided data access layers are bad. Nope, it's you.
http://blog.wekeroad.com/2007/10/09/unleashing-elmer-fudd/
Jon Galloway on February 6, 2010 10:13 PMPeople seem to have missed one of the most used (and IMHO very useful) fluent interfaces... C++'s iostream . Who among us is not familiar with the code:
cout "Hello World." endl;
Fluent interfaces have their place, as do embedded languages. Though I have to admit that I've seen some JSP code (and PHP for that matter) that would cause any programmer to run away screaming. It's the constant switching between languages (HTML and Java) that can really get you.
The comments to this entry are closed.
|
|
Traffic Stats |