(define-value-type SSN : String [1 .. 9 | 11] ssn-validation-predicate)
Goals:
1) Avoid boxing/unboxing (struct cell / cell-refs).
2) Create generative sub-types of certain base types, String, Number to satisfy TR. Note they are not opaque types but, i.e. T <: String
Item 2) means
;; works as Ages are Integers
(: add-ages (Age Age -> Age))
(define (sum-ages a1 a2)
(Age (+ a1 a2)) ;; + defined on Integers
;; not the same as (define-type Age Integer) because ...
(sum-ages 12 16) ;; fails as Integers are not Ages
;; lifting Integer to Age involves a runtime contract check, but no allocation.
(sum-ages (Age 12) (Age 16)) ;; fine, no allocation
A hand waved way of getting there, which got ugly quick and as I typed. I was sort of brainstorming if I could get Value Types without any Racket internal surgery and with no more than a bit of macrology.
So waving both hands wildly...
0) Modify the TR `cast' operator to recognize Value Type structures.
a) The generated contract from the `cast' operator of a value type to an appropriate Value Type structure succeeds at runtime for an instance of the value type.
b) The generated contract from the `cast' operator applies the gen:validator on the Value Type structure as part of the contract.
1) Extend the struct: macro to allow a struct: parent to be not only another struct: but a [struct: | String | Number]
a) IF the parent is a struct: nothing new to do here.
b) If parent is a value type, String or a Number (value type)
- This is a Value Type structure.
- A value type structure has only one mandatory value which is of the same type as the parent.
- A Value Type structure is -sealed- and may not be used as the parent in another struct: definition.
- A Value Type structure's constructor is a (A -> A) pass-thru of the value. i.e., the struct: is never allocated to wrap the value.
- A Value Type structure _may_ have a gen:validate generic method associated with it.
4) To avoid creating a true Value Type structure instance via low level apis, they would need to be modified to prohibit creating any instance of a Value Type structure.
What we are trying to achieve is all of the type checking from TR using struct: to generate a new type at compile time, yet at runtime the instance values of the Value Type are the primitive values and are NOT manifested as the struct: instances.
Example:
Create an SSN Value Type.
;; An SSN is String value, of length 9 or 11, which is validated by a regular expression.
(: ssn-validation-predicate (String -> Boolean : SSN))
(define (ssn-validation-predicate str-ssn)
(regexp-match? ssn #rx(....)))
(define-value-type SSN String [9 | 11] ssn-validation-predicate) ;;
The above roughly expands to:
(struct: SSN String ([value : String])
#:methods gen:validate ssn-validation-predicate)
(define SSN-validator-contract (and/contract ....)) ;;; combines string-length 9|11 check with ssn-validation-predicate into a contract
The struct: macro notes that this is a Value Type structure definition because its parent is a value type, String, and not another struct:. So the generated SSN constructor function avoids creating an actual structure at runtime and allows a string value as successfully cast to a SSN after applying any associated validation contract.
(: SSN (String -> SSN))
(define (SSN ssn)
(cast ssn SSN))
In the above ...
- `cast' knows we are casting to a Value Type, SSN, so generated runtime contract allows a String value (and _not_ a SSN struct type instance) to be "passed-thru" but lifted to type SSN for TR purposes.
- Therefore, the cast fails an actual instance of an SSN structure, if one somehow managed to construct an instance.
- As part of the `cast' generated contract the SSN-validator-contract and length checks are combined and applied interstitial in the pass-thru of (String -> String).
And finally since SSN at runtime is a string value, at compile time it's a subtype of String so...
(substring (SSN "123-45-6789") 0 3) ;; works at TR compile time checking and at runtime running
(substring (SSN "123x456-5689") 0 3) ;; fails validation at runtime, though a sufficiently smart compiler would apply the contract validation check at compile time for values known at compile time.
(parse-ssn (SSN "123-456-6789")) ;; works but runtime representation remained as a string value, no struct: box/unbox.