On 2011-05-13 17:19, Adam D. Ruppe wrote:
Jacob Carlborg wrote:
How is it working out with a static type system, compared to a
dynamic, for web development?
It's *much* better, especially for rapid development. The compiler
will tell me if my changes anywhere break things anywhere else,
so I can modify with confidence.
I can't tell you how many times I've made a minor mistake in PHP,
Ruby, or Visual Basic and had it slip through to production because
the code path was obscure enough to evade testing.
That's what I really miss when doing web development in a language with
a dynamic type system. It's hard to do any refactoring, always the
possibility to break some code you had no idea would be affected.
Sometimes, something similar does happen in D - to!int("") throws -
but, the compiler catches the majority of incompatible changes
instantly.
If I had int addUser() and change it to User addUser(), I don't
have to hunt every instance down myself. Just hit compile and
the compiler tells me where the change causes problems. (Rarer than
you might think - thanks to auto, a lot of the program is already
duck typed in a way. The actual return value name is rarely used.)
It also helps documentation to have good types. I often see stuff
like this in dynamic language docs:
function doSomething(options);
What does it return? What options are available? Maybe the text
will tell us that options is an associative array. UGH, I hate
that! Now, there's even less knowing what's going on.
Actually that's one thing I really like with Ruby and Rails, that you
can put different types of values in arrays and hashes. But I can agree,
sometimes it's hard to know what options are available.
D's documentation is a lot more helpful, and easier to keep up to
date.
string doSomething(Options options); // not much better yet....
struct Options;
int speed;
bool strictMode;
bool caseSensitive;
Ahhh, now I know!
But that way you would need to declare different types of Options
structs all over the place?
Another thing that the static types enable that I don't think is
possible with dynamic functions is the automatic forms you see
on my link.
http://arsdnet.net/cgi-bin/apidemo/get-a-box
I didn't write any html for that. The library made it for me.
In the source, you can see it's prototype:
http://arsdnet.net/apidemo.d
Element getABox(Color color);
The reflection was able to say "it needs a Color, and there's only
a handful of valid Colors - enum Color - so I can automatically
create a<select> box for this."
Dynamic types for return values can still do most the stuff my
api generator does. Whether it's a static function/template toHtml
or a dynamic property doesn't matter much.
But the parameters... I don't think it can be done without the
static types there.
(Another nice thing is the library checks the type too. Coming
off the request parameters, you have strings; dynamic types. But
going into the function, it knows it must be a Color, so it
checks. If not, an exception is thrown.
By the time you're inside the function, you can be guaranteed that
Color color is indeed a Color. Thus, to!string() to insert it into
the html attribute that I did here is perfectly safe. It's a
whitelist based filter, all done automatically.)
There are a few cases where dynamic types are helpful or needed.
D lets us opt in to them with only a minimal amount of fuss.
The places where I use it are:
1) Data from the user. Form variables are of type string coming
into the program. Once decoded, you have string[][string]. Your
program probably wants something else.
The to!() template in Phobos makes this pretty easy, or if you
want default value and get/post combined, my cgi class has a request
template:
int a = cgi.request!int("a");
That's similar to this in PHP:
$a = 0;
if(isset($_POST['a']))
$a = (int) $_POST['a'];
else
if(isset($_GET['a']))
$a = (int) $_GET['a'];
PHP is so outrageously verbose. And the PHP is buggy compared to
the D... if you do cgi.request!int("a", 10) and the user does a=txt,
the PHP result is wrong. It's not quite fair to compare a function
to a code collection though. A php function could do it right too.
I think there are other languages available that is far better than PHP
that you could compare with instead. Ruby on Rails:
a = params[:a].to_i
Depending on what you need this might not be the best way to write it.
If :a doesn't exist in "params" it would return "nil" and "nil.to_i"
would give you 0. If you use the Rails methods it will to the right
thing most of the times without you need to worry about it. For example:
Post.find(params[:a])
This will try to find a Post with the given id and automatically
converted the given value to an integer. If it fails to convert the
value to an integer it will return 0 (this is not the best solution and
I don't like it). When it can't find a Post with the given value (you
most likely won't have a an id in the database with the value 0) it will
through an exception.
Annnnyway, I'm digressing. Hell, I'm going on about weak types
instead of dynamic types! Even in Ruby, you'd have to do a to_i
since the url value simply is a string.
Back to dynamic types. The three other places where you see them
are databases and interfacing with external services.
2) Databases
With databases, you probably expect a certain type anyway. If the
column is typed as INTEGER, you probably don't want 'lol' in there.
But, the database takes care of that for you.
In my libs, I just use strings.
auto line = mysql.query("SELECT id FROM users WHERE id = 10").front;
res[0] == "10"
Conversions are now done the same as URL parameters:
int id = to!int(res[0]);
Oftentimes, I don't really care about it though; if I'm outputting
it to html, I'll just say:
table.appendRow(res["id"], res["name"])
and the like anyway. You want a string there, so it just works.
(appendRow is in fact a variadic template so it'd just work
regardless, but you get the idea anyway).
I've been thinking for a while to try and create something similar as
Rails' ActiveRecrod for D but every time I tried to do it turns out to
work quite badly with a static type system. Either you would need to
specify the columns and the types in the model class (which I would like
to avoid) or you would have to use Variants all over the places. And if
you use Variants all over the place then I don't so much reason in using
a language with a static type system compared to a dynamic type system.
Thanks to static checks though, if I were to later change my mind
and wanted ints, or made the database return Variants or whatever,
no problem - the compiler will point out trouble spots to me.
Inserting data is the same:
mysql.query("INSERT INTO users (id) VALUES (?)", 10); // works
mysql.query("INSERT INTO users (id) VALUES (?)", cgi.post["id"]); // works too
Or using the data object class:
auto obj = new DataObject(mysql, "users");
obj.id = 10; // works
obj.id = "10"; // works just as well
obj.commitChanges();
(The DataObject can read too, offering database fields as obj.id
style properties. They always return string, just like plain query,
but are also read/write.)
DataObject can also be specialized automatically to use static
types if you like.
db.sql:
create table users(id integer primary key, name varchar(30));
file.d:
// parse out that create table to map the names and types automatically
alias DataObjectFromSqlCreateTable!(import("db.sql"), "users") User;
User a;
a.id = "12"; // type mismatch, fails to compile
a.name = "hello"; // works
a.nmae = "sad" // typo caught at compile time
a.commitChanges(); // otherwise it is still based on DataObject
In short, I have the best of both worlds, with the only extra effort
being writing to!() here and there and that one line to import the
sql definition.
3) HTML
I *want* strict typing when working with html in almost all cases.
It helps catch encoding bugs.
The one place where I want dynamics - fetching an element off
the html template - I have it, this time provided through classes.
auto table = cast(Table) document.getElementById("data-table");
assert(table !is null); // fails if it isn't a<table> tag
// use the table class's special functions
Forms, Links, and other types of nodes are handled similarly. The
Element base class though does most the simple operations. Being
plain old inheritance, the virtual functions act dynamically
enough.
Of course, if the HTML changes, that assert/enforce will trigger
at run time. We're still ahead of dynamic languages though: the
compiler will tell me what breaks if I remove the cast, with one
exception: I use an opDispatch there for attributes. That will
break some functions.
Table t = ...;
t.caption = "Hello!"; // changes the<caption> tag child
Element t = ...;
t.caption = "Hello!"; // sets a caption="" attribute
But meh, in this case, I like the flexibility of accessing
attributes like that. The compiler still catches more than it
doesn't - we're still ahead of the mistakes possible in dynamic
languages while keeping the convenience.
4) External services
This is where dynamic types are the most useful. Accessing Facebook,
Twitter, etc., returns xml or json with types that change based
on a lot of things. Is the user logged in? What url is it?
In this case, it's like the database, but the types tend to be more
complex. Simple string cowboying won't cut it.
Thankfully, D's Variant is up to the task.
Variant userRet = fbGraph(token, "/me");
// we know /me returns a json object from the docs, so fetch it:
auto user = userRet.get(Variant[string]);
auto name = user["name"].get!string;
// name is a string == "Adam D. Ruppe"
Not terribly hard. Robert Jacques IIRC has written an improved
std.json patch to make this work almost just like Javascript:
auto user = fbGraph(token, "/me");
user.name.get!string; // same as above
So D's definitely up to the job. The only difference is that final
use of get!string. I believe Phobos' to! works as well.
As I said above, I don't see the big advantage if I have to use Variants
all over the place .
In the end, there's nothing web related in my experience that a
dynamic language can do that D can't do almost or just as well,
and plenty that D and it's static types can do that dynamic types
can't do, or require piles of painful tests and manually written
code or documentation to do.
D kicks the Web's ass. (And my web related libraries go even further
than seen here. My custom DOM offers a lot of things that are way
cool, enabling new CSS stuff, filling forms easily, in about 1/10
the code of doing it similarly in PHP, and without mucking up the
HTML. The web api/site generator makes D accessible through urls,
forms, javascript - view the source here for a glimpse of that
lib:
http://arsdnet.net/cgi-bin/apidemo/javascript
20 kb of javascript, including the auto generated API bindings!
And more stuff is packed in there too. Best of all: D, through
CGI, consistently does well or better than mod_php in my speed
benchmarks. I've barely tried to optimize too; there's a lot of
untapped potential.
If your host doesn't suck, dive into D on the web. You won't want
to go back.)
I don't know what your experiences are but since you mention PHP a lot I
can agree with you, PHP sucks. Compared to PHP you can do a lot better,
also with a dynamically typed language. Actually all the "native"
languages in web development (HTML, CSS, JavaScript) are quite horrible.
I don't know if it's just me but I try to hide those native languages as
much as possible. I'm currently now uses Ruby on Rails with the
following languages:
* Ruby as the server side script (obviously)
* HAML, generates HTML - http://haml-lang.com/
* SASS, generates CSS - http://sass-lang.com/ (I'm using the sass syntax
and not the scss syntax)
* CoffeeScript, generates JavaScript -
http://jashkenas.github.com/coffee-script/
Every language is compiled to its native format automatically when
needed and cached in production. I just tried Rails 3.1 beta yesterday
and now SASS and CoffeeScript are default. I think these languages make
web development a less of a pain, but in the end they all compile to
their native languages and there is no way to get away from that.
--
/Jacob Carlborg