Too type happy in dart:io?

236 views
Skip to first unread message

mythz

unread,
Jul 1, 2013, 1:37:04 AM7/1/13
to mi...@dartlang.org

Working with some of the dart:io SDK can be quite tedious and hard to navigate. 

Trying to do simple things like finding the absolute path of the current directory is painful.  
Browsing around and looking at what's available statically I tried stuff like this:

  print(new Directory('.').current.path);
  print(new Directory('').current.path);

Both of these throw:
Breaking on exception: Class '_Directory' has no instance getter 'current'.

  print(new Path('.').directoryPath);
  print(new Path('.').directoryPath.toNativePath());
  print(new File('.').path);

Whilst all these yield '.', basically I couldn't find any way to retrieve the absolute path of the current directory.

In node.js it's available with the module variable '__dirname'. It might be a magic variable but at least it's discoverable and works well with all their path libs since they all work with strings.

So in the end I'm having to do what looks like a blocking IO call just to get it:

String __dirname = new File('.').fullPathSync();    //is there anything better?

The Path class is also cumbersome to work with, it mixes Path and String which IMO creates un-necessary friction given that File and Directory only works with strings.
In end as I wasn't getting much value from it as all the code ended up being more verbose so I just ended up porting the behavior of node.js path APIs internally, since they're simpler and easier to work with: http://nodejs.org/api/path.html

And it also looks like the HTTP Server APIs got a change for the worse, this is now the example given to set a Content-Type:
http://api.dartlang.org/docs/releases/latest/dart_io/HttpResponse.html

HttpResponse response = ...

response.headers.contentType
      = new ContentType("application", "json", charset: "utf-8");

It's so long that the example code to set a singe field wraps on 2 lines. I don't see how it can be more readable given that Content-Types are always read in their concatenated form (i.e. application/json) in every other place I've seen them. I'm also assuming that this will always be slower than setting a literal string so I feel like I'm paying a perf penalty for the price of having more verbose and less readable code.

There are many places where the SDK APIs seem clean and really well thought out, but I also think there's a risk of introducing too many types (esp around strings) which seems to just cause friction, promoting less readable and more verbose code. IMO APIs should be weighed against node.js's APIs and measured on their readability and imposed friction.

- Demis

Lasse R.H. Nielsen

unread,
Jul 1, 2013, 3:02:11 AM7/1/13
to mi...@dartlang.org
I haven't tried it much, but it seems "Directory.current" (a static getter on the Directory class) is what you want for the current directory. It appears to be an absolute path (only tested on Linux).

/L


--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new
 
 



--
Lasse R.H. Nielsen - l...@google.com  
'Faith without judgement merely degrades the spirit divine'
Google Denmark ApS - Frederiksborggade 20B, 1 sal - 1360 København K - Denmark - CVR nr. 28 86 69 84

mythz

unread,
Jul 1, 2013, 3:49:27 AM7/1/13
to mi...@dartlang.org
Ahh crap it is too, thx. 
That would also be the most logical place for it too, it's weird it was a runtime error, guess that's what I get for relying too much on intelli-sense :).

Anders Johnsen

unread,
Jul 1, 2013, 4:02:13 AM7/1/13
to General Dart Discussion
Hi Demis,

Comments are inlined.

Trying to do simple things like finding the absolute path of the current directory is painful.  
Browsing around and looking at what's available statically I tried stuff like this:

  print(new Directory('.').current.path);
  print(new Directory('').current.path);

Both of these throw:
Breaking on exception: Class '_Directory' has no instance getter 'current'.

Yes, current is not a member, but a static getter on the Directory class. The correct usage would be

print(Directory.current.path);
 

  print(new Path('.').directoryPath);
  print(new Path('.').directoryPath.toNativePath());
  print(new File('.').path);

Whilst all these yield '.', basically I couldn't find any way to retrieve the absolute path of the current directory.


Path is not intended to do any OS calls at all, but merely a way to work on path strings. "new File('.').path" also doesn't invoke any OS calls, as 'path' just returns the constructor argument.
 

In node.js it's available with the module variable '__dirname'. It might be a magic variable but at least it's discoverable and works well with all their path libs since they all work with strings.


We believe that using Directory.current is easier to find than '__dirname', but maybe more examples may help the documentation. 
 

The Path class is also cumbersome to work with, it mixes Path and String which IMO creates un-necessary friction given that File and Directory only works with strings.
In end as I wasn't getting much value from it as all the code ended up being more verbose so I just ended up porting the behavior of node.js path APIs internally, since they're simpler and easier to work with: http://nodejs.org/api/path.html

Yes, the path class is different from the File and Directory class, in the way it's only used for string manipulation (hence the mixture of Path and String methods). One example is:

  HttpRequest request = ...;
  var path = new Path(request.uri.path).canonicalize();

Here path will be the canonical path of the request, even though it may not be a physical object on the system. This is useful for e.g. virtual directories that may be entirely memory-mapped.

And it also looks like the HTTP Server APIs got a change for the worse, this is now the example given to set a Content-Type:
http://api.dartlang.org/docs/releases/latest/dart_io/HttpResponse.html

HttpResponse response = ...

response.headers.contentType
      = new ContentType("application", "json", charset: "utf-8");

It's so long that the example code to set a singe field wraps on 2 lines. I don't see how it can be more readable given that Content-Types are always read in their concatenated form (i.e. application/json) in every other place I've seen them. I'm also assuming that this will always be slower than setting a literal string so I feel like I'm paying a perf penalty for the price of having more verbose and less readable code.

Dart is a OO language, and we prefer to use specific object types instead of type overloading. However, note that you can also write the above like so:

  response.headers.contentType = ContentType.parse("application/json; charset=utf-8");
 

There are many places where the SDK APIs seem clean and really well thought out, but I also think there's a risk of introducing too many types (esp around strings) which seems to just cause friction, promoting less readable and more verbose code. IMO APIs should be weighed against node.js's APIs and measured on their readability and imposed friction.

We try to limit the number of classes in dart:io, but still try to be true to the philosophy of Dart. We have types in Dart and we believe that we're better off with ContentType and Path with direct accessors such as ContentType.mimeType, ContentType.charset, Path.filename and Path.directoryPath. Only having a String class will make it quite verbose and not very readable.

I hope this clears up some of the design decissions we have made in dart:io. That said, we are always addressing issues that are counter-productive for developers, so feel free to file any issues you may have, at http://dartbug.com.

Cheers,

- Anders

mythz

unread,
Jul 1, 2013, 3:05:29 PM7/1/13
to mi...@dartlang.org
Hi Anders,

Thanks for your comments.

Yeah Directory.current is definitely better than __dirname, and would've saved me a lot of time if I got to it the first time.
This should no longer be an issue once static methods are taken out of instance intelli-sense and a checked error/warning is raised when trying to access a static member in an instance context.

I understand there are times when OOP APIs can be preferable, but it should be weighed against each use-case.
Nice thing about HTTP as a text protocol is that setting any HTTP Headers are consistent and flexible, thanks to which also allows us to have a minimal and flexible API Surface (which based on some of the other APIs, looks to be a goal for Dart).
When you start typing them it just adds friction, and I'm not sure when benefit this provides? Even as a web framework author, I rarely ever deal with structured Content-Type segments. 
Given that in the end a string has to be emitted on the wire, the API must be for developer convenience and I don't see even how the the condensed example:

response.headers.contentType = ContentType.parse("application/json; charset=utf-8");

Is any better than using a string?

response.headers.contentType = "application/json; charset=utf-8";

So we've now go from an unstructured string into a typed model, that in the end gets converted back into a raw string again before getting emitted on the wire.
I also dislike using magic strings in code, but it still lets me easily create a static helper for strings which is what I do in https://github.com/dartist/express/blob/master/lib/content_types.dart

class ContentTypes {
  static const String HTML = "text/html; charset=UTF-8";

But with this change I now have to add a superfluous extra type to store the typed version:

class ContentTypes {
  static const String HTML = "text/html; charset=UTF-8";
  static ContentType HTML_TYPE = ContentType.parse(HTML);

Which lets me reduce code down to:

response.headers.contentType = ContentTypes.HTML_TYPE;

Which could've just as easily been done with strings:

response.headers.contentType = ContentTypes.HTML;

Which requires less boilerplate/friction under the hood. I'd expect most of the times given that setting the Content-Type is a common operation that not many people will use the 
IMO the Dart IO HTTP APIs should be approached in the same way as node.js, i.e. a minimal, flexible and un-opinionated foundation upon which higher-level web frameworks can be built-on.
If others see value in typing the whole world with every known HTTP Status code or HTTP Headers, then that's an opinion that can be isolated to these typed/high-level frameworks. 
But imposing them on the lowest level API (i.e. the Dart SDK) creates friction on those frameworks that don't want this.

Some things I look to when designing API's is a trade-off between capturing "user intent" in its most readable form and balancing that with maintaining a lean code-base with a flexible and reduced API surface.
This seems to contradict both of these goals since unnecessary types create friction which prevents us from being able to use generic string containers to hold and carry information, i.e. being able to pass and set all headers generically with just a Map<String,String>.

- Demis

Anders Johnsen

unread,
Jul 2, 2013, 2:42:58 AM7/2/13
to General Dart Discussion
Hi Demis,

Note that the HttpHeaders.contentType is a shortcut for working with the ContentType of the headers. You can still put the raw string in directly:

  var response = ...
  response.headers.set(HttpHeaders.CONTENT_TYPE, "text/plain");

or 

  var response = ...
  response.headers.set("Content-Type", "text/plain");

The direct getters/setters on the HttpHeaders objects are convenience access to the most used fields.

I hope this helps!,

- Anders


--

Bob Nystrom

unread,
Jul 2, 2013, 1:07:12 PM7/2/13
to General Dart Discussion
On Sun, Jun 30, 2013 at 10:37 PM, mythz <demis....@gmail.com> wrote:

Working with some of the dart:io SDK can be quite tedious and hard to navigate. 

For what it's worth, I also found the path manipulation API in dart:io cumbersome (and, at the time, buggy), which is why Nathan and I created the pathos package.

The Path class is also cumbersome to work with, it mixes Path and String which IMO creates un-necessary friction given that File and Directory only works with strings.

Agreed completely. Pathos is purely string based.

In end as I wasn't getting much value from it as all the code ended up being more verbose so I just ended up porting the behavior of node.js path APIs internally, since they're simpler and easier to work with: http://nodejs.org/api/path.html

Pathos has a similar API to node:

path.current
path.separator
path.absolute('foo/bar.txt');                      // -> /your/current/dir/foo/bar.txt
path.basename('path/to/foo.dart');                 // -> 'foo.dart'
path.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
path.dirname('path/to/foo.dart');                  // -> 'path/to'
path.extension('path/to/foo.dart');                // -> '.dart'
path.isAbsolute('/path/to/foo.dart');              // -> true
path.isRelative('/path/to/foo.dart');              // -> false
path.join('path', 'to', 'foo');                    // -> 'path/to/foo'
path.split('path/to/foo');                         // -> ['path', 'to', 'foo']
path.normalize('path/./to/..//file.text');         // -> 'path/file.txt'
path.relative('/root/path/a/b.dart');              // -> 'a/b.dart'
path.withoutExtension('path/to/foo.dart');         // -> 'path/to/foo'
path.fromUri(Uri.parse('file:///path/to/foo'))     // -> '/path/to/foo'
path.toUri('/path/to/foo')                         // -> Uri.parse('file:///path/to/foo')

It also takes great pains to do what you expect on Windows, Mac, and Linux, and has quite comprehensive tests.

Try it out and see if it works for you. :)

Cheers!

- bob

mythz

unread,
Jul 2, 2013, 3:18:57 PM7/2/13
to mi...@dartlang.org
Hi Bob,

These looks great, exactly what the Path APIs should look like. 
I don't see why we need the Path class anymore, this should be baked is the default since it promotes much less friction when dealing with Files/Directories/paths.

One of the concerns I have with the Dart SDK APIs is that it tries too hard to be OOP, for the sake of OOP, where I don't think the benefit of adding types is being weighed against the friction of introducing them. 
I'd really like to see the examples of benefits that justified the use of an interim Path type for manipulating paths, since it just looks like the good standing convention of using strings was ignored for no good reason.

- Demis

Bob Nystrom

unread,
Jul 2, 2013, 5:03:58 PM7/2/13
to General Dart Discussion

On Tue, Jul 2, 2013 at 12:18 PM, mythz <demis....@gmail.com> wrote:
I don't see why we need the Path class anymore, this should be baked is the default since it promotes much less friction when dealing with Files/Directories/paths.

One of the nice things about using strings for paths is that they don't have to be baked into the platform. Since pathos treats paths as strings, it's implicitly compatible with any API like dart:io that uses path strings.

This lets any code use pathos that wants to without coupling the public API of that code to pathos.

Having outside of the platform is actually a feature: it makes it easier to version it and release updates. It's much harder to change stuff in the core libraries.

Cheers!

- bob

mythz

unread,
Jul 2, 2013, 5:16:03 PM7/2/13
to mi...@dartlang.org
Non-core packages living outside the core package may be a feature, but having an inconsistent and competing option as a default in the SDK isn't.
Happy to ignore what's in the SDK, but we now have at least 2 ways to manage paths (+ I'm also maintaining node_shims to ease porting), with the worst option being the default that's baked in the SDK.

Anyway the Path API likely wont affect me, just want to provide feedback on the risk of over-typing SDK API's.

- Demis

Bob Nystrom

unread,
Jul 2, 2013, 5:22:47 PM7/2/13
to General Dart Discussion
On Tue, Jul 2, 2013 at 2:16 PM, mythz <demis....@gmail.com> wrote:
Non-core packages living outside the core package may be a feature, but having an inconsistent and competing option as a default in the SDK isn't.
Happy to ignore what's in the SDK, but we now have at least 2 ways to manage paths (+ I'm also maintaining node_shims to ease porting), with the worst option being the default that's baked in the SDK.

Anyway the Path API likely wont affect me, just want to provide feedback on the risk of over-typing SDK API's.

Yeah, I'm not crazy about the redundancy either and it's something I was sensitive to when I put pathos out there. I personally felt that pathos was enough of an improvement over the Path class in dart:io for my use cases to justify it.

Moving forward, I would personally be happy if the Path class was removed from dart:io, but I'm not the maintainer of that library. Maybe file a bug so that the relevant people can weigh in?

Thanks!

- bob

mythz

unread,
Jul 2, 2013, 5:53:02 PM7/2/13
to mi...@dartlang.org

Josh Gargus

unread,
Jul 3, 2013, 1:48:05 AM7/3/13
to mi...@dartlang.org


On Monday, July 1, 2013 12:05:29 PM UTC-7, mythz wrote:
Hi Anders,

Thanks for your comments.

Yeah Directory.current is definitely better than __dirname, and would've saved me a lot of time if I got to it the first time.
This should no longer be an issue once static methods are taken out of instance intelli-sense and a checked error/warning is raised when trying to access a static member in an instance context.

I understand there are times when OOP APIs can be preferable, but it should be weighed against each use-case.
Nice thing about HTTP as a text protocol is that setting any HTTP Headers are consistent and flexible, thanks to which also allows us to have a minimal and flexible API Surface (which based on some of the other APIs, looks to be a goal for Dart).
When you start typing them it just adds friction, and I'm not sure when benefit this provides? Even as a web framework author, I rarely ever deal with structured Content-Type segments. 
Given that in the end a string has to be emitted on the wire, the API must be for developer convenience and I don't see even how the the condensed example:

response.headers.contentType = ContentType.parse("application/json; charset=utf-8");

Is any better than using a string?

response.headers.contentType = "application/json; charset=utf-8";

So we've now go from an unstructured string into a typed model, that in the end gets converted back into a raw string again before getting emitted on the wire.

It seems clear that using the string is both more readable and shorter to type.  On the other hand, Anders has a point that there are benefits to having ContentType.charSet, etc.

However, it seems like we can easily have the best of both worlds here.  Why not allow the contentType setter to accept either a String or a ContentType as the argument... if it's a String, then run it through ContentType.parse() and store it internally as a ContentType.


Which requires less boilerplate/friction under the hood. I'd expect most of the times given that setting the Content-Type is a common operation that not many people will use the 
IMO the Dart IO HTTP APIs should be approached in the same way as node.js, i.e. a minimal, flexible and un-opinionated foundation upon which higher-level web frameworks can be built-on.
If others see value in typing the whole world with every known HTTP Status code or HTTP Headers, then that's an opinion that can be isolated to these typed/high-level frameworks. 
But imposing them on the lowest level API (i.e. the Dart SDK) creates friction on those frameworks that don't want this.

Some things I look to when designing API's is a trade-off between capturing "user intent" in its most readable form and balancing that with maintaining a lean code-base with a flexible and reduced API surface.
This seems to contradict both of these goals since unnecessary types create friction which prevents us from being able to use generic string containers to hold and carry information, i.e. being able to pass and set all headers generically with just a Map<String,String>.

Well said.

Cheers,
Josh

 

Justin Fagnani

unread,
Jul 3, 2013, 3:26:08 AM7/3/13
to General Dart Discussion
On Tue, Jul 2, 2013 at 10:48 PM, Josh Gargus <jj...@google.com> wrote:


On Monday, July 1, 2013 12:05:29 PM UTC-7, mythz wrote:
Hi Anders,

Thanks for your comments.

Yeah Directory.current is definitely better than __dirname, and would've saved me a lot of time if I got to it the first time.
This should no longer be an issue once static methods are taken out of instance intelli-sense and a checked error/warning is raised when trying to access a static member in an instance context.

I understand there are times when OOP APIs can be preferable, but it should be weighed against each use-case.
Nice thing about HTTP as a text protocol is that setting any HTTP Headers are consistent and flexible, thanks to which also allows us to have a minimal and flexible API Surface (which based on some of the other APIs, looks to be a goal for Dart).
When you start typing them it just adds friction, and I'm not sure when benefit this provides? Even as a web framework author, I rarely ever deal with structured Content-Type segments. 
Given that in the end a string has to be emitted on the wire, the API must be for developer convenience and I don't see even how the the condensed example:

response.headers.contentType = ContentType.parse("application/json; charset=utf-8");

Is any better than using a string?

response.headers.contentType = "application/json; charset=utf-8";

So we've now go from an unstructured string into a typed model, that in the end gets converted back into a raw string again before getting emitted on the wire.

It seems clear that using the string is both more readable and shorter to type.  On the other hand, Anders has a point that there are benefits to having ContentType.charSet, etc.

However, it seems like we can easily have the best of both worlds here.  Why not allow the contentType setter to accept either a String or a ContentType as the argument... if it's a String, then run it through ContentType.parse() and store it internally as a ContentType.

Support your local union types: http://dartbug.com/4938




Which requires less boilerplate/friction under the hood. I'd expect most of the times given that setting the Content-Type is a common operation that not many people will use the 
IMO the Dart IO HTTP APIs should be approached in the same way as node.js, i.e. a minimal, flexible and un-opinionated foundation upon which higher-level web frameworks can be built-on.
If others see value in typing the whole world with every known HTTP Status code or HTTP Headers, then that's an opinion that can be isolated to these typed/high-level frameworks. 
But imposing them on the lowest level API (i.e. the Dart SDK) creates friction on those frameworks that don't want this.

Some things I look to when designing API's is a trade-off between capturing "user intent" in its most readable form and balancing that with maintaining a lean code-base with a flexible and reduced API surface.
This seems to contradict both of these goals since unnecessary types create friction which prevents us from being able to use generic string containers to hold and carry information, i.e. being able to pass and set all headers generically with just a Map<String,String>.

Well said.

Cheers,
Josh

 

--

Josh Gargus

unread,
Jul 9, 2013, 4:25:55 PM7/9/13
to mi...@dartlang.org
On Wednesday, July 3, 2013 12:26:08 AM UTC-7, Justin Fagnani wrote:

Support your local union types: http://dartbug.com/4938


Thanks Justin.  Starred it, and added the use-case discussed here.

Cheers,
Josh 
Reply all
Reply to author
Forward
0 new messages