Skip to content

Instantly share code, notes, and snippets.

@jaytaph
Last active December 23, 2015 02:49
Show Gist options
  • Save jaytaph/6569127 to your computer and use it in GitHub Desktop.
Save jaytaph/6569127 to your computer and use it in GitHub Desktop.
importing in saffire

namespaces, importing and autoloading

Qualified class names

A class name can be in one of the following formats:

  • Unqualified class name (UCN)

    The class does not contain any namespace separators ::.

    Example: foo

    Classes are resolved by concatting the current namespace before the classname.

  • Qualified class name (QCN)

    The class contains a namespace separator ::, but not at the start of the name.

    Example: foo::bar

    Classes are resolved by concatting the current namespace before the classname.

  • Fully qualified class name (FQCN)

    The class contains a namespace separator at the start of the name.

    Example: ::foo::bar

    Classes are resolved to use the classname directly. The current namespace is not used in any way.

Using classes from different namespaces

When referencing to objects, it depends on the qualificiation on how to are resolved. Example:

# /dir/foo.sf
bar = Bar();        // Unqualified class name    

In this case, Bar object will be found in /dir/bar.sf.

# /dir/foo.sf
baz = Bar::Baz();    // Qualified class name

In this case, the Baz object will be found in /dir/Bar/Baz.sf.

# /dir/foo.sf
baz = ::Bar::Baz();    // Fully Qualified class name

In this case, the Baz object will be resolved indepently from the current namespace (and thus the /dir directory).

Import statement

Instead of referencing to qualified or fully qualified objects all the time, it's possible to import these names as aliases in the current namespace. This way we can alias the object without its (long) fully qualified name.

syntax

 import <class> [from <namespace>] [as <alias>]

translation: from the given namespace, import class, and make it known in the current namespace as the alias. This way, any reference to alias will be translated to namespace::class.

Many times, the class that will be imported will be of the same name as we want to use it inside our current namespace. If the "as " part is not given, saffire will automatically alias the class under the same name.

Saffire will trigger (import)exceptions when:

  • The namespace cannot be found/loaded
  • the alias is already used in the current namespace

It is possible to load the SAME class from the SAME namespace with different aliases:

  import io from company::framework as my_io;
  import io from company::framework as another_io;

examples

Some examples on how importing would work.

Example 1

import company::framework;

Alias for:

import framework from company::framework as framework

Translation: from the company::framework namespace, import the class named framework, and make it known in the current namespace as the class framework;

Example 2

import company::framework as framework2;

Alias for:

import framework from company::framework as framework2

Translation: from the company::framework namespace, import the class named framework, and make it known in the current namespace as the class framework2;

When you import a namespaced class, but don't specify an alias for it, it will automatically assume the classname as the alias. Therefore, the following will trigger an exception on the second import:

import company::framework;        # Imported as 'framework'
import another::framework;        # ImportArgument as 'framework' already exists

The correct way would be:

import company::framework;                            # Imported as 'framework'
import another::framework as alternativeFramework;    # Imported as 'alternativeFramework'

Example 3

import io;

Alias for:

import io from io as io

Translation: from the io namespace, import the class named io, and make it known in the current namespace as the class io;

Resolving namespaces

Resolving namespaces will happen in the following way:

  • Unqualified class names

    Resolved by using the directory from the current namespace.

    For example, if the namespace is ::foo::bar, and the directory it maps to is /home/user/saffire/foo/bar, any unqualified classes will be resolved from /home/user/saffire/foo/bar/<classname>.sf.

  • Qualified class names

    Resolved by starting from the directory from the current namespace.

    For example, if the namespace is ::foo::bar, and the directory it maps to is /home/user/saffire/foo/bar, then a qualified class like baz::test will be resolved from /home/user/saffire/foo/bar/baz/test.sf.

  • Fully qualified class names

    Resolved by searching through the import list.

    For example, a fully qualified classname discards the current namespace. For example, a fully qualified class like ::framework::http::response will be resolved as framework/http/response.sf, by looking in the import list.

    If the importlist is '.', './modules', '/usr/share/saffire/modules', it will look in the following places:

    ./framework/http/response.sf
    ./modules/framework/http/response.sf
    /usr/share/saffire/modules/framework/http/response.sf
    

    Directories are relative from the working directory of the called script. So if the script is in /home/user, then ./framework/http/response.sf would become /home/user/framework/http/response.sf.

Special cases

import io;

How to deal with this? First off, it's a UQCN, meaning it would resolve to the current working directory. How can we make this work?

Namespaces

A namespace is a way to isolate classes (and variables). A namespace can be seen as a prefix for that class or variable which automatically gets added to class or variable when it's referenced.

Namespaces do not have to be single names, but can be separated by the namespace-seperator token "::". This can be seen as a directory separator:

 company::framework::http::request

Namespaces are automatically defined by their filepath. The example above can be found inside the file:

<searchpath>/company/framework/http/request.sf

Namespaces are resolved by checking the searchpath. By default, the searchpath is something similar to [[".", "./modules", "/usr/share/saffire/modules", "/usr/share/saffire/modules/sfl"]].

This means that whenever we want to import the example namespace, the following filepaths will be checked (in this order):

  • ./company/framework/http/request.sf
  • ./modules/company/framework/http/request.sf
  • /usr/share/saffire/modules/company/framework/http/request.sf
  • /usr/share/saffire/modules/sfl/company/framework/http/request.sf

If the file is not found, an (import)exception will be thrown.

The first file that will be loaded through saffire, will have an empty namespace context (::).

Overriding files

This way, it's easy to override files if needed: suppose you want to override the company::framework::http::response class, but not any other class inside the framework. This could be automatically done by creating a file named ./modules/company/framework/http/response.sf.

Since the ./modules directory will be search PRIOR to the company framework module path in /usr/share/saffire/modules, it will actually use your custom file. Since there are no other files for other classes (like request.sf), those files will be loaded directly from the original framework.

If you like to extend the response class inside your custom response.sf, it's possible to import the ORIGINAL response.sf. This can be done by just importing the same file. When saffire will import the file, it will detect that the file you want to import, is already the same file as the one it current is in, so it will skip that, and continue looking for the next match.

This means that:

 # source: test.sf
 import company::framework::http::request; 

imports the ./modules/company/framework/http/request.sf file, but

# source: ./modules/company/framework/http/request.sf
import company::framework::http::request; 

will actually import /usr/share/saffire/modules/company/framework/http/response.sf, as the first autoloading match would be itself, and would obviously skip this.

absolute vs relative namespaces

As namespaces map onto filepaths, it's possible to use relative and absolute namespaces as well. Every namespace that starts with a namespace-separator (::), will be considered a relative namespace. Every other namespace is considered a relative namespace.

Relative namespaces will automatically be converted internally by appending the PATH of the CURRENT namespace.

Examples:

  # namespace ::
  import ::io;

Absolute namespace, will be looking at /io.sf.

# namespace ::
import io;

Relative namespace, as the CURRENT namespace is ::, it will be looking at the absolute namespacepath of '::io', which will be /io.sf.

#namespace company::framework::http;
import request;

Relative namespace, will be looking for /company/framework/http/request.sf

#namespace company::framework::http;
import ::request;

Absolute namespace, will be looking for /request.sf

Questions

Q: Should it be possible, just like inside normal filesystems to identify a parent namespace?

# namespace company::framework::http;
import <parent>::templating;  # will be importing company::framework::templating.sf

In this case, it might be easier to actually change the separator from :: to /, in order to identify the correlation between directory mapping and namespace mapping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment