[Types-sig] recursive types, type safety, and flow analysis (was: Recursive types)

Greg Stein gstein@lyra.org
Tue, 21 Dec 1999 13:07:27 -0800 (PST)


On Tue, 21 Dec 1999, Guido van Rossum wrote:
> > From: Greg Stein <gstein@lyra.org>
> > 
> > On Tue, 21 Dec 1999, skaller wrote:
> > >...
> > > 	If I may: there is an issue here which
> > > some people may not have realised: recursive types.
> > 
> > These are not possible in Python because definitions are actually
> > constructed at runtime. The particular name/object must be available at
> > that point in the execution.
> 
> Huh?  "Recursive types" typically refers to all sorts of nodes, graphs
> and trees (where an instance attribute has the same type as its
> container).  Certainly these are possible in Python!

True... I use the stuff, too...

I should have clarified that I don't think his particular example would
work because of the compile- / definition- time recursion of the names.
Runtime? Sure. It's fine.

> > > In an interface file, this can be handled by 
> > > two passes.
> > > 
> > > 	In implementation files, it is much
> > > harder, since scoping rules are dynamic.
> > > This is a good argument for interface files.
> > > Example:
> > > 
> > > 	class X:
> > > 		def f(y:Y): ...
> > 
> > This fails. Y is not defined.
> 
> If I understand the context correctly (X defined before Y but using Y)
> I disagree.  Since this works fine without type declarations (as long
> as the instantiations happen after the classes are defined) I don't
> see why adding static typing should break this.

Because Y is not defined. This is analogous to the following code:

class Foo:
  def build_it(self, x, y, cls=Bar):
    return cls(x, y)

class Bar:
  ...

The above code breaks. I am positing that if you put "Y" into a
declarator, then it should exist at that point in time. Where "time" is
specified as following the flow of execution as the functions/classes are
defined.

> Also, I think that static typing should have a much more liberal view
> about forward referencing than Python itself.  Since it is quite legal
> to have
> 
>   def f(): g()
>   def g(): ...
>   print f()
> 
> I think that typecheckers should deal with this, and not complain
> about the forward reference to g in f!  (Except when f is called
> before g is defined.  Flow analysis should allow this distinction.)

Good point.

I don't think we can detect that call-before-definition, though.

I think your point can be restated as: Can we type-check the following
code?

def f() -> String:
  return g()

def g() -> String:
  ...


I haven't thought about this particular scenario or the resulting impact
on the inferencer. We probably require some kind of a two-pass analysis as
John points out. Maybe it is as simple as deferring analysis of function
bodies until the global "body" is analyzed. Actually, I think the deferral
mechanism is sufficient, as that mirrors the execution environment: at the
time the function body is executed, the globals are defined.
[ with the caveat of call-before-define, but we can't determine that ]

Hrm. The whole call-before-define problem might actually axe Paul's desire
for type safety. I don't think we can *ever* guarantee that a name will
exist at the time it is used. For example:

value = 1
def typesafe f():
  somefunc(value)

del value
f()

If you start doing *control* flow analysis, then you might be able to
definitely state the above code is in error. But then, I'll just throw
this wrench at it:

if sometest():
  del value
f()

Now what?

The type inferencing that I believe we can/should be using is based on
some basic data flow, without regard to definitively determining whether a
particular branch is reached or not. If a possibility exists, then the
possible types are unioned in. If type-safety is defined as "no
NameErrors", then we have a problem, as control flow is required.

Note that I believe the following can be handled:

value = 1
def typesafe f()
  func_taking_int(value)

value = "foo"
f()

In this case, the global variable "value" has a typedecl of: Int or
String. This would fail the func_taking_int() function call.

Back to the other point: I do believe that you should not use a name in a
type-declarator if it isn't defined.

Cheers,
-g

-- 
Greg Stein, http://www.lyra.org/