Module: DFMC-Typist Author: Steve Rowley Synopsis: Algebra of the types in the typist. Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// /// Implementation of the typist algebra. (NB: No macro-defining macros!) /// /// /// Normalization of s. /// /// Any which contains other s (i.e., "type /// constructors") is subject to normalization. The theory of normal form here /// is disjunctive normal form, i.e., pull all the unions as far outside as /// they'll come. Sort of like prenex/prolix form in logic. /// /// See Agesen's CPA paper: Agesen, O., "The Cartesian Product Algorithm: Simple /// and Precise Type Inference of Parametric Polymorphism," ECOOP-95, /// http://self.smli.com/papers/cpa.html. /// /* // *** This is debugging code, normally dead. define constant $normalize-freqs$ = make(); // *** This is debugging code, normally dead. define function bump-normalization-freq(cl :: ) => () $normalize-freqs$[cl] := element($normalize-freqs$, cl, default: 0) + 1; values() end; // *** This is debugging code, normally dead. define function show-normalize-stats () // See what crawls out when we lift the rocks. format-out("\nType Normalization Statistics.\n==============================\n"); let total = 0; let alist = #(); map-table($normalize-freqs$, method (cl :: , freq :: ) // Accumulate total counts, and freq/cl pairs for sorting. alist := pair(pair(freq, cl), alist); total := total + freq end); // Sort in order of decreasing frequency for presentation to programmer. alist := sort!(alist, test: method (x, y) first(x) > first(y) end); for (cell in alist) let freq = first(cell); let cl = rest(cell); format-out("\n%s\t%d", cl, freq) end; format-out("\n\nTotal calls: %d", total); values() end; */ // A "megamorphic" type is one that is (unreasonably) extremely polymorphic, // e.g., a union of inconveniently large size. These actually occur in // practice: the Win32 library has a create-font function of 14 arguments, // each of which is a union (, ); that would lead // to a CPA expansion of 2^14 elements! // Somewhat arbitrary, but bigger arbitrary numbers screw us compiling the // FFI. define constant $megamorphic-punt-threshold$ = 32; // *** This probably conses too much! define function type-estimate-CP-expand(megamorphic-punt-constr :: , constr :: , #rest products) => (CP-expansion :: ) // Cartesian Product expansion. Products is a list of args to be // CP-expanded. Constr gets called on the final unionees to build the // type you want to be the element of the final union. local method do-CP (fn :: , sets :: ) => () // Do fn over the Cartesian product of sets. Sets is a of // s; each represents a set (ordered, of course). // It's a so we can tail() it in the recursive case. case empty?(sets) => ; // 0 sets: do nothing empty?(tail(sets)) => do(fn, head(sets)); // 1 set: do elements otherwise => do(method (x) // 2 or more sets: recurse do-CP(curry(fn, x), tail(sets)) end, head(sets)); end; values() end, method as-union-list (x) => (union-list :: ) // Convert unions to lists of unionees, others to list of self. select (x by instance?) => type-estimate-unionees(x); otherwise => list(x); end end, method final-union-size() => (union-size :: ) // How big is this gonna be, Batman? reduce(method (so-far, product) so-far * if (instance?(product, )) size(type-estimate-unionees(product)) else 1 end end, 1, products) end; if (final-union-size() > $megamorphic-punt-threshold$) // Unreasonably large union would be made from this extremely polymorphic // type; punt to something else instead. Caller defines "something else." megamorphic-punt-constr() else // Do regular CPA expansion. let CP-unionees :: = make(); do-CP(method (#rest args) // Call the constructor args. Result is an element of CP union. push-last(CP-unionees, apply(constr, args)) end, map-as(, as-union-list, products)); // Will be automatically normalized in case the union is degenerate. make(, unionees: CP-unionees) end end; define method type-estimate-normalize(bot :: ) => (te :: ) // Bottom normalization is trivial. // bump-normalization-freq(object-class(bot)); type-estimate-to-be-normalized?(bot) := #f; bot end; define method type-estimate-normalize(top :: ) => (te :: ) // Top normalization. // bump-normalization-freq(object-class(top)); type-estimate-to-be-normalized?(top) := #f; top end; define method type-estimate-normalize(lim :: ) => (te :: ) // Some limited normalization is trivial. // Limited integers, limited classes, limited collections & limited functions // override this, below. Limited instances inherit it. // bump-normalization-freq(object-class(lim)); type-estimate-to-be-normalized?(lim) := #f; lim end; define method type-estimate-normalize(li :: ) => (te :: ) // Override method for limited integers. // bump-normalization-freq(object-class(li)); if (~type-estimate-to-be-normalized?(li)) // Already been normalized li elseif (type-estimate-min(li) = type-estimate-max(li)) // "Severely limited" integers are of size one -- normalize to singletons. make(, singleton: type-estimate-min(li)) else // Just note that it's normalized & return it. type-estimate-to-be-normalized?(li) := #f; li end end; define method type-estimate-normalize (lc :: ) => (te :: ) // Override method for limited classes. // bump-normalization-freq(object-class(lc)); let sub-cl = type-estimate-subclass(lc); if (~type-estimate-to-be-normalized?(lc)) // Already been normalized lc elseif (^sealed-with-no-subclasses?(sub-cl)) // If a class is sealed & has no subclasses in this library, it will never // have any. Such "severely limited" classes become singletons. make(, singleton: sub-cl) else // Just note that it's normalized and go on. type-estimate-to-be-normalized?(lc) := #f; lc end end; define method type-estimate-normalize(lc :: ) => (te :: ) // Override method on for unions in the of: type. // bump-normalization-freq(object-class(lc)); if (~type-estimate-to-be-normalized?(lc)) // Already been normalized lc elseif (type-estimate-class(lc) == dylan-value(#"") & type-estimate-size(lc) == 0) // "Severely limited" collection that canonicalizes to #() only. make(, singleton: #()) else let of = type-estimate-of(lc); // *** Should already be normalized? let n-of = when (of) type-estimate-normalize(of) end; select (n-of by instance?) => type-estimate-CP-expand( // *** Actually should punt to collection which promotes n-of // to something like or , or tighter. // OTOH, this can only be megamorphic if lc is already // megamorphic. So punting should never happen here? curry(type-estimate-base, lc), curry(make, , normalize?:, #f, class:, type-estimate-class(lc), concrete-class:, type-estimate-concrete-class(lc), size:, type-estimate-size(lc), dimensions:, type-estimate-dimensions(lc), of:), n-of); singleton(of) => type-estimate-to-be-normalized?(lc) := #f; lc; otherwise => make(, normalize?: #f, class: type-estimate-class(lc), concrete-class: type-estimate-concrete-class(lc), of: n-of, size: type-estimate-size(lc), dimensions: type-estimate-dimensions(lc)); end end end; // This function can returns a class type estimate rather than consing // a new limited function estimate when nothing worthwhile is known, // hence the result type. define method type-estimate-normalize(fn :: ) => (te :: ) // CP-expand unions in the args & results. // bump-normalization-freq(object-class(fn)); if (~type-estimate-to-be-normalized?(fn)) // Already been normalized fn else // Have to do some work to normalize it. let requireds = type-estimate-requireds(fn); let keys = type-estimate-keys(fn); let vals = type-estimate-values(fn); // Normalized pieces // *** Should already be normalized? let n-requireds :: = map(type-estimate-normalize, requireds); let n-keys = when (keys) map(type-estimate-normalize, keys) end; let n-vals = type-estimate-normalize(vals); // Check for presence of unions in normalized pieces. let unions? = any?(rcurry(instance?, ), n-requireds) | (n-keys & any?(rcurry(instance?, ), n-keys)) | instance?(n-vals, ); // Check if we want to promote to class , with no extra info. if (empty?(n-requireds) & type-estimate-rest?(fn) == #t & n-keys == #f & type-estimate-all-keys?(fn) == #f & type-estimate-match?(n-vals, make())) // Just promote to class , since we really know nothing else. make(, class: dylan-value(#"")) elseif (every?(\==, requireds, n-requireds) & table=?(keys, n-keys, \==) & vals == n-vals & ~unions?) // No change & no unions. type-estimate-to-be-normalized?(fn) := #f; fn elseif (~unions?) // Change, but no unions make(, normalize?: #f, class: type-estimate-class(fn), requireds: n-requireds, rest?: type-estimate-rest?(fn), keys: n-keys, all-keys?: type-estimate-all-keys?(fn), vals: n-vals) else // CPA expand unions here let num-req :: = size(n-requireds); let num-keys :: = if (n-keys) size(n-keys) else 0 end; let the-keys :: = make(, size: num-keys); let the-vals :: = make(, size: num-keys); when (keys) for (a-val keyed-by a-key in n-keys, i :: from 0) the-keys[i] := a-key; the-vals[i] := a-val; end for; end when; let arg-seq :: = concatenate(n-requireds, // [0, num-req - 1] case // [num-req, num-req + num-keys - 1] n-keys => as(, the-vals); otherwise => as(, #[]); end, as(, vector(n-vals))); // [num-req + num-keys] // format-out("\n*** Normalizing: %s", fn); // format-out("\n Num-req = %d, num-keys = %d", num-req, num-keys); // format-out("\n Arg-seq of size %d = %s", size(arg-seq), arg-seq); let result = apply(type-estimate-CP-expand, // *** Should really construct limited function which promotes // unions to something like , or maybe . curry(type-estimate-base, fn), method (#rest args) let cpa-reqds = copy-sequence(args, end: num-req); let cpa-kvals = copy-sequence(args, start: num-req, end: num-req + num-keys); let cpa-vals = args[num-req + num-keys]; // format-out("\n*** Args: %s", args); // format-out("\n requireds: %s", cpa-reqds); // format-out("\n keys: %s", cpa-kvals); // format-out("\n vals: %s", cpa-vals); make(, normalize?: #f, class: type-estimate-class(fn), requireds: cpa-reqds, rest?: type-estimate-rest?(fn), keys: when (n-keys) let new-tbl ::
= make(
, size: num-keys); for (key in the-keys, new-val in cpa-kvals) new-tbl[key] := new-val end for; new-tbl end, all-keys?: type-estimate-all-keys?(fn), vals: cpa-vals) end, arg-seq); // format-out("\n*** Result: %s", result); result end end end; define method type-estimate-normalize(cl :: ) => (te :: ) // Class type estimate normalization. // bump-normalization-freq(object-class(cl)); if (~type-estimate-to-be-normalized?(cl)) // Already been normalized cl elseif (type-estimate-class(cl) == dylan-value(#"")) // Normalize to singleton(#()), since sealed & 1 instance. make(, singleton: #()) else // Otherwise normalization is trivial. type-estimate-to-be-normalized?(cl) := #f; cl end end; define method type-estimate-normalize(raw :: ) => (te :: ) // Raw type estimates also normalize trivially. // bump-normalization-freq(object-class(raw)); type-estimate-to-be-normalized?(raw) := #f; raw end; /// *** Deal with exhaustive partitions, especially in unions. E.g., /// - (limited integers <=0) union (limited integers >=0) = . /// - class unions, like all the subclasses of a sealed class: /// union = . /// - some singletons: singleton(#()) = ? (Or vice versa?) define method type-estimate-normalize(un :: ) => (te :: ) // Pull up embedded subunions, collapse subtypes. O(n^2) in union-size(un). // bump-normalization-freq(object-class(un)); if (~type-estimate-to-be-normalized?(un)) // Already been normalized un else // Gotta work to normalize it. let unionees :: = type-estimate-unionees(un); let new-unionees :: = make(); local method canonicalize-unionee (unionee) // Embedded unions, supertypes, subtypes. See DRM pp. 71-72. // *** Should already be normalized??? let norm-unionee = type-estimate-normalize(unionee); case instance?(norm-unionee, ) => // Pull up embedded subunion. do(canonicalize-unionee, type-estimate-unionees(norm-unionee)); any?(curry(type-estimate-subtype?, norm-unionee), new-unionees) => // norm-unionee is a subtype of a supertype already in. ; otherwise => // Add to the union, removing subtypes already in. remove!(new-unionees, norm-unionee, test: type-estimate-subtype?); push-last(new-unionees, norm-unionee); end end; // Normalize each unionee, and decide how to add result to canonicals. do(canonicalize-unionee, unionees); let new-size = size(new-unionees); case // Union of 0 things, 1 thing, no changes, or changes. new-size = 0 => make(); new-size = 1 => new-unionees[0]; new-size == size(unionees) & // Absence of this test was a subtle bug! every?(\==, new-unionees, unionees) => type-estimate-to-be-normalized?(un) := #f; un; otherwise => make(, normalize?: #f, unionees: new-unionees); end end end; define method type-estimate-normalize(val :: ) => (te :: ) // CP-expand unions in the values. // bump-normalization-freq(object-class(val)); let fix = type-estimate-fixed-values(val); let rest = type-estimate-rest-values(val); if (~type-estimate-to-be-normalized?(val)) // Already been normalized val elseif (any?(rcurry(instance?, ), fix) | instance?(rest, )) // contains a bottom, so it's all bottoms make(, rest: make(), normalize?: #f) else // Gotta work to normalize it // Normalize embedded s. // *** Should already be normalized? let n-fix = map(type-estimate-normalize, fix); let n-rest = if (rest) type-estimate-normalize(rest) else #f end; // Check for presence of unions in normalized pieces. let unions? = any?(rcurry(instance?, ), n-fix) | instance?(n-rest, ); if (every?(\==, n-fix, fix) & n-rest == rest & ~unions?) type-estimate-to-be-normalized?(val) := #f; val // Neither change nor unions elseif (~unions?) // Change, but no unions make(, normalize?: #f, fixed: n-fix, rest: n-rest) else // CPA expand the unions here apply(type-estimate-CP-expand, method () // Punt position: new multiple values, but less uniony since we // coerce value types to be something nicer. make(, fixed: map(method (x) select (x by instance?) // *** Should do better than this. Include // types, anyway. => make(, class: dylan-value(#"")); otherwise => x; end end, n-fix), rest: select (n-rest by instance?) singleton(#f) => #f; // *** Should do better than this. Should also // deal with raws, e.g., use ? => make(, class: dylan-value( #"")); otherwise => n-rest; end) end, method (#rest rgs) // Construct the values types in the CP-union make(, normalize?: #f, fixed: copy-sequence(rgs, start: 1), rest: first(rgs)) end, n-rest, n-fix) end end end; /// /// Unions of s. /// /* // *** This is debugging code, normally dead. define variable *type-estimate-union-args* = #(); // *** This is debugging code, normally dead. define function show-union-stats (#key data = *type-estimate-union-args*) => () // Summarize the contents of *type-estimate-union-args*. format-out("\n\n# of type-estimate-union calls = %d.", size(data)); let left-subtype = 0; // # times left arg is subtype of right let right-subtype = 0; // # times right arg is subtype of left let no-subtype = 0; // # times no subtype relationship obtains let arg-types = make(); // Set of arg types which went into unions // Loop over data, accumulating summary info. for (args in data) let arg1 = head(args); let arg2 = tail(args); add!(arg-types, object-class(arg1)); add!(arg-types, object-class(arg2)); // *** Arrgh. So we can call with-testing-context, so we can do algebra. let do-with-testing-context = access(dfmc-testing, do-with-testing-context); with-testing-context (#f) case type-estimate-subtype?(arg1, arg2) => left-subtype := left-subtype + 1; type-estimate-subtype?(arg2, arg1) => right-subtype := right-subtype + 1; otherwise => no-subtype := no-subtype + 1; end end end; // Print out subtypeness freqs format-out("\n\nWhen type-estimate-union args are subtypes of each other:"); format-out("\nLeft subtype of right: %d\nRight subtype of left: %d\nNo subtype either way: %d", left-subtype, right-subtype, no-subtype); // Ok, now we know the arg-types. Accumulate 2-d table of arg frequencies. let arg-type-indices = make(
); // Map from arg types to array indices. for (arg-type in arg-types, j from 0) // Arbitrarily in iteration order. arg-type-indices[arg-type] := j end; let num-arg-types = size(arg-types); let arg-freq-table = make(, dimensions: list(num-arg-types, num-arg-types), fill: 0); let col-totals = make(, size: num-arg-types, fill: 0); for (args in data) let arg1-index = arg-type-indices[object-class(head(args))]; let arg2-index = arg-type-indices[object-class(tail(args))]; arg-freq-table[arg1-index, arg2-index] := arg-freq-table[arg1-index, arg2-index] + 1 end; // Print out arg type frequency table. format-out("\n\nFrequencies of arguments to type-estimate-union:"); // Column labels. format-out("\n"); for (col-arg-type in arg-types) format-out("\t%s", col-arg-type) end; format-out("\tRow Totals"); // Rows. for (row-arg-type in arg-types) let row-index = arg-type-indices[row-arg-type]; let row-total = 0; format-out("\n%s", row-arg-type); for (col-arg-type in arg-types) let col-index = arg-type-indices[col-arg-type]; let count = arg-freq-table[row-index, col-index]; format-out("\t%d", count); row-total := row-total + count; col-totals[col-index] := col-totals[col-index] + count; end; format-out("\t%d", row-total) end; // Last row is for column totals. format-out("\nCol Totals"); for (col-arg-type in arg-types) let col-index = arg-type-indices[col-arg-type]; format-out("\t%d", col-totals[col-index]) end; // return nothing values() end; */ define generic type-estimate-union-internal(te1 :: , te2 :: ) => (te :: ); define method type-estimate-union (te1 :: , te2 :: ) => (te :: ) // Record some metering data & trampoline to right place. // *type-estimate-union-args* := pair(pair(te1, te2), *type-estimate-union-args*); case // Metering shows an _awful_ lot of cases have te1 <= te2. Short-circuit // that early on, rather than consing the type-union and then normalizing it // back down! // *** 15-Jan-97 metering of Dylan library compilation: // Left subtype of right: 69454 / 74145 // Right subtype of left: 4585 / 74145 // No subtype either way: 106 / 74145 // Left arg is bottom: 67217 / 74145 instance?(te1, ) => te2; type-estimate-subtype?(te1, te2) => te2; type-estimate-subtype?(te2, te1) => te1; otherwise => type-estimate-union-internal(te1, te2); end end; define macro type-estimate-union-rules-definer // Expand a bunch of rules into methods for type-estimate-union. { define type-estimate-union-rules ?rules end } => { ?rules } rules: // Body is ;-separated rules generating ;-separated methods. { } => { } { ?rule; ... } => { ?rule; ... } rule: // Each rule generates a type-estimate-union method. { ?tname1:name :: ?typ1:name, ?tname2:name :: ?typ2:name <- ?expr:expression } => { define method type-estimate-union-internal(?tname1 :: ?typ1, ?tname2 :: ?typ2) => (te :: ) ?expr end } end; // *** Doesn't exploit fact that te1 & te2 now known to be incomparable. define type-estimate-union-rules // Just make unions and rely on normalization to do the rest. te1 :: , te2 :: <- // Default method for non-unions: make a union & normalize. make(, unionees: list(te1, te2)); te :: , u :: <- // If one arg is a union, add the other to it & normalize. make(, unionees: add(type-estimate-unionees(u), te)); u :: , te :: <- // If othe arg is a union, add the one to it & normalize. make(, unionees: add(type-estimate-unionees(u), te)); u1 :: , u2 :: <- // If both args are unions, concatenate and normalize. make(, unionees: concatenate(type-estimate-unionees(u1), type-estimate-unionees(u2))); end; /// /// Intersections of s. /// define macro type-estimate-intersection-rules-definer // Expand a bunch of rules into methods for type-estimate-intersection. { define type-estimate-intersection-rules ?rules end } => { ?rules } rules: // Body is ;-separated rules generating ;-separated methods. { } => { } { ?rule; ... } => { ?rule; ... } rule: // Each rule generates a type-estimate-intersection method. { ?tname1:name :: ?typ1:name, ?tname2:name :: ?typ2:name <- ?expr:expression } => { define method type-estimate-intersection(?tname1 :: ?typ1, ?tname2 :: ?typ2) => (te :: ) ?expr end } end; define type-estimate-intersection-rules te1 :: , te2 :: <- error("*** type-estimate-intersection not implemented yet.") end; /// /// Differences of s. /// define macro type-estimate-difference-rules-definer // Expand a bunch of rules into methods for type-estimate-difference. { define type-estimate-difference-rules ?rules end } => { ?rules } rules: // Body is ;-separated rules generating ;-separated methods. { } => { } { ?rule; ... } => { ?rule; ... } rule: // Each rule generates a type-estimate-difference method. { ?tname1:name :: ?typ1:name, ?tname2:name :: ?typ2:name <- ?expr:expression } => { define method type-estimate-difference(?tname1 :: ?typ1, ?tname2 :: ?typ2) => (te :: ) ?expr end } end; define type-estimate-difference-rules te1 :: , te2 :: <- error("*** type-estimate-difference not implemented yet.") end; /// /// Base of s. See DRM, p. 48. /// define method type-estimate-base (top :: ) => (te :: ) top end; define method type-estimate-base (cl :: ) => (te :: ) // DRM, p. 48: class types are their own base. cl end; define method type-estimate-base (raw :: ) => (te :: ) // Raw types are their own base (analogy to classes). raw end; define method type-estimate-base (lim :: ) => (te :: ) // DRM, p. 48: generally, the base of a limited type is the class being // limited. However, this is overridden below for singletons. make(, class: type-estimate-class(lim)) end; define method type-estimate-base (lim :: ) => (te :: ) make(, class: type-estimate-concrete-class(lim) | type-estimate-class(lim)) end; define method type-estimate-base (li :: ) => (te :: ) // DRM, p. 48: Singleton: override the method on general limited types. The // base of a singleton is the singleton itself. li end; define method type-estimate-base (un :: ) => (te :: ) // DRM, p. 48: The base of a union is the union of the bases of the unionees. let unionees = type-estimate-unionees(un); let base-unionees = map(type-estimate-base, unionees); if (every?(\==, base-unionees, unionees) & ~empty?(unionees)) // Note that the nonempty check is an extension, for empty unions. // They'll normalize to bottom, below. un else type-estimate-normalize(make(, unionees: base-unionees)) end end; define method type-estimate-base (mv :: ) => (te :: ) // The base type of a multiple-values type is defined to be a multiple-values // of the base types of its components. This is kind of a guess, since // multiple-values are obviously not a Dylan user type! let fixed = type-estimate-fixed-values(mv); let rest = type-estimate-rest-values(mv); let base-fixed = map(type-estimate-base, fixed); let base-rest = if (rest) type-estimate-base(rest) else #f end; if (every?(\==, fixed, base-fixed) & rest == base-rest) // No change mv else // Some change type-estimate-normalize( make(, fixed: base-fixed, rest: base-rest)) end end; define method type-estimate-base (bot :: ) => (te :: ) // Base type of bottom is bottom. // This is kind of a guess, but the only reasonable one. bot end; /// /// Structural matching, mostly useful in the test suite. /// define macro type-estimate-match?-rules-definer // Expand rules into method definitions. { define type-estimate-match?-rules ?rules end } => { ?rules } rules: // Body is ;-separated rules generating ;-separated methods. { } => { } { ?rule; ... } => { ?rule; ... } rule: // Each rule generates a type-estimate-match? method. { ?te1:name, ?te2:name, ?typ:* <- ?expr:expression } => { define method type-estimate-match? (?te1 :: ?typ, ?te2 :: ?typ) => (match? :: ) // Insist that the classes be exactly the same, to prevent // spurious matches by inheritance. object-class(?te1) == object-class(?te2) & ?expr end } typ: { ?:expression } => { ?expression } end; define type-estimate-match?-rules // Could be automagically derived if type classes were macro-defined. o1, o2, <- #f; // Failure case // *** Need to be systematic about mapped types here! b1, b2, <- b1 == b2; // Convenience i1, i2, <- i1 == i2; // Convenience c1, c2, <- c1 == c2; // Convenience s1, s2, <- s1 == s2; // Convenience o1, o2, <&object> <- o1 == o2; // *** do we need something more detailed? //l1, l2, <- every?(type-estimate-match?, l1, l2); // Convenience // Native runtime has s on the loose. l1, l2, <- every?(type-estimate-match?, l1, l2); // Convenience t1, t2,
<- table=?(t1, t2, type-estimate-match?); // Convenience te1, te2, <- #t; te1, te2, <- type-estimate-class(te1) == type-estimate-class(te2); te1, te2, <- type-estimate-raw(te1) == type-estimate-raw(te2); te1, te2, <- type-estimate-match?(type-estimate-fixed-values(te1), type-estimate-fixed-values(te2)) & type-estimate-match?(type-estimate-rest-values(te1), type-estimate-rest-values(te2)); te1, te2, <- type-estimate-class(te1) == type-estimate-class(te2) & type-estimate-match?(type-estimate-requireds(te1), type-estimate-requireds(te2)) & type-estimate-rest?(te1) == type-estimate-rest?(te2) & type-estimate-match?(type-estimate-keys(te1), type-estimate-keys(te2)) & type-estimate-all-keys?(te1) == type-estimate-all-keys?(te2) & type-estimate-match?(type-estimate-values(te1), type-estimate-values(te2)); te1, te2, <- type-estimate-class(te1) == type-estimate-class(te2) & type-estimate-min(te1) = type-estimate-min(te2) & type-estimate-max(te1) = type-estimate-max(te2); te1, te2, <- type-estimate-subclass(te1) == type-estimate-subclass(te2); te1, te2, <- type-estimate-match?(type-estimate-singleton(te1), type-estimate-singleton(te2)); te1, te2, <- type-estimate-class(te1) == type-estimate-class(te2) & type-estimate-match?(type-estimate-of(te1), type-estimate-of(te2)) & type-estimate-size(te1) = type-estimate-size(te2) & type-estimate-dimensions(te1) = type-estimate-dimensions(te2); te1, te2, <- type-estimate-match?(type-estimate-unionees(te1), type-estimate-unionees(te2)); te1, te2, <- #t; end; /// /// Instance? of s. /// define macro type-estimate-instance?-rules-definer // Expand a bunch of rules into methods for type-estimate-instance?. { define type-estimate-instance?-rules ?rules end } => { ?rules } rules: // Body is ;-separated rules generating ;-separated methods. { } => { } { ?rule; ... } => { ?rule; ... } rule: // Each rule generates a type-estimate-instance? method. { ?oname:name :: ?otyp:name, ?tname:name :: ?ttyp:name <- ?expr:expression } => { define method type-estimate-instance?(?oname :: ?otyp, ?tname :: ?ttyp) => (instance? :: , known? :: ) ?expr end } end; // *** Deal with second value. // *** Is use of kosher here, or should I be looking at ^mappings? define type-estimate-instance?-rules x :: , te :: <- values(#t, #t); x :: , te :: <- values(#f, #t); x :: , te :: <- // DRM, p. 72. any?(curry(type-estimate-instance?, x), type-estimate-unionees(te)); x :: , te :: <- // Have to recognize the mv-vectors. error("*** type-estimate-instance?(_, values) doesn't make sense."); x :: , te :: <- values(^instance?(x, type-estimate-class(te)), #t); x :: , te :: <- values(^instance?(x, type-estimate-raw(te)), #t); x :: , te :: <- values(^instance?(x, type-estimate-class(te)) & // Potential bug with singleton(fn) not a subtype of // limited(, ...)? See comment above // type-estimate-subtype? of limited function. type-estimate-subtype?(type-estimate(x), // Relatively crude hammer te), #t); x :: , te :: <- // See DRM, p. 125. ^instance?(x, dylan-value(#"")) & ^instance?(x, type-estimate-class(te)) & ^instance?(^element-type(x), as(<&type>, type-estimate-of(te))); // ^instance?(x, type-estimate-class(te)) // & case // type-estimate-size(te) // Size: keyword given // => value(&size(x)) == type-estimate-size(te) // // *** x not a . // ; // type-estimate-dimensions(te) // Dimensions: keyword given // => // *** dim(x) = type-estimate-dimensions(te) // // *** x not a . // ; // otherwise => #t; // Neither: no restriction here. // end //& type-estimate-subtype?(***element-type, type-estimate-base(te)); // error("*** type-estimate-instance?(_, limited-colln) not yet implemented."); x :: , te :: <- values(^id?(x, type-estimate-singleton(te)), #t); x :: , te :: <- values(^instance?(x, type-estimate-class(te)) // Some kind of class & ^subtype?(x, type-estimate-subclass(te)), // which is a subclass #t); x :: , te :: <- values(^instance?(x, type-estimate-class(te)) // Appropriate integer & (~type-estimate-min(te) | type-estimate-min(te) <= x) & (~type-estimate-max(te) | x <= type-estimate-max(te)), #t); end; /// /// Disjointness of s. /// define generic type-estimate-disjoint?-1 (t1 :: , t2 :: ) => (disjoint? :: ); define method type-estimate-disjoint?(t1 :: , t2 :: ) => (disjoint? :: , unused? :: ) // Only method: 2 types are disjoint if their intersection as sets is empty. if (instance?(t1, ) | instance?(t2, )) // one type is bottom, i.e., empty set, so intersection is always empty. values(#t, #t) elseif (type-estimate-subtype?(t1, t2) | type-estimate-subtype?(t2, t1)) // If one is a subtype of the other & either nonempty, then they // can't possibly be disjoint. values(#f, #t) else // No subset relation, but could still be some overlap. See DRM p. 49. values(type-estimate-disjoint?-internal(t1, t2), #f) end end; define inline method type-estimate-unionees(te :: ) => (singleton-set :: ) list(te); end method; define function type-estimate-disjoint?-internal (t1 :: , t2 :: ) => (disjoint? :: ) if (instance?(t2, )) // handle this so individual rules can assume t2 is not a union. every?(curry(type-estimate-disjoint?, t1), type-estimate-unionees(t2)) elseif (type-estimate-disjoint?-special-case?(t1)) type-estimate-disjoint?-1(t1, t2) elseif (type-estimate-disjoint?-special-case?(t2)) type-estimate-disjoint?-1(t2, t1) else // Last-ditch desperation rule that "just says no." #f end; end function; define method type-estimate-disjoint?-special-case? (t :: ) => (res :: singleton(#f)) #f end method; define macro type-estimate-disjoint?-internal-rule-definer // Expand a bunch of rules into methods for type-estimate-disjoint?-internal. { define type-estimate-disjoint?-internal-rule (?tname1:name :: ?typ1:name) end } => { define method type-estimate-disjoint?-special-case? (type :: ?typ1) => (res :: singleton(#t)) #t end method; define method type-estimate-disjoint?-1 (type1 :: ?typ1, type2 :: ) => (disjoint? :: ) "type-estimate-disjoint?-" ## ?typ1 (type1, type2) end method } { define type-estimate-disjoint?-internal-rule (?tname1:name :: ?typ1:name) ?tname2:name :: ?typ2:name <- ?expr:expression ; ?more:* end } => { define method "type-estimate-disjoint?-" ## ?typ1 (?tname1 :: ?typ1, ?tname2 :: ?typ2) ?expr end; define type-estimate-disjoint?-internal-rule (?tname1 :: ?typ1) ?more end } end macro; define type-estimate-disjoint?-internal-rule (t1 :: ) // Raw -- disjoint from everything but themselves. (But since we know // by the time we get here that they're not subtypes, always are disjoint!) t2 :: <- #t; t2 :: <- ~^id?(type-estimate-raw(t1), type-estimate-raw(t2)); end; define type-estimate-disjoint?-internal-rule (t1 :: ) // Multiple values -- disjoint from everything but themselves. t2 :: <- #t; t2 :: <- values-guaranteed-disjoint?(t1, t2); end; define type-estimate-disjoint?-internal-rule (t1 :: ) // Classes. t2 :: <- #t; t2 :: <- ^classes-guaranteed-disjoint?(type-estimate-class(t1), type-estimate-class(t2)); end; define type-estimate-disjoint?-internal-rule (t1 :: ) // Unions -- disjoint if components are disjoint. // Degenerate case is always #t since every? is #t over the empty set. t2 :: <- every?(rcurry(type-estimate-disjoint?, t2), type-estimate-unionees(t1)); end; define type-estimate-disjoint?-internal-rule (t1 :: ) // Bottom is always disjoint from everything, including itself. t2 :: <- #t; end; define type-estimate-disjoint?-internal-rule (t1 :: ) // Generic limited types -- (lim, lim) methods on each concrete limited. t2 :: // Disjoint if the base type is disjoint. <- type-estimate-disjoint?(type-estimate-base(t1), t2); end; define type-estimate-disjoint?-internal-rule (t1 :: ) // Limited integers t2 :: <- type-estimate-disjoint?(type-estimate-base(t1), t2); t2 :: // Compare minima and maxima <- limited-integers-guaranteed-disjoint?(t1, t2); end; // *** Limited functions. // *** Limited collections (see 3 rules in DRM p. 49). define type-estimate-disjoint?-internal-rule (t1 :: ) // Limited classes (subclasses) t2 :: <- type-estimate-disjoint?(type-estimate-base(t1), t2); t2 :: // Disjoint if no common subclasses (other cases on generic limited, above) <- ^classes-guaranteed-disjoint?(type-estimate-subclass(t1), type-estimate-subclass(t2)); end; define type-estimate-disjoint?-internal-rule (t1 :: ) // Limited classes (subclasses) t2 :: <- type-estimate-disjoint?(type-estimate-base(t1), t2); t2 :: // TODO: extend to handle size and dimensions <- ^classes-guaranteed-disjoint?(type-estimate-class(t1), type-estimate-class(t2)) | type-estimate-disjoint?(type-estimate-of(t1), type-estimate-of(t2)); end; define type-estimate-disjoint?-internal-rule (t1 :: ) // Limited instances (singletons): look at the object. (But since we know // they're not subtypes by the time we get here, they're always disjoint!) t2 :: <- ~type-estimate-instance?(type-estimate-singleton(t1), t2); end; // *** Think about whether this is the right disjointness relation! define function values-guaranteed-disjoint? (t1 :: , t2 :: ) => (disjoint? :: ) // Whether these multiple value types are guaranteed disjoint: // t1 disj t2 iff NO instance of one can be an instance of the other, ie., // ~ exists x: x :: t1 & x :: t2. local method arity (val :: ) => (fixed :: , rest? :: ) // Fixed arity (exactly n values): (n, #f) // Infinite arity (n or more values): (n, #t) values(size(type-estimate-fixed-values(val)), type-estimate-rest-values(val) ~== #f) end, method vref (val :: , i :: ) => (value :: , rest? :: ) // Get the ith value type, using #rest value if necessary case i < size(type-estimate-fixed-values(val)) // Wants a positional value => values(type-estimate-fixed-values(val)[i], #f); type-estimate-rest-values(val) // Out of fixed vals, but can give a rest val => values(type-estimate-rest-values(val), #t); otherwise // No #rest val, so i is out of range for fixed vals. => error("%d out of range for multiple values %s", i, val); end end, method disjoint-by-type? () => (disjoint? :: ) // OK, arities overlap: are any pair guaranteed type-disjoint? let (nfixed1, nrest1?) = arity(t1); let (nfixed2, nrest2?) = arity(t2); block (xit) for (i from 0) when ((i >= nfixed1 & ~nrest1?) | (i >= nfixed2 & ~nrest2?)) // Ran off the end of one of one w/o #rest, so fail. // If either arg has no #rest, this is the failure exit. xit(#f, #t) end; let (t1i, rest1?) = vref(t1, i); // Next value from t1 let (t2i, rest2?) = vref(t2, i); // Next value from t2 case // Provably disjoint at ith value position? type-estimate-disjoint?(t1i, t2i) => xit(#t, #t); // Into #rest of both? No point in looking further. rest1? & rest2? => xit(#f, #t); // Go try some more. otherwise => ; end end end end, method disjoint-by-arity? () => (disjoint? :: ) // See if arities could not possibly overlap. Cheap first test. let (fixed1, rest1) = arity(t1); let (fixed2, rest2) = arity(t2); // Obviously, if both are infinite, they overlap. 3 more cases: (~rest1 & ~rest2 & fixed1 ~= fixed2) // Both finite & different | (~rest1 & rest2 & fixed1 < fixed2) // 1 finite, below low of 2 | ( rest1 & ~rest2 & fixed1 > fixed2) // 2 finite, below low of 1 end; values(disjoint-by-arity?() | disjoint-by-type?(), #t) end; define function limited-integers-guaranteed-disjoint? (t1 :: , t2 :: ) => (disjoint? :: ) local method above? (min, max) case ~max | ~min => #f; // max = +inf or min = -inf otherwise => min > max; // Both finite, so test end end; above?(type-estimate-min(t1), type-estimate-max(t2)) | above?(type-estimate-min(t2), type-estimate-max(t1)) end; define function ^classes-guaranteed-disjoint?(c1 :: <&class>, c2 :: <&class>) => (disjoint? :: ) // True if classes c1 & c2 are guaranteed disjoint. In general, 2 classes // are disjoint if they have no common subclasses. All this squirming around // is because that's difficult to determine statically. See example // in guaranteed-joint?. local method ^classes-disjoint-by-primary?(c1 :: <&class>, c2 :: <&class>) => (disjoint? :: ) // We can prove c1 & c2 are disjoint if their primary superclasses // won't allow diplomatic relations. This happens when both have // primary superclasses, and those primaries aren't themselves // in a supertype/subtype relationship. // // In fact, you just have to check the leftmost primaries on each: // The primaries of each class form a chain, a subset of the CPL. // If leftmost-prim-1 is a subclass of leftmost-prim-2, then // chain above leftmost-prim-2 is already in 1's CPL, and vice versa. let c1-left-primary = ^least-primary-superclass(c1); let c2-left-primary = ^least-primary-superclass(c2); c1-left-primary ~== #f & c2-left-primary ~== #f & ~^subtype?(c1-left-primary, c2-left-primary) & ~^subtype?(c2-left-primary, c1-left-primary) end, method ^classes-disjoint-by-slots?(c1 :: <&class>, c2 :: <&class>) => (disjoint? :: ) // DRM p. 57: "... two classes which specify a slot with // the same getter or setter generic function are disjoint..." // Details cribbed from ^compute-slot-descriptors. local method slot-match? (s1 :: <&slot-descriptor>, s2 :: <&slot-descriptor>) => (match? :: ) // Owners different and either getters or setters match. // (I.e., don't be confused by commonly inherited slots!) ^slot-owner(s1) ~== ^slot-owner(s2) & (^slot-getter(s1) == ^slot-getter(s2) | (^slot-setter(s1) == ^slot-setter(s2) & ^slot-setter(s1) & ^slot-setter(s2) & #t)) end; let c2-slots = ^slot-descriptors(c2); any?(rcurry(member?, c2-slots, test: slot-match?), ^slot-descriptors(c1)) end, method ^classes-disjoint-by-domain?(c1 :: <&class>, c2 :: <&class>) => (disjoint? :: ) // *** There is another disjointness test, hence this stub: // disjoint-by-sealed-domains. This is true if the classes // are not known to be joint (i.e., there is no explicitly // known common subclass) and there is a sealed domain that // guarantees that no new common subclasses can be defined. ignore(c1); ignore(c2); #f end, // *** There are a bunch of loose/tight compilation issues: // - how much is known about a class in another library? // - how about if it's not exported from that library? // Ultimately, this is a definition of a library signature. // *** Can I exploit model-library(ci) here somehow? // *** Memoize this & do recursively, not consing like ^all-subclasses. // *** ^direct-subclasses-known-to ? ^worldwide-direct-subclasses ? method ^classes-disjoint-by-sealing?(c1 :: <&class>, c2 :: <&class>) => (disjoint? :: ) // If you get this far, c1 and c2 _could_ be disjoint, but we need // to look at subclasses to be sure. Ensure no common subclass now, // and adequate sealing to guarantee there never will be one. local method disjoint-using-subclasses? (c1-subclasses :: , c2 :: <&class>) // we know all of c1's subclasses, and // we know c2's superclasses (but not necessarily its // subclasses) ~any?(rcurry(^subtype?, c2), c1-subclasses) end; let c1-subclasses = ^all-subclasses-if-sealed(c1); if (c1-subclasses) disjoint-using-subclasses?(c1-subclasses, c2) else let c2-subclasses = ^all-subclasses-if-sealed(c2); if (c2-subclasses) disjoint-using-subclasses?(c2-subclasses, c1) else #f end end end, method ^classes-guaranteed-disjoint-1?(c1 :: <&class>, c2 :: <&class>) => (disjoint? :: ) // First check that one is not a subtype of the other ~^subtype?(c1, c2) & ~^subtype?(c2, c1) // Now they're known not to be subtypes either way. & ( ^classes-disjoint-by-primary?(c1, c2) | ^classes-disjoint-by-slots? (c1, c2) | ^classes-disjoint-by-domain? (c1, c2) | ^classes-disjoint-by-sealing?(c1, c2)) end; // First look in the cache to see if we already know the answer. // *** Investigate now that keys are 2 model-classes? let disjoint-cache :: = library-type-estimate-disjoint?-cache(current-library-description()); let cache-key1 = pair(c1, c2); let cache-element = element(disjoint-cache, cache-key1, default: not-found()); if (found?(cache-element)) // Found it in the cache. values(cache-element, #t) else // Have to compute it and remember it. Index under args both ways. let val = ^classes-guaranteed-disjoint-1?(c1, c2); disjoint-cache[cache-key1] := (disjoint-cache[pair(c2, c1)] := val); val end end; /// /// Subtypeness of s. See DRM pp. 48, 72, 73, and 124. /// /// These methods were enumerated by considering each of the types in turn, /// as the left argument. For each of those, enumerate all the things that /// could give a #t in the right argument. /// /// Another way to grok it is to make the 11x11 table for the types of the /// 2 args, put a check where there's a method, and stare at it. /// /// *** NB: do these cope with or generate "dont-know" responses correctly? /// define method type-estimate-subtype?(te1 :: , te2 :: ) => (subtype? :: , known? :: ); type-estimate-subtype?-1(te1, te2) end; /// /// Top is a supertype of everything /// define method type-estimate-subtype?(te1 :: , te2 :: ) => (subtype? :: , known? :: ); values(#t, #t) end; /// /// Bottom is a subtype of everything, including itself. Nothing other than /// bottom is ever a subtype of bottom. /// define method type-estimate-subtype?(te1 :: , te2 :: ) => (subtype? :: , known? :: ); // Bottom is a subtype of everything. values(#t, #t) end; // Disambiguating method define method type-estimate-subtype?(te1 :: , te2 :: ) => (subtype? :: , known? :: ); values(#t, #t) end; /// /// Unions subtype component-wise -- see the 3 rules on DRM p. 72. /// /// Note that there are other things you can infer if you know the union is an /// exhaustive partition, e.g., /// type-union(limited(, min: 0), limited(, max:0) = /// Currently we can't prove this (well, forwards we can, but not backwards). /// define method type-estimate-subtype?(u :: , t :: ) => (subtype? :: , known? :: ); // DRM p. 72, union subtyping rule 1: u <= t iff every(ui): ui <= t. values(every?(rcurry(type-estimate-subtype?, t), type-estimate-unionees(u)), #t) end; // Disambiguating method define method type-estimate-subtype?(te1 :: , te2 :: ) => (subtype? :: , known? :: ); values(#t, #t) end; // This can assume that te1 is not or or // and te2 is not . // Its job is to handle te2 = define generic type-estimate-subtype?-1 (te1 :: , te2 :: ) => (subtype? :: , known? :: ); define method type-estimate-subtype?-1(te1 :: , te2 :: ) => (subtype? :: , known? :: ); type-estimate-subtype?-2(te1, te2); end; define method type-estimate-subtype?-1(t :: , u :: ) => (subtype? :: , known? :: ); // DRM p. 72, union subtyping rule 2: t <= u iff exists(ui): t <= ui. // If we ever want to do something about smart subtyping between limited // types and unions of limited types, can put methods on ...-subtype?-1. values(any?(curry(type-estimate-subtype?, t), type-estimate-unionees(u)), #t) end; // This can assume that te1 is not or or // and te2 is not or define generic type-estimate-subtype?-2 (te1 :: , te2 :: ) => (subtype? :: , known? :: ); define method type-estimate-subtype?-2(te1 :: , te2 :: ) => (subtype? :: , known? :: ); // Last-ditch desperation rule: just say no. values(#f, #t) end; /// /// Multiple values subtype only amongst themselves. /// define method type-estimate-subtype?-2(v1 :: , v2 :: ) => (subtype? :: , known? :: ) // Is values type estimate v1 a subtype of v2? // * Substitutionality is the key to thinking about this: suppose a // function returned v1 when you expected v2; would that be ok, or not? // * Properly speaking, v1 <= v2 iff for every variable x1i taken // from the ith position of v1 and x2i taken from v2, x1i <= x2i. // * Howver, we don't know how many values the receiver will take, so in order // to be correct for ALL POSSIBLE receivers, we have to assume the // receiver will take INFINITE values. // * Thus everybody gets right-padded with #f's for default values. // * Since we don't know how many values are in a #rest value, the type of // anything taken from a rest value is rest U #f (you might be off the end). // * If there is no #rest value, then all values off the end are #f. let fixed1 = type-estimate-fixed-values(v1); let fixed2 = type-estimate-fixed-values(v2); let rest1 = type-estimate-rest-values(v1); let rest2 = type-estimate-rest-values(v2); let defaultf = make(, singleton: #f); local method type+defaultf (type :: false-or()) => (te :: ) // This is the type of anything extracted from a #rest value. Have // to consider that you might be "off the end," i.e., get #f. if (type) type-estimate-union(type, defaultf) else defaultf end end; let rest1+defaultf = type+defaultf(rest1); let rest2+defaultf = type+defaultf(rest2); let nfixed1 = size(fixed1); let nfixed2 = size(fixed2); values( block (return) if (empty?(fixed1) & instance?(rest1, )) // Special case for values(#rest ), which is guaranteed to be // a subtype of any values spec. Don't do #f defaulting to #rest value, // since you'll never return from a -producer anyway. #t elseif (~every?(type-estimate-subtype?, fixed1, fixed2)) // Collection-aligned values not subtypes, so fail. #f // Now look @ collection-unaligned fixed values & #rest values. elseif (nfixed1 > nfixed2) // v1 returns more fixed vals than v2 // Want (extra fixed1), (rest1 U #f) <= (rest2 U #f) for (i from nfixed2 below nfixed1) type-estimate-subtype?(fixed1[i], rest2+defaultf) | return(#f) finally type-estimate-subtype?(rest1+defaultf, rest2+defaultf) end elseif (nfixed1 < nfixed2) // v1 returns fewer fixed vals than v2 // Want rest1 U #f <= (extra fixed2), rest2 U #f for (i from nfixed1 below nfixed2) type-estimate-subtype?(rest1+defaultf, fixed2[i]) | return(#f) finally type-estimate-subtype?(rest1+defaultf, rest2+defaultf) end else // Identical # of fixed values, which are subtypes. // Think about the #rest's. type-estimate-subtype?(rest1+defaultf, rest2+defaultf) end end, #t) end; /// /// Raw subtyping. /// define method type-estimate-subtype?-2(r1 :: , r2 :: ) => (subtype? :: , known? :: ); ^subtype?(type-estimate-raw(r1), type-estimate-raw(r2)) end; /// /// Class subtyping. /// define method type-estimate-subtype?-2(t1 :: , t2 :: ) => (subtype? :: , known? :: ); // This generalizes the Dylan subtype? relationship. If 2 classes are // subtype?s of each other, then this is true. However, this can also // be true for other cases with abstraction, sealing & primariness. // For example, c1 can be a subSET of class c2 if c1 is abstract & sealed, // and all its subclasses are subtypes of c2. Then all instances of // c1 are indirect instances of some subclass, hence are indirect // instances of c2. let c1s-checked = #f; // consed lazily below let c2 = type-estimate-class(t2); local method guaranteed-joint-class?(c1 :: <&class>) => (joint? :: ) // Jointness analysis on the model classes. These are things // which must be true of c1 or, sometimes, all its subclasses. // If c1 is a subclass of c2, then they're joint. ^subtype?(c1, c2) // OK, we know c1 is not a subclass of c2. How about indirectly? | ( // If they're disjoint, then, a fortiori, they're not joint. ~^classes-guaranteed-disjoint?(c1, c2) // If c1 is open, then we can't GUARANTEE jointness: // could make new subs of c1 disjoint from c2. & ^class-sealed?(c1) // If c1 is concrete, then might have a direct instance, // but c1 isn't a subtype of c2, so direct instance isn't // an instance of c2, so not joint. & ^class-abstract?(c1) // OK, now c1 is abstract & sealed. Since c1 is abstract, it // never has any direct instances. Since c1 is sealed, can // enumerate its direct subclasses at compile time. So ask // direct subclasses of c1: is each subclass joint with c2? & guaranteed-joint-class?-recurse(c1)) end, method guaranteed-joint-class?-recurse(c1 :: <&class>) => (joint? :: ) // Recursive case -- memoized for efficiency. Due to multiple // inheritance, there might be multiple paths to a given subclass. // Memoization makes sure we consider each such subclass only 1ce. // Split out so table is consed lazly, only when needed. unless (c1s-checked ~== #f) c1s-checked := make(
) end; let c1-check = element(c1s-checked, c1, default: not-found()); if (c1-check ~== not-found()) // We've been here before, so we already know the answer. c1-check else // Haven't been here before; compute & remember the result. // (Other tests above have given #t in order to get here.) c1s-checked[c1] := // *** maybe use ^worldwide-direct-subclasses ? every?(guaranteed-joint-class?, ^direct-subclasses(c1)) end end; values(guaranteed-joint-class?(type-estimate-class(t1)), #t) end; /// /// Limited types: limited integers, limited classes, limited functions, /// limited collections, and limited instances. /// define method type-estimate-subtype?-2(lim :: , t :: ) => (subtype? :: , known? :: ); // limited(C, ...) <= T if C <= T. See p. 249. // This is a "default" method for limited types in the left argument. // Only a singleton can have its base type be itself, and there's a more // specific method for that. So this can never loop. values(type-estimate-subtype?(type-estimate-base(lim), t), #t) end; define method type-estimate-subtype?-2(te1 :: , te2 :: ) => (subtype? :: , known? :: ); // Blocking method: if one of the more specific methods on a limited // type doesn't grab you, this will say #f. It blocks the (, ) // method above, which was giving spurious #t answers on questions like // subtype?(, ), which is, of course, #f. values(#f, #t) end; define method type-estimate-subtype?-2(te1 :: , te2 :: ) => (subtype? :: , known? :: ); type-estimate-subtype?-limited(te1, te2) end; define method type-estimate-subtype?-limited(te1 :: , te2 :: ) values(#f, #t) end; /// /// Limited integers subtype among themselves by interval intersection. /// Limited integers can (rarely) be subtypes of integer singletons. /// Limited integers subtype with integer classes by the default limited method /// above. /// define method type-estimate-subtype?-limited(li1 :: , li2 :: ) => (subtype? :: , known? :: ); // DRM, p. 48: interval analysis. let cl1 = type-estimate-class(li1); let cl2 = type-estimate-class(li2); let min1 = type-estimate-min(li1); let min2 = type-estimate-min(li2); let max1 = type-estimate-max(li1); let max2 = type-estimate-max(li2); values(^subtype?(cl1, cl2) // Same kind & (min2 == #f | (min1 ~== #f & min1 >= min2)) // No min rstr | above & (max2 == #f | (max1 ~== #f & max1 <= max2)), // No max rstr | below #t) end; // Due to normalization, min ~== max, so this can't happen /* define method type-estimate-subtype?-limited(l :: , s :: ) => (subtype? :: , known? :: ); // limited integers can be singletons, if interval is exactly 1. let l-cl = type-estimate-class(l); let s-cl = type-estimate-class(s); let sing = type-estimate-singleton(s); let min = type-estimate-min(l); let max = type-estimate-max(l); values(^subtype?(l-cl, s-cl) & min = sing & max = sing, #t) end; */ /// /// Limited classes subtype among themselves by subclassing. /// Limited classes can (rarely) subtype with class singletons, provided it's /// sealed and there are no subclasses in the defining library. /// Limited classes subtype with , by the default limited method above. /// define method type-estimate-subtype?-limited(lc1 :: , lc2 :: ) => (subtype? :: , known? :: ); // subclass(c1) <= subclass(c2) iff c1 <= c2 values(^subtype?(type-estimate-subclass(lc1), type-estimate-subclass(lc2)), #t) end; define method type-estimate-subtype?-limited(sub :: , sing :: ) => (subtype? :: , known? :: ); // subclass() = singleton() if is sealed & no subclasses. // (Normalization shouldn't let this happen, but...) let subclass = type-estimate-subclass(sub); let sing = type-estimate-singleton(sing); values(subclass == sing // Same class in both cases & ^sealed-with-no-subclasses?(subclass), // and it's sealed and no subclasses here #t) end; /// /// Limited functions subtype among each other by the usual contravariant rule. /// They DON'T subtype with singletons of functions, since they constrain only /// the signature of the function, not its body. (*** Do we believe this???) /// Limited functions subtype with et al. by the default limited /// method above. /// define method type-estimate-subtype?-limited(f1 :: , f2 :: ) => (subtype? :: , known? :: ); // Subtyping of limited functions. local method fn-code (rest?, keys, all?) => (code :: limited(, min: 0, max: 3)) // Encode tail behavior for the table below. ecase ~rest? & ~keys & ~all? => 0; // Expects 0 more args keys & ~all? => 1; // Expects finite # key/val pairs all? => 2; // Expects infinite # key/val pairs rest? & ~keys & ~all? => 3; // Expects infinite anything end end, method arg-types>=?(f1, f2) => (contains? :: ) // Think substitutionality: can you use an f1 where f2 is expected? let reqs1 = type-estimate-requireds(f1); let reqs2 = type-estimate-requireds(f2); let rest?1 = type-estimate-rest?(f1); let rest?2 = type-estimate-rest?(f2); let keys1 = type-estimate-keys(f1); let keys2 = type-estimate-keys(f2); let all?1 = type-estimate-all-keys?(f1); let all?2 = type-estimate-all-keys?(f2); case (size(reqs1) > size(reqs2) | ~every?(type-estimate-subtype?, reqs2, reqs1) | (keys1 & keys2 & ~every?(type-estimate-subtype?, keys2, keys1))) // Calls of 2 could have too few args for 1 (can't be fixed by // assuming 1 has a rest arg or anything like that). // Or types of shared required args don't match. // Or both have keywords & types of shared keys don't match. // If #rest parameter could be typed (DRM p 83), could hack that. => values(#f, #t); // If you get here, at least enough args will be given for 1, // and that the types match for the requireds which are shared, // and that the types match for the keys which are shared. (size(reqs1) = size(reqs2)) // Same number of required args, and types compatible. // // Each has rest, specifies keys, or all-keys. From those, we // classify the function into one of 4 cases: expects 0 more args, // expects finite key/val pairs, expects infinite key/val pairs, // and expects infinite anything. Here's the match test: // f2 // _________________ // || 0 | kF | kI | I | // +=======================| // | 0 || Y | N | N | N | // |----||---+----+----+---| // f1 |kF || Y | * | N | N | * means check keys2 subset keys1 // |----||---+----+----+---| // |kI || Y | Y | Y | N | // |----||---+----+----+---| // | I || Y | Y | Y | N | // ----------------------- => let f1-code = fn-code(rest?1, keys1, all?1); let f2-code = fn-code(rest?2, keys2, all?2); if (f1-code = 1 & f2-code = 1) // Check keys2 are subset of keys1 table-key-subset?(keys2, keys1) else // Just have to be in lower triange or diagonal. f1-code >= f2-code end; otherwise // 1 has fewer required args than 2, so needs #rest or something. // // Now we have to decide what to do for 2's "extra" args. // 1 must have #rest or some explicit #key words, or #all-keys. // * If 1 has #rest only, there can never be too many args. // Since #rest's , there's no type problem. // * But if 1 has either explicit #key words or #all-keys, // it expects key/val pairs. 2 is giving general args though, // in its remaining required args. So no match. => values(rest?1 & ~keys1 & ~all?1, #t); end end; // The way to think about this is substitutionality: can we use f1 where we // were expecting f2? In order for that to happen, 2 things must be true: // // [1] values(f1) <= values(f2), i.e., we have to be able to accept the values // returned by f1. // // [2] args(f1) >= args(f2), i.e., the args of f1 must be AT LEAST AS GENERAL // as those for f2, so it can be called where f2 was expected. // // This means args type THE OTHER WAY from values; hence the phrase // "contravariant function typing." values(arg-types>=?(f1, f2) & type-estimate-subtype?(type-estimate-values(f1), type-estimate-values(f2)), #t) end; /// /// Limited collections subtype among themselves. /// Limited collections can (rarely) subtype with singletons, e.g., /// singleton(#()). /// Limited collections subtype et al. by the default limited /// method above. /// define method type-estimate-subtype?-limited(lc1 :: , lc2 :: ) => (subtype? :: , known? :: ); // Limited collections: DRM, pp. 124-125. (The case where lc1 is a singleton // is taken care of in the singleton method above.) let class1 = type-estimate-class(lc1); let class2 = type-estimate-class(lc2); let of1 = type-estimate-of(lc1); let of2 = type-estimate-of(lc2); let size1 = type-estimate-size(lc1); let size2 = type-estimate-size(lc2); let dim1 = type-estimate-dimensions(lc1); let dim2 = type-estimate-dimensions(lc2); values(if (~dim1 & ~dim2) // Rule 1: nobody specifies dimensions:. ^subtype?(class1, class2) & (~size2 | size1 = size2) // *** why not size1 >= size2 ? & ((of1 == #f & of2 == #f) | // Both #f (of1 ~== #f & of2 ~== #f & // Or both not #f & equivalent. type-estimate=?(of1, of2))) // *** why not type-estimate-subtype?(of1, of2)? else // Rule 2: somebody specified dimensions:. ^subtype?(class1, class2) & dim1 // *** why not size1 & dim2 = rank1? & (~dim2 | every?(\=, dim1, dim2)) // *** why not >=? & (size2 | reduce(\*, 1, dim1) = size2) // *** why not >=? & ((of1 == #f & of2 == #f) | // Both #f (of1 ~== #f & of2 ~== #f & // Or both not #f & equivalent. type-estimate=?(of1, of2))) // *** why not type-estimate-subtype?(of1, of2)? end, #t) end; define method type-estimate-subtype?-limited(lc :: , s :: ) => (subtype? :: , known? :: ); // Users can't say limited(, size: 0), but we might be able to infer it. values(type-estimate-class(lc) == dylan-value(#"") & type-estimate-size(lc) == #() & type-estimate-singleton(s) == #(), #t) end; /// /// Singletons subtype with anything else if they're an instance of it. /// define method type-estimate-subtype?(s :: , t :: ) => (subtype? :: , known? :: ); // DRM, p. 48: "by object type and identity" type-estimate-instance?(type-estimate-singleton(s), t) end; // Disambiguating method define method type-estimate-subtype?(s :: , t :: ) => (subtype? :: , known? :: ); // DRM, p. 48: "by object type and identity" values(#t, #t) end; /// /// Retraction of type-estimates. /// define method type-estimate-retract (ref :: ) => (did-anything? :: ); let cache = library-type-cache(current-library-description()); // The "right" way to do this is to remove ref from the cache, and then // for each (recursive) dependent of ref's type, take the difference with // ref's type, and so on. // // Ok, that's hard, since don't yet have a theory of type-estimate-difference! // So just yank ref out of the cache, and yank out all dependents, as well. // Compares like a baseball bat to a scalpel, but works. local method type-estimate-retract-1 (ref :: ) => (da? :: ) // Simple-minded recursive key removal from the cache. Return #t // if we did anything, else #f. let type-var = cached-type-variable(ref, cache); type-var & begin // It was in the cache, so we have work to do. retract-cached-type-variable(ref, cache); do(method (just) type-estimate-retract-1(justification-lhs(just)) end, type-variable-supportees(type-var)); #t end end; type-estimate-retract-1(ref) end;