Skip to content

Commit

Permalink
Free variable analysis on fixed structure
Browse files Browse the repository at this point in the history
  • Loading branch information
vanschelven committed Sep 21, 2017
1 parent 2d9ee95 commit 863a61d
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 0 deletions.
26 changes: 26 additions & 0 deletions doctests/free_variables.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
>>> from dsn.form_analysis.structure import MalformedForm, ValueForm, VariableForm, QuoteForm, DefineForm, IfForm, LambdaForm, SequenceForm, ApplicationForm
>>> from dsn.form_analysis.structure import FormList, Symbol, SymbolList

>>> from dsn.form_analysis.free_variables import free_variables
>>> from dsn.form_analysis.constants import VT_INTEGER

>>> free_variables(ValueForm(VT_INTEGER, 32))
set()

>>> free_variables(VariableForm(Symbol("a")))
{'a'}

>>> sorted(free_variables(SequenceForm(FormList([
... VariableForm(Symbol("a")),
... VariableForm(Symbol("b")),
... ]))))
['a', 'b']

>>> free_variables(LambdaForm(
... SymbolList([Symbol("bound_by_param")]),
... FormList([
... DefineForm(Symbol("bound_by_define"), VariableForm(Symbol("a"))),
... VariableForm(Symbol("bound_by_param")),
... VariableForm(Symbol("bound_by_define")),
... ])))
{'a'}
77 changes: 77 additions & 0 deletions dsn/form_analysis/free_variables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from dsn.form_analysis.structure import (
VariableForm,
LambdaForm,
IfForm,
DefineForm,
ApplicationForm,
SequenceForm,
)

from dsn.form_analysis.somewhere import collect_definitions


def free_variables(form):
# return type? let's start with python-lists of symbols.
# Or rather: a set!

if isinstance(form, VariableForm):
return set([form.symbol.symbol])

if isinstance(form, LambdaForm):
a = set.union(*[free_variables(f) for f in form.body])
b = set([d.symbol.symbol for d in collect_definitions(form)])
c = set([p.symbol for p in form.parameters])
return a - b - c

# When considering a DefineForm, we might ask whether the form.symbol should be excluded from the result or not.
# Arguments could be made for both decisions; which is unfortunate.

# The confusion stems from the fact that choice of symbol influences the surrounding scope, but not the value of the
# DefineForm itself (the value resulting from evaluating Define differs from implementation to implementation,
# indicating that it's a non-central concept in the first place)

# Consider e.g. this definition: "A variable, V, is 'free in an expression', E, if the meaning of E is _changed_ by
# the uniform replacement of a variable, W, not occurring in E, for every occurrence of V in E."; note that the
# definition doesn't help us very much, because it hinges on what the _meaning_ of the expression is... in the case
# under consideration the expression will evaluate the same way when replacing the definition's symbol... but the
# effects on the surrounding scope are _not_ the same.

# Given that observation, it could be argued that the problem is simply to allow for "Define" as a separate form,
# rather than as a property of a lambda or let. I'll take this as a datapoint for now, rather than banning define.

# Rephrasing the question "is the variable free" as "is the variable bound to an enclosing scope" makes it easier:
# in that case the answer is "form.symbol should not be excluded from the result", because if the symbol occurs on
# the RHS it's bound to an enclosing scope (even though it's bound to that scope precisely by the LHS).

# Another approach could be to consider "why are you interested in free variables". The answer is "to determine how
# information from "the world" flows into the expression. Same answer, for the same reason.
# Counterpoint: when doing an analysis for unused definitions, you actually want to ignore things that are defined
# in terms of themselves (because that fact alone does not make it so that the defintion is used elsewhere).

# In any case, I'm settling on "no special treatment of Define's LHS" for now.

return general_means_of_collection(form, free_variables, lambda l: set.union(*l), set())


def general_means_of_collection(form, f, combine, identity):
# Apply thing_to_do on all this form's child (but no lower descendants) forms.

# The behavior for MalformedForm is TBD; in any case it has no child forms
# VariableForm, ValueForm & QuoteForm have no child-forms

if isinstance(form, IfForm):
return combine([f(form.predicate), f(form.consequent), f(form.alternative)])

if isinstance(form, DefineForm):
return f(form.definition)

if isinstance(form, LambdaForm):
return combine([f(child) for child in form.body])

if isinstance(form, ApplicationForm):
return combine([f(form.procedure)] + [f(child) for child in form.arguments])

if isinstance(form, SequenceForm):
return combine([f(child) for child in form.sequence])

return identity
44 changes: 44 additions & 0 deletions dsn/form_analysis/somewhere.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# `somewhere.py` means: I don't know how to organize these functions into modules yet.

# ## Define - restrictions on where it can appear:

# With respect to MIT Scheme, we restrict the locations where "define" is a legal form; in particular:
#
# * Define must be a direct child (not just any descendant) of a lambda.
# * Redefinitions are illegal.
# * Definitions must be at the start of a lambda's body. (not done yet)
# * Definitions must not rely on definitions from the same scope to be evaluated (not done yet)
#
# (As it stands, these restrictions are not yet checked; but the rest of the implementation already relies on it)
#
# I believe that the above restrictions are useful in the sense that they lead to less errors; they also happen to make
# implementation easier.
#
# In SICP, I could find the following notes that hint at the fact that the above restrictions are useful (although they
# are not part of the SICP implementation):
#
# * p. 241, footnote: "If there is already a binding for the variable in the current frame, then the binding is changed.
# [..] some people prefer redefinitions of existing symbols to signal errors" * p. 388, "4.1.6. Internal definitions"
# talks about the problems that arise when not having your definitions at the start of the lambda; especially when the
# definitions use other variables from the present scope, and when the value of the definitions is calculated before all
# variables of the scope are defined.
#
# W.R.T. non-top-level definitions I could not find an explicit discussion of the topic, however.
#
# However, it seems that I'm not the first one to think of the above restriction, e.g. from Racket (a Scheme)'s
# specification:
#
# "11.2 Definitions - Definitionsmay appear within a <top-level body>, at the top of a <library body>, or at the top of
# a <body>"
#
# A similar question (as of yet unresolved) is: will we allow define to redefine parameters from the same scope (i.e.
# name-collissions between paramters and defines in the same scope). In Racket, this is legal:
# ((lambda (foo) (define foo 1) foo) 2)


from dsn.form_analysis.structure import DefineForm


def collect_definitions(lambda_form):
# TODO: determine type (list of forms, FormList, something else?)
return [f for f in lambda_form.body if isinstance(f, DefineForm)]
3 changes: 3 additions & 0 deletions dsn/form_analysis/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,6 @@ def __init__(self, the_list, metadata=None):
pmts(the_list, list)
self.the_list = the_list
self.metadata = metadata

def __iter__(self):
return self.the_list.__iter__()
1 change: 1 addition & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocFileSuite("doctests/form_analysis_into.txt"))
tests.addTests(doctest.DocFileSuite("doctests/form_analysis_construct.txt"))
tests.addTests(doctest.DocFileSuite("doctests/evaluator.txt"))
tests.addTests(doctest.DocFileSuite("doctests/free_variables.txt"))

return tests

Expand Down

0 comments on commit 863a61d

Please sign in to comment.