Strict Typing is More Dynamic

So often the focus of the strongly typed vs dynamic typed debate is about compile time, or lack thereof. Sure, strongly typed languages offer a range of compile time checks not possible in their dynamic counterparts, but less talked about are dynamic, runtime possibilities that a rich type system makes possible.

Most modern, strongly typed, non-system languages (java, c#, others) come with a reflection API, a method of getting rich type information at runtime. This opens up a world of possibilities to reduce code and decouple components. Today I'll look at a few examples of these.

Dependency Injection

public class Foo {
  public Foo(SomeDependency sd, SomeOtherDependeency sod) {
  }
}

The first one that most programmers are probably familiar with is dependency injection. In a strongly typed language, an IOC container can inspect a type, find what other types it depends upon and use the information to construct an object.

In the above code, the IOC container will see that the Foo class has two dependencies. At runtime it can instantiate an instance class by invoking the constructor and providing the correct dependencies. It can provide different dependencies depending on a range of factors, from configuration to url parameters.

A multi tenant application, for example, may have different implementations of SomeDependency for differing configurations. The IOC container can look at the URL and provide the correct implementation for that tenant. The controller is completely oblivious to this so it's code is simpler and more maintainable.

The Mediator Pattern

The mediator pattern is a useful technique to decouple software components. Examples would be CQRS (such as mediatr) and pub/sub like with mass transit. In both cases the calling application knows nothing of the implementor, if one exists at all. Here is an example of a product controller using a mediator:

public class DiscountProductCommand : ICommand {
  public int ProductId { get; set;}
  public public double DiscountAmmount { get; set; }
}

public classs ProductController {

  private readonly IMediator Mediator;
  private readonly IUser User;

  public ProductController(IMediator mediator, IUser user) {
    Mediator = mediator;
    User = user;
  }

  public ActionResult ApplyDiscount(DiscountProductCommand command) {
    if (ModelState.IsValid) {
      Mediator.execute(command);
      User.Notify("Discount has been applied");
      return RedirectToAction("Home");
    }
    return View(command);
  }
}

public class ProductExecutor : IExecute<DiscountProductCommand> {
  public void Execute(DiscountProductCommand command) {
    //code goes here
  }
}

A runtime bootstrapping process will scan the assembly for all types that implement IExecute, automagically wiring up the application with full type safety. This type of loose coupling of components makes software a joy to maintain and extend.

Rich Metadata

Reflection APIs aren't limited to just names and types either, rich meta data can be added with attributes. The functionality of these attributes can vary, from simple simple information to full blown AOP designs. Here are a couple of examples:

public class ProductVM {
  public int? Id { get; set; }

  [Required]
  public string Name { get; set; }

  [DisplayName("SKU")]
  [Required]
  public string Code { get; set; }
}

The DisplayName attribute is used by a form generator to create the correct label and the validator to generate the correct error message. The validator also knows which fields are required, in this simple case all validation is done without writing any real code.

AOP is a more advanced technique to apply cross cutting concerns with less code. It is highly dynamic and depends heavily on strong type information. Here is an example of how rich logging can be automagically applied:

[EnableLogging]
public class UserAPI {

  public List<User> SearchUsers(UserSeearchQuery query) { }

  public void ChangePassword(int userId, [SkipLog]string oldPassword, [SkipLog]string newPassword) { }

}

Using an AOP framework a proxy interceptor can be created. The interceptor automatically serializes and logs the method called (and the parameters), the current user, etc. This is great information to have when a bug is reported because all the information needed to replicate the issue can be found in the log. Further refinement can be made, such as avoiding sensitive information (passwords, cc numbers) from ending up log files. The SkipLog attribute allows us to avoid logging sensitive information while still getting the benefits of the interceptor.

Conclusion

These are examples of how strongly typed languages can defer many decisions until runtime and respond to a range of environmental factors to build the execution path. I would argue that these approaches make strict typing more dynamic and adaptable than dynamic ones, which usually have to resort to a lot of manual wiring and "stringly" typing to achieve the same results.

Languages like ruby have become nearly synonymous with "convention over configuration", yet they are less capable of supporting it than there strongly types counterparts.