Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider adding type contracting (protocols/interfaces/abstract types) #9

Open
faultyserver opened this issue Aug 26, 2017 · 3 comments
Labels
semantics.interpreter Any issue relating to changing the semantics of interpreter code. semantics Generic label for semantics issues. All semantics issues should have this tag. syntax Any issue relating to the syntax of Myst.

Comments

@faultyserver
Copy link
Member

In dynamic, interpreted languages, the ability to define explicit contracts about the interfaces that values expose is often lost. Instead, these are traded for abstract definitions and/or implicit contracts that are often visually simpler, but less obvious when the contract is extended or re-implemented by another class/module.

The primary loss in these languages is the ability to enforce those contract requirements before they are encountered at runtime (if they ever are).

Another great side-effect of being able to enforce those contracts is predictable failure. That is, if a function requires that a contract be satisfied, then calls to that function with non-conforming arguments will fail before any of the function's body is executed. When the contract is not enforceable, it is common for functions to begin execution and fail midway through, resulting in a greater (often inferred, not necessary) dependence on exceptions and exception handling.

I think having Myst adopt a contracting system would be beneficial and worthwhile, and doesn't have to compromise readability or flexibility in the language. The roadmap for Myst already suggests explicit typing will be idiomatic in most cases, so the ability to specify more abstract behaviors follows suit to me.

Examples of languages that implement and enforce contracting systems:

  • Elixir has Protocols and Behaviours which are distinct, but both provide guarantees of working interfaces at a contract-like level.
  • Java has Interfaces which are almost identical to the contracts explained here, but lack some functionality (and are extremely verbose).
  • C++ has virtual functions that can be added to superclasses as a simplistic version of a contract.
  • Crystal has abstract types that are similar to virtual functions in C++.

Some considerations:

  1. What does the syntax look like? I'm somewhat partial to something like a use or implements directive at a module/class level for defining the contracts that the type should satisfy.
  2. How is this affected by re-openable classes? When is the contract enforced? After parsing finishes, or some time during interpretation (function call time?).
  3. Is anything more than module inheritance necessary? In most cases, contracts can be implicitly set up by defining and including modules with the contract definition into subtypes (see Ruby). This is effective, but still has most of the drawbacks of implicitness as mentioned earlier.
@faultyserver
Copy link
Member Author

I spent a solid hour writing out an example usage of a ValueStore protocol as an abstract interface for storage types like YAML, JSON, databases, etc. What I got out of it is that there isn't much benefit to them beyond what including a module would be able to provide.

I like the distinction between "supplies this interface, but may fallback to a default implementation" that modules provide and "supplies this interface completely on it's own" from protocols. The latter seems useful, particularly for things like storage types, but I don't see a real use for them.

I'll leave this open in case a more convincing use case comes up.

@faultyserver
Copy link
Member Author

faultyserver commented Aug 29, 2017

I think I've found one legitimate use case for some kind of protocol enforcement, and it's actually very simple.

Take the following class:

class SomeCollection
  include Enumerable
end

This is semantically valid and not incorrect in any way. SomeCollection will now have access to all of the methods from Enumerable.

But, this obviously isn't actually correct, because there's no each method defined, meaning pretty much every method from Enumerable will end up raising an error.

This is something that could potentially be resolved by protocols before the code runs. This wouldn't be possible in one pass, as each could be defined lexicographically after the include. However, with a semantic/typing pass preceding interpretation (which I already think will be done to enforce function parameter structures), the set of methods defined on the class could be known before the include is evaluated, and a check for each could be performed with a protocol.

This would probably make use of an included hook:

module Enumerable
  protocol Each
    def each()
  end

  def included(base)
    unless base.implements?(Each)
      raise "#{base} included Enumerable, but did not define `#each`."
    end
  end
end

.implements? in the above is obviously a placeholder for some native-level check for meeting the protocol (otherwise it would be too slow to warrant something different than a simple responds_to? implementation). Note that whatever this check ends up being would be the same check used for protocol restrictions in function pattern matching described earlier.

With this, though, Myst could potentially tell you that your code won't work before any code is actually run! (Well, that would need a third pass for handling type finalization separate from evaluation, but it'd be possible! Natively!)

@faultyserver
Copy link
Member Author

This was an interesting read from José Valim on how to properly implement protocols: http://blog.plataformatec.com.br/2014/06/comparing-protocols-and-extensions-in-swift-and-elixir/.

@faultyserver faultyserver added semantics Generic label for semantics issues. All semantics issues should have this tag. syntax Any issue relating to the syntax of Myst. labels Oct 23, 2017
@faultyserver faultyserver added the semantics.interpreter Any issue relating to changing the semantics of interpreter code. label Jan 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semantics.interpreter Any issue relating to changing the semantics of interpreter code. semantics Generic label for semantics issues. All semantics issues should have this tag. syntax Any issue relating to the syntax of Myst.
Projects
None yet
Development

No branches or pull requests

1 participant