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)
```
This PR moves interface handling code out of Primitives.hs and into its
own module. The actual primitives user's rely on to define and implement
interfaces still live in Primitives.hs, but now all the
registration/internal context mgmt code related to interfaces lives in
its own module.
Hopefully this will make it easier to modify and extend!
This change mostly just factors out case statements from a variety of
functions using two useful utilities `maybe` and `either`.
Essentially, these functions enable you to express a case against a
Maybe or Either in terms of a function and default value, and generally
help readability. In our case they:
- Allow us to remove tons of repetitive return statements.
- Allow us to remove do's.
- Eliminate some cases and simplify the code structure.
This commit contains no behavioral changes
This commit replaces instances that used `Data.Map` functions to
manipulate MetaData with their `Meta` module counterparts. In some cases
this allows us to drop the `Data.Map` dependency. In others, it's still
required since we take a similar approach for Envs--we should eventually
abstract away the underlying Map representation of `Env` too.
The Meta module does two things:
- Collects functions related to working with Carp MetaData in one place
- Hides the underlying implementation of MetaData
MetaData is backed by `Data.Map` previously, we'd alter MetaData by
getting the underlying map directly and calling Map functions to modify
the contents. The functions in the Meta module encapsulate this--now
callers may manipulate MetaData without having to know anything about
the underlying Map implementation. This should make it easier to swap
out the underlying representation in the future as well.
Previously, I'd removed the type var check in constraint conflict
checking. This doesn't seem to be problematic, but it might not be the
best way to implement the desired result, which is special conflict
checking for interfaces.
This approach is more conservative and simply bypasses the check for
interface implementation constraint solving only.
Previously we disallowed unification of type variables with types that
contained the same variable name; e.g. unification of `a` with `(Ref
a)` was disallowed by the type system.
While this was a pain for defining custom struct types, it becomes even
more problematic after the past commit, as, since interfaces now solve a
constraint, it means a n interface (a -> a) cannot be unified with an
implementation ((Ref a) -> (Ref a)).
As it turns out, simply removing this rule causes no evident breakages
or problems, and makes the newly constrained and type rigorous interface
matching more ergonomic to work with in addition to removing the custom
struct definition annoyances.
Previously, we only checked that interface signatures and implementation
signatures were unifiable. This check is actually not sufficient, and
leads to interface typings that are overly permissive. For example, the
interface (a -> a) unifies with everything, even an implementation typed
(Int -> Double) which is patently incorrect.
Solving a basic constraint between the interface an implementation fixes
this issue.
This change makes it possible to declare auto-derived getter/setter
functions on types as implementations of interfaces, e.g. Pair.a.
The same checks for normal functions apply to the instantiations. For
example, given an interface:
```
(definterface left (Fn [(Ref (f a b))] (Ref a)))
```
`Pair.a` can implement this interface, but `Maybe.Just` cannot--since it
results in a kind mismatch.
I also realized that interfaces are currently too flexible/permissive.
For example, given an interface:
```
(definterface id (Fn [a] a))
```
Most, if not all, functions can implement this interface, even when the
implementation clearly doesn't conform. For example, `Pair.a` or even a
function with the type `([String] Int)` are both valid implementations
of this interface. This is because we currently only check if interfaces
and implementation signatures are unifiable. Since we consider any type
unifiable with a variable, any implementation passes, even when the
argument type and return type are clearly not the same. We'll need to
constraint solve on the interface and implementation signatures to fix
this--since that's a more significant change, it's omitted from this PR.
For now a new TODO will have to suffice.
`s-expr` accepts either a binding that evaluates to a List or a list
literal such as `'(1 2 3)`. Other forms do not yield s-expressions, and
so we return an error. For example, `(s-expr '[1 2])` returns an error
since an array does not form a list s-expression.
Originally, s-expr was a primitive and didn't evaluate its arguments.
This however make it difficult to use, in addition to forcing us to make
an extra unnecessary lookup. This commit converts s-expr to a command,
which makes it a lot simpler to use in macro definitions and in dynamic
code in general.
Additionally, I've enhanced it to do two things:
- Allow users to get the module for a type instead of the definition
form using an optional argument.
- List the contents of a module upon getting its s-expr.
Many thanks to @hellerve who both suggested this change and advised.
Previously, introspecting an interface only returned `definterface` and
its name. We now return the form corresponding to the interface's types
signature, which enables us to introspect more effectively; e.g.
returning the appropriate arity of an interface.
Unlike many of the other datatypes in Obj.hs, Info does not introduce
mutually recursive dependencies. Obj.hs is quite large and contains a
lot of different functionality, so I think it helps to move anything
that we can separate out into its own module in order to start
simplifying the Obj.hs and its responsibilities.
In Carp, types automatically generate modules. When a type binding is
passed to `s-exp` it returns the deftype form rather than the type's
module form.
Before we used dyanmicNil, which, while equivalent to the empty list,
isn't quite the same thing. This new approach ensures an empty list is
actually printed to the REPL.
This commit makes the forms returned by the s-exp primitive usable with
other dynamic functions that operate on symbols. By default, a special
form like `Defn` is *not* a symbol. So, we map all the special forms to
symbols in order to use the returned s-exps smoothly with other dynamic
functions (which operate on symbols).
This is another special case. Instead of `(defmodule Foo)` it will only
return the module name.
Note that we only return the module name if it's actually only a module
and not found in the type environment.
This primitive returns the s-expression associated with a binding--in
other words, the form a user wrote to define a binding.
We handle special cases such as interfaces and deftypes, whose original
defining forms are stored in the type environment instead of the global
environment.
Note that presently this doesn't always return *exactly* the form the
user wrote. Specifically, the following cases are special:
- Interfaces -- returns `(interface foo)` instead of the full form
- Defndynamic -- returns `(dynamic foo [x] x)` instead of `defndynamic`
- Defmacro -- returns `(macro [x] x)` instead of `defmacro`
In spite of these wrinkles, I think it's a useful function to have,
particularly for type introspection in dynamic functions/macros.
Previously, sig constraints were applied to interior let bindings just
the same as they were to root defn forms (due to a recursive call to
genF). Now, we ignore sig constraints *unless* we're constraining the
root definition form (the form that's actually constrained by the sig).
Now that sigs are being unified correctly, this over-constraining caused
issues with any functions using let bindings to anonymous functions
since the lambdas were constrained by the defined functions signature.
See Binary.unsafe-bytes->int16-seq and family for one example.
Previously, sig resolution for definitions inside of modules didn't work
because this function didn't check for relative paths. This meant that
pretty much all signatures weren't honored, since forward declarations
of metadata only work within the context of modules, and so are
unqualified paths (the right module qualifications need to be extracted
from the Context).
The two success cases on SymPaths were quite similar, so, I've factored
out the pattern match into a case clause, and the shared logic into a
local binding.
Previously, if one called:
```
(implements inc "foo")
(implements inc "foo")
```
It would result in duplication in the implements meta `(inc inc)`.
This commit resolves that issue--interfaces will only be added to the
`implements` meta if they're not already present.
This commit does two things:
1. Updates `primitiveMeta` to handle a previously unhandled case, a
qualified name in a module.
Before `meta` use to fail to find its argument binding in a cases
like this one:
```
(defmodule Foo (hidden bar) (meta Foo.bar "hidden"))
```
This is because `meta` used to cons path strings to the path even if
the path was already qualified with a module name as in the example
above. We now treat these cases separately, and instances like the
above work.
2. Update `primitiveImplements` to set the implements meta to a dynamic
list of interface names, instead of a single interface.
This makes sense since functions could implement multiple generic
interfaces. An example of the new behavior:
```
(implements inc foo)
(implements dec foo)
(meta foo "implements")
=> (dec inc)
```
We'll now emit a nice warning when a user implements an interface that
isn't defined. Retro active registry still works--so if the user defines
the interface before calling the function, the implementation is
registered correctly, so we only show a warning since so long as the
interface is defined before use, implementing it before it's defined is
OK.
Before, we were only looking up interface implementation in a single
context. We needed to use something like MultiSymLookup, but on the
presence and value of a meta key, rather than a binder name.
So, I extracted the local fns in multiSymLookup out into their own
functions and make lookup functions generic, and of the type:
LookupFunc :: a -> Env -> [Binders]
This seems to suit most purposes. Eventually, MultiSymLookup can be
redefined in terms of these simpler lookup mechanisms.
This commit makes the implements primitive work similar to `doc`--If one
calls it before a function is defined, once the definition is provided,
it will auto-implement an interface.
This change adds a new primitive Implements which changes interface
implementations from being implicit to being explicit. Going forward,
users will have to declare (implements <interface> <implementation>) to
explicitly add a function to an interface. This provides two benefits:
- Prevents unwitting name clashes. Previously, if one defined a function
that happened to have the same name as an interface, it was
automatically assumed the function implemented that interface when this
is not always the intention, especially in large programs.
- Name flexibility. One can now implement an interface with a function
that has a different name than the interface, which allows for greater
flexibility.
I've updated core to make the necessary calls to the new primitive.
Since str and copy are derived automatically for types, we treat these
functions as a special case and auto-implement the interfaces.
This commit makes function types unifiable to *only* polymorphic
constructors (StructTy with a VarTy name). This enables one to implement
interfaces defined against constructors against functions so long as:
- The number of function arguments match the number of type constructor
arguments.
Thus, one can define:
```
(definterface constructor (Fn [(f a b)] (f a b)))
(defmodule Test (defn constructor [f] (the (Fn ([a b] a) f))))
```
But how is this useful?
Here's one scenario this corrects in practice. In Haskell, the `some`
function is typed generically as `some :: f a -> f [a]`. `some` takes a
type and successively applies it until it returns an empty value, then
it returns a list of results of the applications. This is great for
types that actually have state, such as parsers, but for many values of
`f` it makes no sense. E.G. given a `Maybe` the function will never
terminate, since `Maybe.Just x` will never transform into the empty
value on its own. This problem is even worse when we don't have inherent
laziness to help us short-circuit application where possible.
In fact, using an obvious definition of `some`, a function is the only
(non-bottom) type in the `f a` position that may lead to eventual
termination without requiring rewriting `some` to explicitly match
against values. One could tuck a function away in a type constructor and
devise a clever enough instance of choice to make this work, but it's
simpler to define it against a function.
Another case: type equivalences. When types are unifiable with
constructors, it gives us an easy way to define concepts generically
across types and functions.
```
(definterface app (Fn [(f a) a] a))
(defmodule Func (defn app [f x] (f x)))
(defmodule Maybe (defn app [m x]
(match m (Maybe.Nothing) (Maybe.Nothing)
_ (Maybe.Just x))))
:i app
app : (Fn [(f a), a] a) = {
Func.app
Maybe.app
}
(definterface compose (Fn [(f a) (f b)] (f c)))
(defmodule Func (defn compose [f g] (fn [x] (f (g x)))))
:i Func.compose (Fn [(Fn [a] b c), (Fn [d] a c)] (Fn [d] b e))
;; In this case, we define composition as the explicit application of
;; the product
(defmodule Maybe (defn compose [ma mb]
(let [x (match ma (Maybe.Nothing) (zero)
(Maybe.Just a) a)
y (match mb (Maybe.Nothing) (zero)
(Maybe.Just b) b)]
(Maybe.Just (Pair x y)))))
:i compose
compose : (Fn [(f a), (f b)] (f c)) = {
Func.compose
Maybe.compose
}
```
In a more general sense, this would enable us to use functions as a
constructor type, analogous to the use of (->) in haskell. The gist is,
this commit will let us extend our higher-kinded generic functions to
functions.
This commit does two things:
- Update validate to permit type defintions like:
`(deftype (Higher (f a)) (Obj [(f a)]))`
- Replace all type vars in such cases so that we generate the correct C
code.
This change supports more flexible Sumtype declarations.
We used to allow a sumtype declaration such as:
`(deftype (State a) Done (Value [a]))`
Which the compiler accepts. However, concretization for this type
failed, since we didn't handle the solitary Sym case `Done` when
concretizing the type.
This commit fixes that issue, so that now the following works:
```
(deftype (State a) Done (Value [a]))
;; Omitting the type annotation here would result in the obvious
;; unresolved generic type error.
(defn emit-done [] (the (State Int) (State.Done)))
(emit-done)
;; => State.Done
```
Looks like I forgot a tiny but important change for the polymorphic
constructor support--constraint solving!
I only found this out when refactoring an applicative <*> or 'sequence'
definition-- the definition has a polymorphic constructor with a
function argument: `(f (Fn [a] b))`.
When carp attempted to resolve this type against a type returned by lift
which has the type `[a] (f a)`, a generically lifted value, including
functions, it complained:
`can't match (r1 r2)` with type `(r3 (Fn [r4] r5))`. (numbers are
arbitrary here and just for illustration).
Sure enough, we weren't solving for such a constraint. The constraint
resolution now works as expected.
I've updated the UninhabitedConstructor type error message based on
@hellerve's excellent suggestion. I've also tried to make the kind
mismatch interface error clearer.
This change checks that members of struct types are completely
inhabited. Using uninhabited constructors was previously allowed, but
led to errors.
After this commit, declarations like:
```
(deftype Foo [pos Maybe])
```
Will throw a repl error.
This commit adds basic kinds checking on interfaces, such that an
interface that takes a type constructor (f a) can't be implemented by a
function using a concrete type like `Int` in that position.
Made the syntax `(register-type ABC "abc" [fields])` to work.
Forced the scoring of a `ExternalType` to 0 to avoid emitting code before the actual typedef.
Now that structs may have a variable in the constructor position, I've
updated the Obj to Ty (and inverse) translations to account for vars or
concrete type names.
In order to support polymorphic constructors (higher-kinds) we'll need
to resolve type variables in the constructor position, such as (a b).
This is a first step toward supporting polymorphic constructors. We add
a new type to represent concrete struct names, and have updated the
struct type resolving code to handle the presence of variables and
resolve to the new ConcreteName type.
Previously, we'd stopped doing this, but it leads to issues with matches
on functions, e.g.
```
(deftype A (F [(Fn [] Int)]))
(defn m [a]
(match a
(A.F f) (f)))
(defn main []
(println* (m (A.F (fn [] 123)))))
```
Will result in a runtime error if the environment isn't extended.