Constrained genericity: Difference between revisions

m
→‎{{header|Wren}}: Changed to Wren S/H
m (syntax highlighting fixup automation)
m (→‎{{header|Wren}}: Changed to Wren S/H)
(3 intermediate revisions by one other user not shown)
Line 1,595:
<syntaxhighlight lang="swift">func foo<T: Eatable>(x: T) { }
// although in this case this is no more useful than just "func foo(x: Eatable)"</syntaxhighlight>
 
=={{header|TXR}}==
 
===Macro wrapper for <code>defstruct</code>===
 
We implement a food-box-defining macro, which checks at macro expansion time that the contained type is <code>edible</code>. The macro generates a structure of the specified name, which has a <code>set-food</code> method that additionally performs a run-time check against the exact variant of the <code>edible</code> type that was given to the macro.
 
<syntaxhighlight lang="txrlisp">(defmacro define-food-box (name food-type : supers . clauses)
(unless (subtypep food-type 'edible)
(error "~s requires edible type, not ~s" %fun% food-type))
^(defstruct ,name ,supers
food
(:method set-food (me food)
(unless (typep food ',food-type)
(error "~s: requires ~s object, not ~s" %fun% ',food-type food))
(set me.food food))
,*clauses))</syntaxhighlight>
 
Instead of the type-based <code>subtypep</code> check, we could easily check for the existence of methods; e.g. test for the presence of a static slot using <code>(static-slot-p food-type 'eat)</code>, or more specifically that it's a function: <code>(functionp (static-slot food-type 'eat))</code>. These tests will blow up if the macro's <code>food-type</code> argument isn't a struct type.
 
In the interactive session below, we:
 
* verify that <code>define-food-box</code> cannot be used with a type argument that isn't derived from <code>edible</code>
* define the struct type <code>edible</code> and then one derived from it called <code>perishable</code>.
* use <code>define-food-box</code> to define a box called <code>fridge</code> which holds <code>perishable</code>. This works because <code>perishable</code> is <code>edible</code>
* create an instance of <code>fridge</code> and show that its <code>set-food</code> method doesn't take an integer, or an <code>edible</code>; only a <code>perishable</code>.
 
{{out}}
 
<pre>$ txr -i generic.tl
This area is under 24 hour TTY surveillance.
1> (define-food-box fridge string)
** define-food-box requires edible type, not string
** during evaluation of form (error "~s requires edible type, not ~s"
'define-food-box
food-type)
** ... an expansion of (error "~s requires edible type, not ~s"
%fun% food-type)
** which is located at generic.tl:3
1> (defstruct edible ())
#<struct-type edible>
2> (defstruct perishable (edible))
#<struct-type perishable>
3> (define-food-box fridge perishable)
#<struct-type fridge>
4> (new fridge)
#S(fridge food nil)
5> *4.(set-food 42)
** (set-food fridge): requires perishable object, not 42
** during evaluation of form (error "~s: requires ~s object, not ~s"
'(set-food fridge)
'perishable
food)
** ... an expansion of (error "~s: requires ~s object, not ~s"
%fun% 'perishable
food)
** which is located at expr-3:1
5> *4.(set-food (new edible))
** (set-food fridge): requires perishable object, not #S(edible)
** during evaluation of form (error "~s: requires ~s object, not ~s"
'(set-food fridge)
'perishable
food)
** ... an expansion of (error "~s: requires ~s object, not ~s"
%fun% 'perishable
food)
** which is located at expr-3:1
5> *4.(set-food (new perishable))
#S(perishable)
6> *4
#S(fridge food #S(perishable))</pre>
 
===Custom <code>defstruct</code> clause===
 
Wrapping <code>defstruct</code> is a heavy-handed approach that may be difficult to retrofit into an existing code base. One possible issue is that two developers write such a macro, and then someone needs to use both of them for the same class. But each macro wants to write its own entire <code>defstruct</code> form.
 
Here, we instead use a custom clause to inject the <code>food</code> slot, <code>set-food</code> method, and the static and dynamic checks. The mechanisms remain identical.
 
<syntaxhighlight lang="txrlisp">(define-struct-clause :food-box (food-type :form form)
(unless (subtypep food-type 'edible)
(compile-error form "~s requires edible type, not ~s" :food-box food-type))
^(food
(:method set-food (me food)
(unless (typep food ',food-type)
(error "~s: requires ~s object, not ~s" %fun% ',food-type food))
(set me.food food))))</syntaxhighlight>
 
{{out}}
 
<pre>$ txr -i generic.tl
Apply today for a TXR credit card, and get 1MB off your next allocation.
1> (defstruct fridge ()
(:food-box string))
** expr-1:1: defstruct: :food-box requires edible type, not string
1> (defstruct edible ())
#<struct-type edible>
2> (defstruct perishable (edible))
#<struct-type perishable>
3> (defstruct fridge ()
(:food-box perishable))
#<struct-type fridge>
4> (new fridge)
#S(fridge food nil)
5> *4.(set-food 42)
** (set-food fridge): requires perishable object, not 42
** during evaluation of form (error "~s: requires ~s object, not ~s"
'(set-food fridge)
'perishable
food)
** ... an expansion of (error "~s: requires ~s object, not ~s"
%fun% 'perishable
food)
** which is located at expr-3:1
5> *4.(set-food (new edible))
** (set-food fridge): requires perishable object, not #S(edible)
** during evaluation of form (error "~s: requires ~s object, not ~s"
'(set-food fridge)
'perishable
food)
** ... an expansion of (error "~s: requires ~s object, not ~s"
%fun% 'perishable
food)
** which is located at expr-3:1
5> *4.(set-food (new perishable))
#S(perishable)</pre>
 
=={{header|Wren}}==
Wren is dynamically typed and so any constraint on class instantiation can only be checked at run time.
<syntaxhighlight lang="ecmascriptwren">// abstract class
class Eatable {
eat() { /* override in child class */ }
9,482

edits