[p-dev] [docs] Document intentions for modules and interfaces.

Paul Bone paul at bone.id.au
Mon Mar 14 11:57:25 AEDT 2016


Hi,

I`'m seeking feedback on my ideas for modules and interfaces.  If anyone has
any comments I'd appriciate them.  You can also read this online:
http://www.plasmalang.org/docs/plasma_ref.html

Cheers.

---

[docs] Document intentions for modules and interfaces.

docs/plasma_ref.txt:
    As above.
---
 docs/plasma_ref.txt | 289 +++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 255 insertions(+), 34 deletions(-)

diff --git a/docs/plasma_ref.txt b/docs/plasma_ref.txt
index 5f29214..6a3a17a 100644
--- a/docs/plasma_ref.txt
+++ b/docs/plasma_ref.txt
@@ -65,11 +65,6 @@ more.
 
 There is a lot missing.  Some changes I intend to make are:
 
-TODO: Probably decouple module declarations from export lists/items.
-
-TODO: Maybe add support to import more than one thing with a single
-      directive.
-
 TODO: Probably add support for naming return parameters
 
 TODO: Add support for multiple return
@@ -96,6 +91,10 @@ situations.
 | Type Variable    | first letter lower   | lower_case  |
 | Data constructor | -                    | UpperCase   | to visually distinguish construction from function application.
 | Field selector   | -                    | lower_case  |
+| Interface        | ?                    | UpperCase   |
+| Instance         | ?                    | lower_case  | not first class,
+but may appear in expressions.
+| Resources        | -                    | lower_case  |
 |===
 
 Note that there may be more symbol namespaces in the future.
@@ -115,7 +114,8 @@ also become a requirement rather than a suggestion.
 .Types and type variables
 Type variables must be distinguished from types.  Note that variables
 don't need to be distinguished from functions as this is available from
-context: free variables do not exist.
+context: free variables do not exist and a bound variable has the same
+semantics as a defined function name.
 
 Therefore plasma uses case to distinguish between type names (uppercase
 first letter) and type variables (lowercase first letter).
@@ -129,16 +129,27 @@ variable, it may stand for any type.  Note that this is the same as
 Haskell but the opposite of Mercury.
 
 .Data constructors
-Code that does different things should look
-different. Therefore data construction should stand apart from function
-calls, and hence it is useful if data constructors are capital letters.  It
-could be argued that the same is true for field selection.  Suggestions
+Code that does different things should look different.
+Therefore data construction should stand apart from function calls, and
+hence it is useful if data constructors to begin with capital letters.
+It could be argued that the same is true for field selection.  Suggestions
 welcome.
 
+.Interfaces and Interface Instances
+Interfaces are to instances as types are to values,
+This is reflected in our decision to suggest that interfaces should be
+CamelCase and instances lower_case.
+Also, instances and module qualifiers can both appear within
+expressions as a prefix to another symbol.
+Instances will also appear distinct from module qualifiers.
+
 == Modules
 
 Each file is a module, the file name must match the module name (case
-insensitive).  By convention, module and file names are all lower case.
+insensitive).  By convention CamelCase is used.
+
+NOTE: I prefer lower case filenames, but I also want these to match.  Maybe
+I'll grow to like CamelCase file names.
 
 Each module begins with a module declaration.
 
@@ -146,15 +157,89 @@ Each module begins with a module declaration.
     module my_module
 ----
 
-Modules may be imported with an import declaration.
+=== Module Imports
+
+Modules may be imported with a use or import declaration.
+
+----
+    use IO
+    import RBTreeMap
+    import RBTreeMap as Map
+    import IO.getpid as getpid
+----
+
++use+ will add the module's contents to the current environment.  So the
+declaration on line 1 will import all symbols (open, getpid etc) into the
+current environment, meaning that module qualification is not required to
+use those symbols, however it may be used to avoid ambiguity either for
+human readers or the compiler.  So +use IO+ adds +getpid+ and +IO.getpid+ to
+the environment.
+
++import+ declares that we will use that module and how
+we can access its contents.  The declarations on lines 2 and 3 add a
+module name (+RBTreeMap+ or +Map+, respectively) to the current environment.
+The forth declaration imports only the +getpid+ function from +IO+ and names
+it +getpid+ in the current environment.
+
+You may be thinking that the third use of +import+ should be use; since it
+adds a function, rather than a module, to the current environment.  However
+the distinction that I'm making between use and import is that +import+
+*explicitly* updates the environment, you can always look at an import
+declaration and know which new symbols are available.  Whereas +use+ can
+update the environment in non-obvious ways.
+
+A module cannot be used with either a +use+ or +import+ declaration.
+
+Bindings created by import shadow pre-existing bindings.  It is an error for
+more than on import to bind the same symbol in the same scope level:
+
+----
+    import SortedListSet as Set
+    import RBTreeSet as Set
+----
+
+Is illegal.  However
 
 ----
-    import other_module
+    import SortedListSet as Set
+    ...
+    // some code
+    ...
+    {
+        import RBTreeSet as Set
+        ...
+        // some code using RBTreeSets
+        ...
+    }
+    ...
+    /// back to SortedListSet
+    ...
 ----
 
-A module cannot be used without an import declaration.
+is permitted.
+(Yes, module imports may appear within function bodies and so-on.)
+
+TODO: Figure out if context always tells us enough about the role of a
+symbol that modules do not need to shadow types and constructors.  I suspect
+this is true but I'll have to define the rest of the language first.
 
-And symbols can be exported with export directives.
++use+ can shadow existing bindings.  Symbol resolution works with and
+without module qualification, recall that +use IO+ adds +getopt+ and
++IO.getopt+ to the environment:
+
+* Unambiguous match: if there is a single unique match it is resolved using
+  that match.
+* Local first: If the symbol is unqualified and there is a matching symbol
+  in the local module/scope (scope refers to interfaces & instances below)
+  then the symbol is resolved.
+* Exact module qualification: If there is a matching symbol with the exact
+  module qualifiers.  +IO.getpid+ matches exactly +IO.getpid+ but not
+  +System.IO.getpid+ then that the symbol is resolved.
+* Failure.
+
+=== Module exports
+
+Symbols can be exported with export directives.
 
 ----
     export my_function
@@ -169,6 +254,161 @@ To export everything from a module use a +*+.
     export *
 ----
 
+TODO: Syntax for exporting types abstractly & fully.
+
+== Types
+
+* Algebraic types
+* parametric polymorphism (aka generics)
+* Abstract types
+* Other features may be considered for a later version
+* Type variables are lower case, type names begin with an uppercase letter
+  (Haskell style)
+
+=== Builtin types
+
+How "builtin" these are varies.  Ints are completely builtin and handled by
+the compiler where as a List has some compiler support (for special symbols
+& no imports required to say "List(t)") but operations may be via library
+calls.
+
+* Int
+* Uint
+* Int8, Int16, Int32, Int64
+* Uint8, Int16, Int32, Int64
+* Char (a unicode codepoint)
+* Float (NIY)
+* Array(t)
+* List(t)
+* String (neither a CString or a list of chars).
+* Higher order types
+
+These types are implemented in the standard library.
+
+* CString
+* Map(t)
+* Set(t)
+* etc...
+
+=== User types
+
+User defined types support descriminated unions (here a +Map+ is
+either a +Node+ or +Empty+), and generics (k and v are type parameters).
+
+----
+  type Map(k, v) = Node(
+                        m_key   :: k,
+                        m_value :: v,
+                        m_left  :: Map(k, v),
+                        m_right :: Map(k, v)
+                    )
+                  | Empty
+----
+
+TODO: Syntax will probably change, I don't like +,+ as a seperator, I prefer
+a terminator, or nothing to match the rest of the language.  Curly braces?
++|+ is also used as a seperator here.
+
+Types may also be defined abstractly, with their details hiden behind module
+abstraction.
+
+== Interfaces
+
+Interfaces are a lot like OCaml modules.  They are not like OO classes and
+only a little bit like Haskell typeclasses.
+
+Interfaces are used to say that some type and/or code behave in a particular
+way.
+
+The +Ord+ interface says that values of type +Ord.t+ are totally ordered
+and provides provide a generic compassion function for +Ord.t+.
+
+----
+    type CompareResult = LessThan | EqualTo | GreaterThan
+
+    interface Ord {
+        type t
+
+        compare(t, t) -> CompareResult
+    }
+----
+
++t+ is not a type parameter but +Ord+ itself may be a parameter to another
+interface, which is what enables +t+ to represent different types in different
+situations; +compare+ may also represent different functions in different
+situations.
+
+We can create instances of this interface.
+
+----
+    instance ord_int : Ord {
+        type t = Int
+
+        compare(a : Int, b : Int) -> CompareResult {
+            if (a < b) {
+                LessThan
+            } else if (a > b) {
+                GreaterThan
+            } else {
+                EqualTo
+            }
+        }
+    }
+----
+
+Note that in this case each member has a definition.  This is what makes
+this an interface instance (plus the different keyword), rather than an
+(abstract) interface.  The importance of this distinction is that interfaces
+cannot be used by code directly, instances can.
+
+Code can now use this instance.
+
+----
+    r = ord_int.compare(3, 4)
+----
+
+Interfaces can also be used as parameter types for other interfaces.
+Here we define a sorting algorithm interface using an instance (+o+) of the
++Ord+ interface.
+
+----
+    interface Sort {
+        type t
+        sort(List(t)) -> List(t)
+    }
+
+    instance merge_sort(o : Ord) : Sort {
+        type t = o.t
+        sort(l : List(t)) -> List(t) {
+            ...
+        }
+    }
+----
+
++merge_sort+ is an instance, each of its members has a definition, but it
+cannot be used without passing an argument (an instance of the +Ord+
+interface).  A list of +Int+s can now be sorted using:
+
+----
+    sorted_list = merge_sort(ord_int).sort(unsorted_list)
+----
+
+NOTE: This example is somewhat contrived, I think it'd be more convenient
+for sort to take a higher order parameter.  But the example is easy to
+follow.
+
++merge_sort(ord_int)+ is an interface expression, so is +ord_int+ in the
+example above.
+Interface expressions will also allow developers to name and reuse specific
+interfaces, for example:
+
+----
+    interface s = merge_sort(ord_int)
+    sorted_list = s.sort(unsorted_list)
+----
+
+More powerful expressions may also be added.
+
 == Code
 
 === Functions
@@ -318,25 +558,6 @@ Is the same as
     ... = bar(X, Y, Z);
 ----
 
-== Types
-
-* Algebraic types
-* parametric polymorphism (aka generics)
-* Abstract types
-* Other features may be considered for a later version
-* Type variables are lower case, type names begin with an uppercase letter
-  (Haskell style)
-
-=== Basic types
-
-* Int
-* Uint
-* IntN
-* UintN
-* Char
-* String (TBA)
-* Float (NIY)
-
 == Handling effects (IO, destructive update)
 
 Plasma is a pure language, we need a way to handle effects like IO and
-- 
2.7.0



More information about the dev mailing list