diff options
| author | 2025-03-05 17:57:07 -0500 | |
|---|---|---|
| committer | 2025-03-05 17:57:07 -0500 | |
| commit | 122924a4d15b11a46ec4bade401174fe810cce15 (patch) | |
| tree | 5d2640e25aa9df4ea915b89cebafaf63699a42d3 /README.md | |
| parent | remove special handling of the default implementation (diff) | |
add subtyping back
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 139 |
1 files changed, 75 insertions, 64 deletions
@@ -20,13 +20,14 @@ surprises. ### Why Singly Polymorphic (aka Single Dispatch)? -Multiple dispatch runs into the problems that subtype inheritance runs -into (see the section "On Inheritance"). +Multiple dispatch runs into problems similar to that of multiple +inheritance, except that there is no obvious dismabiguation pattern when +two equally specific multiple dispatch methods exist. -If one wanted multiple dispatch or multiple inheritance, it would be better -to use a CLOS style system, which has much more fine-grained control over -method dispatch. Dynamic and local scoping could be added to a CLOS style -system, although the API would probably have to change. +If one wanted multiple dispatch or multiple inheritance, it would be +better to use a CLOS style system, which has much more fine-grained +control over method dispatch. Dynamic and local scoping could be added +to a CLOS style system, although the API would probably have to change. ### Why Scope? @@ -52,11 +53,15 @@ without mutating global state and without overriding local scope. ### On Inheritance -I am very conflicted on inheritance for subtypes of a type. The issue -is that combining it with scoping introduces a form of the diamond -problem. For example: imagine if type `T` has an implementation in global -scope, and its supertype `T'` has an implementation in local scope. Which -one should be used? +Scheme, both through the numeric tower (see below) and R6RS, have single +inheritance. I do not like inheritance: Rust (which inspired me to write +this) does not have inheritance at all. + +Combining subtype inheritance (even when restricted to single inheritance) +with scoping introduces a form of the diamond problem. For example: +imagine if type `T` has an implementation in global scope, and its +supertype `T'` has an implementation in local scope. Which one should +be used? SAHP / \ @@ -66,15 +71,9 @@ one should be used? \ / T -There are two reasonable choices: most specific and closest scope. Closest -scope is probably the best option, but in some instances it may not -be enough. One might want to specify a default implementation without -desiring to override all specific implementations of subtypes. This could -be done by giving one the ability to specify how each implementation -should be inherited. - -SAHPs currently do not automatically inherit implementations. Records -designed with SAHPs in mind should use composition, not inheritance. +SAHPs default to selecting the closest scope. However, an implementation +for a type may be labeled `overridable`, in which case if a more specific +type occurs in a higher scope, that implementation is selected. ### The Numeric Tower @@ -94,50 +93,44 @@ defined. They will usually consist of * irrationals, * proper complex numbers. -An implementation could also have unitful numbers, quarternions, etc. - -In order to support the numeric tower, SAHPs define portable sets of -types arranged in a hierarchy of subsets. They are - - all-exact-integers - ⊆ all-exact-rational - ⊆ all-real - ⊆ all-complex - ⊆ all-number +Notably, exactness *is* decided by type (or more accurately, the machine +representation). -Type sets are lists that contain other type sets or concrete -types. Defining an implementation against a type set explicitly -defines the type set against all implementations, overriding previous -implementations: hence the prefix `all`. These sets are static. +SAHPs dispatch on the following hierarchy of types: -### What Types are dispatched against? +* `exact-integer` +* `exact-rational` +* `real` +* `complex` +* `number` -All fundamental types (pairs, fixnums, vectors, defined record types). -Predicates are not dispatched against because that would make the type -system inefficient. One could force this by putting predicates into the -default implementation of a SAHP. +This probably captures most practical uses of the numeric tower. ## API A SAHP is a procedure object. There is no portable way to differentiate -between a SAHP and a regular procedure. A SAHP has multiple tables (some -conceptual) where an implementation is looked up. In order, the SAHP will +between a SAHP and a regular procedure. A SAHP has multiple tables +(some conceptual) where an implementation is looked up. The SAHP will dispatch on (in order of precedence): * local scope * dynamic scope * global scope -* default implementation in local scope -* default implementation in dynamic scope -* default implementation in global scope -The scope resolution tries to find the most specific implementation, and -given a specific implementation, the closest scope. +The scope resolution tries to find the most specific implementation in +the closest scope. If the implementation in closest scope is not the +most specific implementation, then: + +* If the implementation in closest scope is labeled `overridable`, then + the implementation in the next closest scope is selected, and this + disambiguation process continues again. +* If the implementation in closest scope is not labeled `overridable`, + then it is selected. The SAHP will raise an error object that satisfies the predicate `SAHP-implementation-not-found-error?` if no implementation is found. -Multiple SAHPs can share global scope tables, and dynamic scope. +Multiple SAHPs can share global scope tables and dynamic scope. The procedures stored in a SAHP must take at least one argument, which is the argument whose type is dispatched against. @@ -150,13 +143,10 @@ it will call `procedure` with all of the arguments passed to it. A type expression is defined as: -* The symbols `boolean`, `char`, `null`, `pair`, `procedure`, `symbol`, - `bytevector`, `eof-object`, `port`, `string`, or `vector`, or -* The values of `all-exact-integers`, `all-exact-rationals`, - `all-real`, `all-complex`, and `all-number`, or -* Lists of type expressions, or +* The symbols `*` `boolean`, `char`, `null`, `pair`, `procedure`, `symbol`, + `bytevector`, `eof-object`, `port`, `string`, `vector`, + `exact-integers`, `exact-rationals`, `reals`, `complex`, or `numbers`, or * the value bound to a name of a record type, or -* the symbol `*`, for the default implementation * any other implementation defined value. ### The Object @@ -173,33 +163,50 @@ dynamic state. SAHPs that are `SAHP=?` are not necessarily `eqv?`. ### Global Scope - (set-global-SAHP! SAHP type-expr procedure procedure) + (set-global-SAHP! SAHP type procedure) + +or + + (set-global-SAHP! SAHP type flag procedure) + +The flag can be omitted, or the symbol `overridable`. -Set `SAHP` to resolve to `procedure` given `type-expr` in global scope. +Set `SAHP` to resolve to `procedure` given `type` in global scope. - (define-global-SAHP (name (type-expr argument) . arg-rest) body ...) + (define-global-SAHP (name (type argument) . arg-rest) body ...) + +or + + (define-global-SAHP (name (type flag argument) . arg-rest) body ...) Creates a procedure with the given arguments and body. The SAHP will -resolve to this procedure given `type-expr` in global scope. +resolve to this procedure given `type` in global scope. + +The flag can be omitted, or the symbol `overridable`. ### Dynamic Scope - (parameterize-SAHP ((SAHP (type-expr value) ...) ...) body ...) + (parameterize-SAHP ((SAHP (type value) ...) ...) body ...) + +or + + (parameterize-SAHP ((SAHP (type flag value) ...) ...) body ...) Each `SAHP` must resolve to an SAHP. -Each SAHP will have each `type-expr` under it resolve to `value` -in dynamic scope. These procedures take precedence over global scope -procedures. The SAHPs are the same SAHP in the block in the sense of -`eq?`. +Each SAHP will have each `type` under it resolve to `value` in dynamic +scope. These procedures take precedence over global scope procedures. +The SAHPs are the same SAHP in the block in the sense of `eq?`. + +The flag can be omitted, or the symbol `overridable`. ### Local Scope - (letrec-SAHP ((SAHP (type-expr value) ...) ...) body ...) + (letrec-SAHP ((SAHP (type type value) ...) ...) body ...) Each `SAHP` must be an identifier that resolves to a SAHP. -Each SAHP will have each `type-expr` under it resolve to the corresponding +Each SAHP will have each `type` under it resolve to the corresponding `value` in lexical scope. Each procedure will have the new SAHPs in scope. These procedures will take precedence over dynamic and global scope procedures. @@ -213,6 +220,10 @@ If the SAHPs passed to `letrec-SAHP` had local bindings, then `letrec-SAHP` will replace old bindings and add new ones without affecting the previous SAHP object. +The flag can be omitted, or the symbol `overridable`. + ## TODO * Standard SAHP library. +* `if-not-exists` to provide default implementations for specific + types. |
