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

Add s-exp primitive and Introspection module #826

Merged
merged 12 commits into from
May 23, 2020

Conversation

scolsen
Copy link
Contributor

@scolsen scolsen commented May 22, 2020

This PR adds an s-exp primitive, which returns the s-expression or form associated with a binding. It's similar to the default behavior of dynamic xobjs, but it does some extra checking on receiving module xobjs to return the original deftype forms whenever the modules are generated from a type.

s-exp is the backbone of a new Introspection module, which contains predicates about bindings and their associated forms. One can use this module in a dynamic context to determine if a binding is a function, struct, sumtype, interface, it's arity, etc.

Here are a few examples of s-exp:

鲤 (def foo 3)
鲤 (s-exp foo)
=> (def foo 3)
鲤 (defn bar [x] x)
鲤 (s-exp bar)
=> (defn bar [x] x)
鲤 (deftype Foo [x Int])
鲤 (s-exp Foo)
=> (deftype Foo [x Int])
鲤 (deftype (Bar a) (Baz [a])) 
鲤 (s-exp Bar)
=> (deftype Bar (Baz [a]))
鲤 (definterface carp (Fn [a] a))
鲤 (s-exp carp)
=> (definterface carp)
鲤 

and the Introspect module:

鲤 (Introspect.function? bar)
=> true
鲤 (Introspect.arity bar)
=> 1
鲤 (Introspect.function? foo)
=> false
鲤 (Introspect.variable? foo)
=> true
鲤 (Introspect.type? Foo)
=> true
鲤 (Introspect.struct? Foo)
=> true
鲤 (Introspect.arity Foo)
=> 1
鲤 (Introspect.sumtype? Foo)
=> false
鲤 (Introspect.sumtype? Bar)
=> true
鲤 (Introspect.arity Bar)
=> (1)
鲤 (deftype (Qux a) (Goo [a]) (Loo [a Int]))
鲤 (Introspect.arity Qux)
=> (1 2)

Let's go ahead and say this fixes #560?

scolsen added 8 commits May 21, 2020 19:32
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.
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 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 commit changes the s-exp primitive to lookup paths in the global
env--using the context env isn't sufficient for type/module definitions.
The introspect module contains functions that return information about
program bindings based on their s-expressions.
@scolsen scolsen requested review from eriksvedang and hellerve and removed request for eriksvedang May 22, 2020 05:30
Copy link
Member

@hellerve hellerve left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really great, amazing work!

Maybe we should point out somewhere what happens on name conflicts, e.g. when a thing is both a type and a module. Which binding wins? I see below that this is special-cased and we get the type, so we should probably tell people about that.

And how would we go about getting the module, if we really wanted to? This is not necessarily a breaker for this PR, but IMO there should definitely be a way. Maybe we could separate type and value handling and push that logic into Carp?

src/Primitives.hs Outdated Show resolved Hide resolved
src/Primitives.hs Outdated Show resolved Hide resolved
scolsen added 2 commits May 22, 2020 11:42
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.
@scolsen
Copy link
Contributor Author

scolsen commented May 22, 2020

This looks really great, amazing work!

Maybe we should point out somewhere what happens on name conflicts, e.g. when a thing is both a type and a module. Which binding wins? I see below that this is special-cased and we get the type, so we should probably tell people about that.

And how would we go about getting the module, if we really wanted to? This is not necessarily a breaker for this PR, but IMO there should definitely be a way. Maybe we could separate type and value handling and push that logic into Carp?

Great points as always. I think it'd be really valuable to be able to get either the type or module of a binding. I'm not certain, but that functionality may still need to remain in the compiler since it requires peeking into two different environments.

If you don't mind, I'll leave this out of this PR for the sake of simplicity, but it's definitely worth pursuing, so I'll add an issue for it!

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.
@sdilts
Copy link
Contributor

sdilts commented May 23, 2020

I like it! Instead of s-exp it might be more ergonomic to name it something like get-sig or something similar, as it seems like the getter version of sig. To me, the name feels kind of like stoi, or itoa in C: the name makes sense, but it's hard to read or remember if you don't use it all the time.

@eriksvedang
Copy link
Collaborator

Looks great to me! I kinda like the s-exp name but we can discuss it a bit more before merge:ing, perhaps there's something better.

@scolsen
Copy link
Contributor Author

scolsen commented May 23, 2020

I also like s-exp, but I'm open to alternatives! @sdilts I like what get-sig suggests, but I think it's too narrow for this case. Can you elaborate your line of thinking around stoi and itoa a bit more? That also feels somewhat narrow to me, since we're working with forms not strings, but maybe you're thinking of it in a more general sense that I'm missing?

@sdilts
Copy link
Contributor

sdilts commented May 23, 2020

With the naming thing, I don't like abbreviations unless they are used quite often and are pretty common. In most cases, it makes it hard for me to remember the correct names, and I think that stoi and itoaare particularly egregious examples. The name should at least should use expr instead of exp, as exp is usually an abbreviation for an exponent function.

Looking over what the function does again, I agree that get-sig is too narrow, My bad eyes missed that it returns whole function definitions and not just the signatures. The name makes much more sense with that in mind.

@scolsen
Copy link
Contributor Author

scolsen commented May 23, 2020

With the naming thing, I don't like abbreviations unless they are used quite often and are pretty common. In most cases, it makes it hard for me to remember the correct names, and I think that stoi and itoaare particularly egregious examples. The name should at least should use expr instead of exp, as exp is usually an abbreviation for an exponent function.

Good point! I agree that full names are better, unless anyone objects, I'm fine with calling it s-expression; otherwise we can use the clearer abbreviation that you suggested s-expr

@eriksvedang
Copy link
Collaborator

I agree that expr is better to avoid mixing it up with exp. Maybe expr is an OK name actually? It's definitely easier to type...

@scolsen
Copy link
Contributor Author

scolsen commented May 23, 2020

Sounds good! Made the name change...but now I'm weirdly getting an error in InitialTypes.hs

@scolsen
Copy link
Contributor Author

scolsen commented May 23, 2020

Weirdly the error doesn't happen when the form is named s-exp but it happens when it's named expr....

@scolsen
Copy link
Contributor Author

scolsen commented May 23, 2020

Ah, looks like it has to do with the fact that some macro bindings are named expr so it doesn't work due to #659

is s-expr ok for now then?

@sdilts
Copy link
Contributor

sdilts commented May 23, 2020

Sounds good to me!

@eriksvedang
Copy link
Collaborator

Haha oops, that’s weird. Works for me.

@eriksvedang eriksvedang merged commit ef5c85e into carp-lang:master May 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Reflection Module Proposal
4 participants