Notably, don't type check dynamic bindings (which can be set to
whatever) and eliminate a hang that resulted from not handling an error
at the end of the `set!` call. Also refactors some of the code in
efforts to make it a bit cleaner.
Also adds an error when `set!` can't find the variable one calls set!
on.
* feat: overwrite existing interface implementations
This commit alters the behavior of interfaces so that implementations
with the same type signature will overwrite previous implementations
with that signature--before this was a runtime error.
Previously, if a user defined two distinctly named implementations of an
interface that shared a type, Carp would panic and error at runtime if
the interface was called and resolved to the type, since it couldn't
decide which implementation to use from the type alone. After this
commit, we instead issue a warning and overwrite existing
implementations of the same type, so that defining:
```
(defn foo [] 0)
(implements zero foo)
```
will replace `Int.zero` in the `zero` interface's implementation path
list and won't result in a runtime error--instead `foo` will be called
when `zero` is called in a context in which it returns an int:
```
[WARNING] An implementation of the interface zero with type (Fn [] Int)
already exists: Int.zero. It will be replaced by the implementation:
foo.
This may break a bunch of upstream code!
```
test/interface.carp also has a concrete illustration of this case.
* chore: address hlint suggestions
* fix: don't print overridden interface implementations in info
This commit updates our handling of interface overrides to remove
interfaces from the implements meta of a function that was overridden by
a new implementation.
Similarly, this refactors primitiveInfo to prevent printing binders that
do not actually implement an interface.
* refactor: incorporate @TimDeve's error message suggestion
* feat: evaluate defs and defns in repl
* fix: better handling of static symbol evaluation
* refactor: include feedback by @scolsen (thanks)
* refactor: incorporate feedback by @eriksvedang into resolver code
* refactor: rename shouldResolve to resolver
* fix: don't set the inner env to globals in type mods
Previously, we set the inner environment of a type generated module to
the global env in cases where the overarching context didn't have an
inner env. This leads to problems where by the recognition of modules is
inconsistent, and one can't use the names of types as submodules in
certain circumstances.
This commit fixes that issue.
* refactor: refactor primitiveDefmodule
This refactor fixes a issues with meta information on submodules, for
instance, sigs on submodule functions used to result in a compiler error
about ambiguous identifiers. This fixes that.
Unfortunately, I don't have a precise idea about what exactly was wrong
with the original definition of this function. My suspicion is that the
recursion originally altered submodule paths in the wrong way, but I'm
not certain. In any case it's fixed.
* fix: ensure macros are expanded in the correct module
Previously, macro expansions folded over all forms after the top level
form, without performing any context updates on encountered
`defmodules`. This created an issue in which macro calls that produced
new bindings, "meta stubs", were *hoisted* out of submodules and into
the top-level module, creating duplicate definitions.
This commit fixes that issue by adding a special case for defmodule in
macroExpand.
* fix: ensure submodules and globals don't conflict
Previously, our module lookups during new module definition always
eventually fell back to the global environment, which caused submodules
that happen to share a name with a global module to be confused with the
global module. This change fixes that, so now one can define both
`Dynamic` (global) and `Foo.Dynamic` without issue.
* fix: remove old prefixes from vector tests
Commit 7b7cb5d1e replaced /= with a generic function. However, the
vector tests still called the specific Vector variants of this function,
which were removed when the generic was introduced. After recent
changes, these calls are now (correctly) identified as erroneous. My
guess is that they only worked in the past because of problems with our
lookups.
* chore: format code
* feat!: support defining types in modules
This commit adds support for defining types (using deftype) in modules.
Previously, all types were hoisted to the top level of the type
environment. After this commit, the type environment supports defining
nested modules just like the value env, so, calling the following:
```
(defmodule Foo (deftype Bar Baz))
```
Adds the following to the type env:
```
Foo : Module = {
Bar : Type
}
```
and the following to the value env:
```
Foo : Module = {
Bar : Module = {
Baz : (Fn [] Foo.Bar)
copy : (Fn [(Ref Foo.Bar q)] Foo.Bar)
delete : (Fn [Foo.Bar] ())
get-tag : (Fn [(Ref Foo.Bar q)] Int)
prn : (Fn [(Ref Foo.Bar q)] String)
str : (Fn [(Ref Foo.Bar q)] String)
}
}
```
Such a type is *distinct* from any type defined at the top level that
happens to also have the name `Bar`.
This commit also updates info and tests to account for types in modules.
BREAKING CHANGE: This change is breaking since it alters the names of
types that were previously defined in modules. A good example of this is
the `Id` type in the `Color` module. Previously, one could refer to this
type by simply typing `Id` since it was hoisted to the top level. Now it
*must* be referred to by `Color.Id` since `Id` at the top level of the
type env and `Color.Id` (Id in the color module) are considered to be
distinct types.
* chore: format code
* refactor: use concat instead of intercalate
* chore: remove excess parentheses
* chore: Add todo to return IO () in printIfFound
* feat: 'delete' interface (deciding whether a type is managed or not)
* refactor: Move implements function to Interface module
* feat: Automatically implement 'delete' for types defined with `deftype`
* fix: Don't implement `delete` for Pointer
* refactor: Clarify `memberInfo` function
* fix: Also check if function types are managed
* fix: Implement 'delete' for String and StaticArray.
* fix: Manage String and Pattern. Tests run!
* feat: Add `managed?` primitive
* docs: Note about primitiveIsManaged
* test: Basic test cases for managed / nonmanaged external types
* test: Make sure `managed?` primitive works
* test: Inactivate sanitizer since we're creating leaks intentionally
* feat: Removed 'isExternalType' function
* refactor: Decide if struct member takes ref or not when printing
..based on blitable interface, and 'prn' implemntation
* refactor: Use 'blit' everywhere
* refactor: Implement `strTakesRefOrNot` in terms of `memberStrCallingConvention`
* refactor: Use `remove` over `filter not`
* fix: don't set the inner env to globals in type mods
Previously, we set the inner environment of a type generated module to
the global env in cases where the overarching context didn't have an
inner env. This leads to problems where by the recognition of modules is
inconsistent, and one can't use the names of types as submodules in
certain circumstances.
This commit fixes that issue.
* refactor: refactor primitiveDefmodule
This refactor fixes a issues with meta information on submodules, for
instance, sigs on submodule functions used to result in a compiler error
about ambiguous identifiers. This fixes that.
Unfortunately, I don't have a precise idea about what exactly was wrong
with the original definition of this function. My suspicion is that the
recursion originally altered submodule paths in the wrong way, but I'm
not certain. In any case it's fixed.
* fix: ensure macros are expanded in the correct module
Previously, macro expansions folded over all forms after the top level
form, without performing any context updates on encountered
`defmodules`. This created an issue in which macro calls that produced
new bindings, "meta stubs", were *hoisted* out of submodules and into
the top-level module, creating duplicate definitions.
This commit fixes that issue by adding a special case for defmodule in
macroExpand.
* fix: ensure submodules and globals don't conflict
Previously, our module lookups during new module definition always
eventually fell back to the global environment, which caused submodules
that happen to share a name with a global module to be confused with the
global module. This change fixes that, so now one can define both
`Dynamic` (global) and `Foo.Dynamic` without issue.
* fix: remove old prefixes from vector tests
Commit 7b7cb5d1e replaced /= with a generic function. However, the
vector tests still called the specific Vector variants of this function,
which were removed when the generic was introduced. After recent
changes, these calls are now (correctly) identified as erroneous. My
guess is that they only worked in the past because of problems with our
lookups.
* chore: format code
* refactor: move primitive errors; refactor primtiveInfo
This commit is the first in what will hopefully be a series of helpful
primitive refactors. To start, we:
- Move some inline `evalError` strings into a `PrimitiveError` module,
(similar to the `TypeError`/`Types` module relationship
- Add `Reifiable` instances for String and Int types to take these
types to their XObj representation.
- Add info utility functions for converting Info data to an XObj
- Refactor the `info` primitive:
- Use monadic combinators + `maybe` instead of nested cases.
- Use helper lookup functions that take a *context*--nearly *all*
lookup calls currently extract some env, typically without doing
anything to it, to pass it to lookup. This is a sign the boundary is
incorrect and lookups should take the context instead--this will allow
us to eliminate a ton of local `globalEnv`, `typeEnv`, etc. bindings.
- Don't print hidden bindings
- Indent printed meta information.
- Color bindings blue
* chore: format code
* refactor: improve names for lookups that take a context
* feat: print hidden binders when calling info
Someone calling info might be interested in hidden binders as well, for
debugging purposes, etc. To enable this, we provide a version of show
for binders that prints hidden binders.
I've also made the printing of meta values in info more generic.
Previously, this function missed a case, namely, one where the head of a
type definition contains a concrete struct, like `Maybe` with a variable
argument, `a`. That is,
```
(deftype (Foo (Maybe a)) [x (Maybe a)])
```
Would throw a type error. This commit fixes that issue.
* fix: Handle unit members correctly in array templates
Units may be used as members in arrays just like any other type,
however, the array templates (which are specific to arrays) hadn't been
updated to handle this. This commit makes the necessary updates and adds
some utility functions in efforts to make using templates in the
compiler easier.
After this commit, the following functions should work correctly on
(Array Unit):
- endo-map
- endo-filter
- copy
- aset!
- aset-uninitialized!
- aset
- pop-back!
- pop-back
- push-back
- push-back!
Just like other types with Unit members, the only value that is stored
in the resulting type is the terminal value of Unit `()` or unit. Any
side effects are run during evaluation of arguments and emitted prior to
any structure manipulations.
Because Unit is a terminal type (contains a single value) it makes
`endo-filter` an interesting case to consider. A filter on such an array
can only either retain all elements or drop all elements since there's
no way to predicate over the single terminal object Unit:
```
(endo-filter &(fn [_] false) [() ()])
=> []
(endo-filter &(fn [_] true) [() ()])
=> [() ()]
```
While arrays of units aren't very meaningful in terms of their contents,
their lengths will be expanded and contracted appropriately, so they can
be used for their length:
```
(defn iterate []
(let-do [x [() () ()]]
(while (> (Array.length &x) 0)
(do (println* "foo")
(Array.pop-back! &x)))))
(iterate)
Compiled to 'out/Untitled' (executable)
foo
foo
foo
=> 0
```
* chore: remove debugging trace
* refactor: make endo-filter template dryer
Previously, we didn't check that type variables had consistent kinds in
type definitions, which could lead to improper types. For example, one
could define:
(deftype (Foo f a b) [x (f a) y f])
without issues even though this type is invalid since the variable `f`
is assigned two distinct kinds (nullary: * and unary: *->*).
This commit adds a check to ensure all of the instances of a type
variable in a type definition have the same kind. It also fixes an issue
whereby only higher-kinded types of one argument were allowed as type
members; now higher-kinded types of any arity are permitted (again
assuming variable kinds are consistent).
When a user writes a type that has variables with inconsistent kinds,
they will be hit with an error:
(deftype (Foo (f a) b) [x (f a) y f])
Invalid type definition for 'Foo':
The type variable `f` is used inconsistently: (f a), f Type
variables must be applied to the same number of arguments.
Traceback:
(deftype (Foo (f a) b) [x (f a) y f]) at REPL:1:1.
* refactor: Move constraints test to own module, create TestLookup module
* refactor: Extracted 'Env' module
* refactor: Extracted 'TypePredicates' module
* test: First, very simple test
* refactor: Extracted 'Managed' module
* refactor: Add 'lookupBinder' function that doesn't return an Env (often what we want)
* refactor: Move out more stuff from Lookup
* refactor: Use new 'lookupBinder' in tons of places (avoids tuple)
* refactor: Got rid of monolithic 'recursiveLookupInternal'
* refactor: Avoid boolean blindness
* refactor: Better names for some lookup functions
* refactor: More logical order in Lookup.hs
* style: Use correct version of ormolu (0.1.4.1)
* refactor: Slightly more consistent naming
* refactor: Address @scolsen:s feedback
* refactor: improve readability of interface functions
Also refactors the `define` function for readability.
The old definitions of these functions were quite unwieldy and difficult
to read. This refactor attempts to make the monadic contexts we're
juggling (often 3, Maybe, IO, Either) easier to spot.
* refactor: Add context env updaters; refactor prims
This commit contains a few more cleanups of the interface and define
functions in Primitives. It also defines a new context module for
holding functions that update context environments.
There are a lot of different places that we need to filter Units out in
order to support them as type members, this commit catches a few more
cases that we missed:
- Lambda env captures
- Case matchers
Fixes issue #1044
* Rename ty to xobjTy.
* Rename info to xobjInfo.
* Rename obj to xobjObj.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Address name shadowing.
* Remove some primes.
* compiler: refactor error reporting and add CARP_DIR check
Co-authored-by: Tim Dévé <TimDeve@users.noreply.github.com>
* set CARP_DIR in tests
Co-authored-by: Tim Dévé <TimDeve@users.noreply.github.com>
* Meta: Fix hangs on calls to meta functions
This commit fixes a subtle bug whereby setting the meta of an existing
binder would cause hangs. Ultimately, this still points to an underlying
issue in our Lookup code that causes such loops, but for now this at
least fixes the hang introduced by the doc call in `core/Tuples.carp`
(see issue #842).
The primary fix seems to be related to setting the parentEnv in a case
in which we failed to do so in `Eval.hs`. Additionally, our meta setting
macros call `eval` which causes them to be evaluated *immediately after*
expansion, causing them to be evaluated in the incorrect context in the
Tuples.carp case.
Additionally:
- Refactored primitiveMetaSet and primitiveMeta to make them cleaner.
- Only set `implements` early when we're certain we won't accidentally
overwrite the interface.
- Generalize DocStub to `MetaStub` so that it may be used for forward
meta declarations of any kind.
* Macros: Don't eval meta-set! macros
Calling eval in the body of the meta-set! macros can cause them to be
evaluated before anticipated, possibly setting the meta on a binding in
the incorrect environment. An exemplary case of this issue existed in
`Tuples.carp` (also fixed in this commit) whereby the generated
defmodule for a tuple type called doc, which was evaluated *before* the
emitted module, resulting in overwrites of global name docs instead of
the expected module function.
We retain `evals` in macros that are more useful in the repl, such as
`print-doc`. If a user wants to evaluated one of the meta-set macros in
the REPL, they'll need to add a call to eval.
* Macros: Restore calls to eval
Turns out the meta-set! macros *do* require calls to eval, unlike I
reported in the previous commit. This commit restores those and replaces
the `doc` call in `Tuples.carp` with a direct `meta-set!` to ensure we
have docs for those functions.
Also fixed a small error in implements primitive.
* Primitives: Refactor i->inner in primitiveImplements
* feat: Can omit .init in simple cases
* fix: Don't treat unqualified modules as interface sym:s
* feat: Can handle implicit init of type inside 'use':d module
* fix: Give deftypes and sumtypes correct parent environment!
* test: A few test cases
* refactor: Remove commented out code
Co-authored-by: Erik Svedang <erik@Eriks-iMac.local>
* docs: Remove a lot of duplicated information and refer to webpages instead
* fix: Remove line about (help), it does not make sense anymore
* fix: Add open-browser to nix config
Co-authored-by: Erik Svedang <erik@Eriks-iMac.local>
The `address` keyword is useful when one needs to circumvent Carp's
memory management for particular cases. However, the evaluator
previously didn't understand `address` in most contexts, so it proved
difficult to toy around with in the REPL.
This commit adds basic support for calling `address` in the REPL by
adding a case to the evaluator. This allows users to test out functions
that take `Ptr` arguments such as `Pointer.to-value`. The `address`
keyword only accepts a symbol as an argument (since getting the address
of a literal value doesn't always make sense (e.g. &2) and produces
invalid C):
(def a 1)
(address a)
=> a
(type (address a))
=> (Ptr Int)
(Pointer.to-value (address a))
=> 1
Note: one will still typically need to wrap it in a function in
higher-order contexts.
* Emit/Deftype: Add a few more special cases for Unit members
There were a few gaps remaining in our handling of Unit types as members
of user defined types, most notably, generic setters weren't handling
Units appropriately. This commit adds special checks for Unit types in
generic setters, and also accounts for a Unit refs:
- Specialize generic setter functions against Unit members
- Specialize calls to str/prn against Unit members in StructUtils
(rather than accessing the struct member, which doesn't exist in the
Unit case, we simple call Unit's prn function (added in the next
commit).
- Don't bind references to Unit values to variables. This fixes an error
whereby a reference to a Unit would generate invalid C such as:
`void* _9 = &;`
Likewise, we omit references to Units from function arguments just as
we omit Unit values.
* Unit: Add Unit type implementations for common interfaces.
Now that Unit can be used as a member type it is subject to several
interfaces, such as prn, that it previously hadn't implemented.
This commit adds Unit.carp to core which implements `prn`, `copy`, and
`zero` for the Unit type.
* Deftype: Return null pointers for Unit getters
This is *hopefully* one of the final updates needed to fully support
Unit's as member types. Getters for fields of such types have no struct
member to read, but are expected to return a void pointer; so we return
a NULL void pointer instead.
This commit also updates our emissions for function calls to prevent
assigning the results of functions with Unit and (Ref Unit) return types
to variables.
* Emit: Filter void args from lambda calls
Just as we filter void argument types from other function calls, we now
filter them from calls to lambdas.
Unit's show implementation defaults to the empty list `()`--this
presents problems when validating types, since generic type members are
replaced by the reification (a type literal) of their arguments. In the
`Unit` case, `()` yields some surprising invalid types.
So, we reify UnitTy as `Unit` instead to ensure consistency. This
permits using Unit as a member in generic types like Result.
Our initial type setting function didn't assign the Dynamic type to
DefDynamic forms; after this commit, it will. It also wasn't an
exhaustive match, leading to ugly non-exhaustive pattern match errors in
rare cases. This commit adds a clause to prevent that.
This commit adds a new obj, `LetDef` which we use to give let bindings a
similar form to Def bindings and DefDynamic bindings. This enables us to
type check `set!` calls on let bindings just as we do def bindings and
defdynamic bindings.
Now that we properly omit all member fields for Unit types, the C
structs we instantiate cannot initialize *any* fields, rendering the
zero-initialization syntax invalid. We use an empty struct instead.
This commit enables support for using values of type () (Unit) in
user-defined types such as product and sumtypes. After this commit,
types such as:
(deftype Units [action-one () action-two ()])
Are valid, and can be instantiated in the obvious way:
(Units.init (IO.println "foo") ())
Some important things to note about the implementation:
- The C structs emitted for types containing Unit members *completely
omit all unit members*. If a type in Carp has N members, the
corresponding C struct will have (N-U) members where U is the number of
members with the type `Unit`.
For example, this type:
(deftype (Foo [one Unit two Int]))
will produce the following typedef in C:
typedef struct {
int two;
} Foo;
As a special case, types that *only* have Unit's as members are represented and
initialized as completely empty structs:
(deftype Foo [empty Unit])
// emits
typedef struct {
} Foo;
Foo Foo_init() {
Foo instance = {};
return instance;
}
Such a type is merely a container for side effects.
- Side effects are not stored at all in the types that contain Unit
members. Instead, any side effects will be lifted out of the emitted C
struct and called prior to initialization.
For example, initializing `(deftype Foo [empty Unit])` with `(Foo.init
(IO.println "foo"))` will produce the following C:
main(...) {
//...
static String _10 = "foo";
String *_10_ref = &_10;
IO_println(_10_ref);
Foo _12 = Foo_init();
//...
}
- The typical operations on product fields are supported on Unit type
members, but they have slightly custom semantics. Since we don't
actually store any values of type Unit in custom types, most
updaters/getters/setters simply run a side effect.
This is mostly only supported to make the use of such members more
intuitive and allow programmers to chain side-effects within some
context, much like monadic IO in Haskell.
- Match forms also work on Unit types for parity, but again, there is no
meaningful behavior here, since Unit only has a single type
inhabitant.
As a bonus, this commit also makes it possible to use `Unit` and `()`
interchangeably in type signatures.
Previously, `expand` only printed the results of a macro expansion to
the console. However, there are legitimate use cases for having expand
return the expanded form, particularly, when nested macro calls should
be expanded but not evaluated. Consider the following:
```
(defmacro reff [] '(Ref Int <q>))
(defndynamic lt [x]
(last x))
(defmacro wrapped [x] (lt x))
```
The above, when called with the `reff` macro as an argument, won't work
as expected, since macros don't evaluate their arguments:
```
(wrapped (reff))
=> reff
```
Currently, the solution is to wrap the `lt` argument in `eval`, but this
won't work, since even though it will expand the macro, it will try to
evaluate `Ref` after the expansion!
```
(defmacro wrapped [x] (lt (eval x)))
(wrapped (reff))
=> Can't find symbol 'Ref' at REPL:1:21.
```
Bummer. However, once we change expand to return a macro form instead of
echoing, we have a solution that allows us to expand a macro without
forcing an evaluation:
```
(defmacro wrapped [x] (lt (expand x)))
(wrapped (reff))
=> <q>
```
This allows programmers to define macros that can take expanded macros
as arguments. Because macros never evaluate their arguments, all calls
to `expand` need to be in the body of the wrapping macro to work:
```
(defmacro wrapped [x] (lt x))
(wrapped (expand (reff)))
=> (Ref Int <q>) ;; because:
;; (wrapped (expand (reff))) === (lt (expand (reff)))
;; (lt (expand (reff))) === (last (expand (reff)))
;; (last (expand (reff))) === (reff)
;; (reff) === (Ref Int <q>)
```
Expansion of values and symbols works as one might expect:
```
(expand 1)
=> 1
(expand \a)
=> \a
(expand "foo")
=> "foo"
(expand [])
=> []
...etc.
```
So, if a macro designer expects a macro to be used in combination with
some other macros, `expand` can typically be applied to all arguments
without penalty.
In certain contexts, naked identifiers (those without a qualifying path)
can be resolved to either global static bindings *or* dynamic bindings.
In the past, we've always preferred dynamic bindings first in the
evaluator, then, if we failed to find them, fell back to global
bindings.
However, for the s-expr command for getting the s-expressions of
bindings, dynamic bindings can conflict with global bindings for
interfaces functions etc.
This commit makes it possible for callers into the dynamic evaluator to
specify whether they want to prioritize dynamic or global lookups. All
internal calls into eval from eval itself use whatever flag was passed
in at the top level. For parity, all existing calls to eval prefer
Dynamic bindings *except* for invocations of the s-expr command. That
acknowledged, we should revisit calls to eval and double check that the
lookup priorities make sense (for instance, primitives should likely
prefer dynamic bindings while identifiers in static terms like `defn`
bodies should prefer global/static bindings by default.
This change reworks our code organization in the evaluator, moving from
explicit case statements on lookups to one that leverages
applicative/alternative and monadic structuring over lookup return
values.
Since lookups return Maybes, we can exploit these typeclasses to make
the code more modular. It's hopefully, much easier to switch the order
in which we perform binding lookups after this change, as we let the
alternative and bind implementations handle pattern matching over
structure for us.
This is necessary to ensure we distinguish between the symbol "Type" as
returned by `type` from the type/module "Type" potentially defined by a
user:
```
(deftype Type)
(type Type)
=> Module
(type (type 1))
=> Type
;; Even though the symbol conflicts with the module defined above,
;; subsequent calls to type return the correct result.
(type (type (type 1)))
=> ()
```
Extends Carp's support for type reflection by returning types for
values as well as bindings.
`type` now also returns a valid Carp expression/s-expression and so its
output can be used as input to dynamic functions and macros (prior to
this commit, `type` printed the type to the REPL but did not return a
meaningful expression in Carp).
Here are a few illustrations of the behavior:
```
(def x 1)
;; type now returns an s-expression/symbol
(type x)
=> Int
;; It also works on values
(type 1)
=> Int
(type 2b)
=> Byte
(type "foo")
=> (Ref String <StaticLifetime>)
;; It works on more complex values as well
(type Maybe)
=> Module
(type Maybe.Just)
(Fn [a] (Maybe a) <StaticLifetime>)
;; reports honestly about polymorphism
(type (Maybe.Nothing))
=> (Maybe a)
(type (Pair.init 1 2))
=> (Pair Int Int)
;; What about the type of types?
(type (type 2))
=> Type
;; Or the type of types of types?
(type (type (type 2)))
=> ()
;; One more time!
(type (type (type (type 2))))
=> ()
;; so, () is the fixpoint of type, and is reached after two applications
(type zero)
;; the type of an interface is all of its implementations
=> (((Fn [] (Array a) <StaticLifetime>) (Fn [] Bool <StaticLifetime>) (Fn
[] Byte <StaticLifetime>) (Fn [] Char <StaticLifetime>) (Fn [] Double
<StaticLifetime>) (Fn [] Float <StaticLifetime>) (Fn [] Int
<StaticLifetime>) (Fn [] Int16 <StaticLifetime>) (Fn [] Int32
<StaticLifetime>) (Fn [] Int64 <StaticLifetime>) (Fn [] Int8
<StaticLifetime>) (Fn [] Long <StaticLifetime>) (Fn [] (Maybe a)
<StaticLifetime>) (Fn [] (Pair a b) <StaticLifetime>) (Fn [] (Quadruple
a b c d) <StaticLifetime>) (Fn [] String <StaticLifetime>) (Fn []
(Triple a b c) <StaticLifetime>) (Fn [] Uint16 <StaticLifetime>) (Fn []
Uint32 <StaticLifetime>) (Fn [] Uint64 <StaticLifetime>) (Fn [] Uint8
<StaticLifetime>)))
```
As shown in the example above, this change also includes a cosmetic
update to the representation of lifetime variables, which are surrounded
in <> to distinguish them from type variables.
This commit also adds a new `kind` primitive that reports on the kind of
a binding or value:
```
(def x 3)
(kind x)
=> Base
(kind 2)
=> Base
(kind Maybe.Just)
=> Higher
(kind (Maybe.Just 2))
=> Higher
```
`kind` and `type` both support interactive development in the repl, for
example, a user can rely on `kind` to check the kind of a type they plan
on using in an interface that demands a higher-kinded argument.
Likewise, they both also support developing macros based on type
information.
getPath returns the path of a form. The DefDynamic case was missing from
the function's definition, resulting in failures when calling functions
such as the meta set primitive on dynamic defs. This change fixes such
issues.
It turns out local binding XObjs and global binding XObjs have a very
different structure, making the type checking we perform on set! in
local contexts problematic.
While a global def resolves to a binding than contains: an XObj with the
bindings corresponding sympath and type
While a let binding's binder *only contains the bindings value*--so the
`x` in `let [x 2]` will resolve to an XObj only of `(Num Int....)`
instead of the more robust `(XObj Lst [SymPath....])` etc. This breaks
some assumptions of the set! type check, so until we make let bindings
and global bindings structurally equivalent, we won't type check `set!`s
on local bindings.
This change handles a case the previous implementation missed, that is,
when a user attempts to set a global variable in a local environment,
such as a let binding:
```
(def x 1)
(let [y 2] (set! x y))
(Int.+ 1 x)
=> 3
```
Internal definitions have precedence over globals, that is, if another
`x` in the let binding above shadowed the `x` in the global environment,
only the local `x` would be updated:
```
(def x 1)
(let [x 2] (set! x 3))
(Int.+ 1 x)
=> 2
```
This change enforces that `set!` calls do not change a variable's type.
Attempting to set a variable of type T to a value of type W will result
in an error. Dynamic variables are not type checked and may be set to
any value.
Generally speaking, it's safer to enforce this restriction on variables
for soundness reasons, and it's often the behavior programmers desire.
Additionally, this check resolves a problem whereby, previously,
reassigning a variable to a value of a different type and using it in
code thereafter would cause the compiler to hang.
Here's a brief illustration of the new behavior:
```
(def x 1)
(set! x "foo")
=> can't `set!` x to a value of type (Ref String a), x has type Int at
REPL:1:7.
(set! x 2)
(Int.+ 1 x)
=> 3
;; Dynamics can still be set to anything
(defdynamic y 2)
(set! y "foo")
:i y
(defdynamic y "foo") : Dynamic
;; Of course, one can still *redefine* a variable to change its type.
(set! x 3.4)
=> can't `set!` x to a value of type Double, x has type Int at REPL:1:7.
(def x 0.0)
[WARNING] Definition at line 1, column 1 in 'REPL' changed type of 'x'
from Int to Double
(set! x 10.0)
:i x
=> x : Double
```
Previously, we retyped *all* variables passed to `set!` as `DynamicTy`,
thus a variable set to a concrete type like `Int`, when `set!` would
suddenly have the type `Dynamic`. This change corrects that issue.
Variables are now typed according to the type of the value passed to
`set!`, and are only typed as Dynamic when the variable itself was
initially typed as `Dyanmic` (declared using defdynamic). To illustrate:
```
;; old behavior
(def x 1)
:i x
=> x : Int
(set! x 2)
:i x
=> x : Dynamic
;; new behavior
(def x 1)
:i x
=> x : Int
(set! x 2)
:i x
=> x : Int
(defdynamic y 2)
:i y
=> y : Dynamic
(set! y 3)
:i y
=> y : Dynamic
```
The `last` command relies on Haskell's `Prelude.last`--which will crash
on an empty list input. We report an error for incorrect input to last,
but we didn't handle the empty list case. This small change to our
pattern match ensures errors in carp code produce Carp's last command
error rather than causing `Prelude.last` to throw an error on empty list
input.
Like the previous two commits, this commit extends support for
reflection to yet another type, externally registered functions,
allowing us to support calls such as `(arity Int.+)`.
Just as the prior commit added support for capturing the arity of
registered commands, this one captures the arity of registered
primitives, enabling one to make calls such as `(arity defmacro)`, etc.
I have also moved the dummy argument name function into Util.hs since
it's used in both Commands.hs and Primitives.hs.
Previously, the command xobjs did not capture the arity of their
definitions. After this commit, they do, which enables us to surface
useful information about commands in reflective modules such as
core/Introspection.
Calls such as `(arity Dynamic.+)` and `(arity Dynamic.load)` will work
after this change.
This change makes refs "constructible" in the perspective of the type
system by allowing them to stand for polymorphic constructors such as
`(f a b)`. After this commit one can take an interface such as:
```
(definterface out (Fn [(f a b)] a))
```
and provide an implementation over references:
```
(defn copied [x] @x)
(implements out copied)
```
`copied` is a valid implementation of `out`, since it simply copies the
value of a reference, dropping its lifetime.
Essentially, this makes the type system slightly more flexible, allowing
one to view references as type constructors in their own right.
Previously, some of the documentation generations specific to Carp's
core library doc generation were hardcoded in renderdocs, thus, even
though users could set their own logo and url, these weren't honored
during the generation of module documentation, resulting in a bunch of
docs pointing to logos and urls that weren't intended.
This change updates the rendering code to honor the url and logo set in
the user's Project config.
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.
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.