The previous commit added privacy checking to the evaluator--as it turns
out, this is only sufficient for top-level forms--interior forms, e.g.
forms in a `defn` are examined by `Expand` via `expandAll` and never
reach the `Eval` check.
To remedy this, I've added the check to symbol expansions. I've also
tweaked the signature of `expandSymbol` to align it with other functions
(e.g. `expandArray`).
Even though `private?` has been around for a while, and we document the
behavior of `private` as marking a binding as private to a module--it
was never implemented as far as I can tell.
This implements private by adding a simple check to the evaluator. If a
binding is found in the global context, we check if it's marked as
private. If so, we inform the user that it can't be called from outside
of its module.
Note that we don't perform this check if the binding is found in the
internal env, since that means it's a function called within the same
module and thus is ok (even if marked as private).
After this change, something like the following works, granting us
proper encapsulation:
```
;; File Foo.carp
(deftype Foo [bar Int])
(private Foo.bar)
(defmodule Foo
(defn get [foo]
(Foo.bar foo))
)
;; Enter REPL
(load "Foo.carp")
(Foo.bar &(Foo.init 1))
The binding: Foo.bar is private; it may only be used within the module
that defines it. at REPL:1:2.
@(Foo.get &(Foo.init 1))
Compiled to 'out/Untitled' (executable)
1
=> 0
```
N.B. I also had to remove a private declaration from fmt-internal--this
declaration didn't really make much sense anyway, as fmt-internal is a
global function, so module-based privacy is not enforceable.
The opaque type is an uninhabited type with no constructors. Opaque can
be used to force some abstract type to range over a type constructor
without concerning oneself with the inhabitant of the constructor--in
other words, it may be used to enable a type to work for all inhabitants
and can express relationships between types. It can facillitate generic
programming.
Consider an example:
```
;; The type of indicies over containers of a single type argument
(deftype (Index (f Opaque) b) [at b])
(definterface tabulate (Fn [(Ref (Fn [(Index (f Opaque) b)] c))] (f c)))
(definterface positions (f (Index (f Opaque) b)))
(implements tabulate tabulate)
(defn tabulate [h]
(fmap h @&positions))
(deftype (Tuple a) [x a y a])
(defmodule Tuple
(sig positions (Tuple (Index (Tuple Opaque) Bool)))
(def positions (Tuple.init (Index.init true) (Index.init false)))
)
```
In the above example, the Opaque type allows us to define tabulate
generically defined over Tuples without having to ensure their
inhabitants match, allowing us to fully determine the resulting values
type via tabulate's function argument. Without Opaque, Index would
contain a generic type which would be unreseolved upon the call to
`tabulate`. It allows us to ensure the `positions` we call are the
positions of the correct constructor type `f` wihtout worrying having to
restrict ourselves to only calling `Indexes` over an `f` of a specific
type (e.g. `(f a)`)--in other words, it allows us to constrain
functions by constructor types only.
Thanks to Opaque, tabulate can generate an `(Array Int)`, `(Array
Bool)`, `(Array String)` all solely dependent on the return type of `h`.
Prior to this commit, a type such as: `(deftype (Foo (f a) [bar a]))`
was rejected by the compiler. However, such a type is sensible--Foo
ranges over some container type `f`, and it is essentially Foo's job to
provide a view into `f`, granting access to `a` and whisking `f` away
via the phantom constructor.
After this change, this type definition is now valid.
Note that one often needs to wrap phantom constructor cases in `the`
unless the type can be determined by the overarching context:
```
(deftype (Foo (f a) [bar a]))
(the (Foo (Maybe Int)) (Foo.init 1))
;; => (Foo 1)
```
The first pattern is one I've found handy for returning some data
structure that contains the results of running two different
ownership-taking functions on some argument.