Commit | Line | Data |
3659fb05 |
1 | =head1 TITLE |
2 | |
3 | Specification - Technical details for dependent types |
4 | |
5 | =head1 DESCRIPTION |
6 | |
7 | Specification for extra, declaritive hinting to type constrain definitions. |
8 | |
9 | =head1 INTRODUCTION |
10 | |
b3469a41 |
11 | We'd like to be able to add additional 'facets' when declaring our type |
3659fb05 |
12 | constraints. These hints would form an extention to the core types built into |
13 | Moose, such as Str, Object, etc. as well as form the foundation for authors of |
14 | Type Constraint extentions to also add these hints. The idea is these hints |
15 | would allow us to move a lot of the more common constraint types from the |
16 | where clause of a subtype to the subtype meta data. This would make it simple |
17 | to create a lot of the more common constraint types, improving usability and |
18 | consistency. Also, since the implementations for these constraints would be |
19 | universally vetted, we are likely to improve global code quality. |
20 | |
21 | Additional advantages would be those derived from the common code ecosystem, |
22 | that is that more people would share common knowledge, improving our ability |
23 | to build a strong community of shared best practices. |
24 | |
25 | Lastly, by hinting type constraints with additional metadata, the possibility |
26 | to introspect that metadata usefully for code generation becomes possible. In |
27 | particular, we'd like these additional constraints to fit neatly with existing |
28 | code generation and mapping tools, such asd DBIx-Class, Reaction, Ernst, etc. |
29 | |
30 | So, an important goal of choosing the hints to to choose and define ones that |
31 | can be mapped to other type constraint systems. For example, having a maximum |
32 | length hint for a string type would map well to most database column types, |
33 | but a hint to match a string to a regular expression should be defined |
34 | carefully, since most databases have limited if any abilities to do this. |
35 | |
36 | Therefore, it will be important to make sure the hinting system can degrade or |
37 | cast gracefully, that way hints defined at the application level will work |
38 | neatly with those defined at a possible database level. |
39 | |
b3469a41 |
40 | =head1 PROPOSED SYNTAX |
41 | |
42 | The following is proposed syntax for the faceted types: |
43 | |
44 | subtype MyType, |
45 | is Str, |
46 | which hasMinLenth(5), hasMaxLength(20), |
47 | where {}; |
48 | |
3659fb05 |
49 | =head1 USE CASES |
50 | |
51 | The following are possible use cases. |
52 | |
53 | =head2 Str |
54 | |
55 | It's common to add additional constraints to the STR type. How many times have |
56 | you done something like: |
57 | |
58 | my $tc = subtype MaxStr10, as Str, where { length($_) <= 10 }; |
59 | |
60 | To create a string of 10 characters or less. Since this constraint is captured |
61 | in the code block it is not introspecable. A possible syntax to declare a Str |
62 | type constraint with a maximum length hint might be: |
63 | |
b3469a41 |
64 | my $tc = subtype MaxStr10, as Str, which hasMaxInclusiveLength(10); |
3659fb05 |
65 | |
b3469a41 |
66 | this information might be introspectable via something like: |
3659fb05 |
67 | |
b3469a41 |
68 | $tc->meta->facets->max_length_inclusive; ## returns 10 |
3659fb05 |
69 | |
70 | =head2 DataTime and coercion possibilities |
71 | |
72 | The following example shows how this system could work to enhance type |
73 | constraints in the MooseX-Types* namespace. It also shows how coercions could |
74 | be hooked into the hint system to allow more finely tuned coercions: |
75 | |
76 | use MooseX::Types::DateTime qw(DateTime); |
77 | |
78 | subtype USEasternDataTime, |
b3469a41 |
79 | is DateTime, |
80 | which hasTimeZone('US/Eastern'); |
3659fb05 |
81 | |
82 | coerce USEasternDataTime, |
b3469a41 |
83 | from DateTime, |
84 | which hasTimeZone('Floating') |
3659fb05 |
85 | via {$_->timezone('US/Eastern')}; |
86 | |
87 | The created subtype USEasternDataTime would only pass if the DateTime object |
88 | has the defined timezone. Their is a coercion that would adjust timezone |
89 | information to the canonical type. |
90 | |
91 | =head2 Code generation and mapping |
92 | |
b3469a41 |
93 | Given a type constraint such as: |
94 | |
95 | subtype ValidName, |
96 | as Str, |
97 | which hasMaxInclusiveLength(45), isAlpha(), |
3659fb05 |
98 | |
99 | That becomes part of a class definition: |
100 | |
101 | package MyApp::Person; |
102 | ... |
b3469a41 |
103 | has 'name' => (isa=>ValidName, required=>1); |
3659fb05 |
104 | |
105 | A code generator or interpreter could introspect the metadata on the attribute |
106 | 'short_name' to create a table like: |
107 | |
108 | CREATE TABLE Person ( |
b3469a41 |
109 | short_name varchar(45) |
110 | ); |
111 | |
112 | Please note how the code generator would need to aggregate meta information from |
113 | both the attribute options as well as it's type constraint, since in this case |
114 | the attribute is required. Here's another, more complicated option. |
115 | |
116 | package MyApp::Types::StatisticsPeople; |
117 | |
118 | ## both types are limited to 45 alphabetical characters that define a |
119 | ## single word (no whitespace before or after), like "John", but not allow |
120 | ## " John", " John ", "john123", etc. |
121 | |
122 | subtype FirstName, |
123 | as Str, |
124 | which hasMaxInclusiveLength(45), isAlpha(), isWord(); |
125 | |
126 | subtype ValidName, |
127 | as Str, |
128 | which hasMaxInclusiveLength(45), isAlpha(), isWord(); |
129 | |
130 | package MyApp::Person; |
131 | |
132 | use Moose; |
133 | use MyApp::Types::StatisticsPeople (qw/FirstName LastName/); |
134 | |
135 | has first_name => (isa=>FirstName, required=>1); |
136 | has last_name => (isa=>LastName, required=>1); |
137 | |
138 | The above might be generated into sql like so (let's assume a database that |
139 | has schema support, like Postgresql, |
140 | |
141 | CREATE TABLE StatisticsPeople.FirstName ( |
142 | first_name_id uuid, |
143 | value varchar(45), |
144 | PRIMARY KEY('first_name_id') |
145 | ); |
146 | |
147 | CREATE TABLE StatisticsPeople.LastName ( |
148 | last_name_id uuid, |
149 | value varchar(45), |
150 | PRIMARY KEY('last_name_id') |
151 | ); |
152 | |
153 | CREATE TABLE PersonFirstName ( |
154 | fk_person_id uuid, |
155 | fk_first_name_id uuid, |
156 | PRIMARY KEY('fk_person_id', 'fk_first_name_id'), |
157 | ) |
158 | |
159 | CREATE TABLE Person ( |
160 | person_id uuid, |
161 | fk_first_name_id uuid, |
162 | fk_last_name_id uuid, |
163 | PRIMARY KEY ('person_id') |
164 | CONSTRAINT 'person_validname' |
165 | FOREIGN KEY ('fk_last_name_id') |
166 | REFERENCES 'ValidName' ('valid_name_id'), |
3659fb05 |
167 | ); |
168 | |
b3469a41 |
169 | my $person = model('Person')->create({ |
170 | person_first_name => { first_name => {value=>'John'} }, |
171 | ... |
172 | }); |
173 | |
174 | Probably an extreme options, but if we have types with all this meta data, why |
175 | not have it reflect down to the database model? |
176 | |
3659fb05 |
177 | [Addional Code examples to follow] |
178 | |
b3469a41 |
179 | =head1 CORE TYPES AND FACET HEIRARCHY |
3659fb05 |
180 | |
181 | The following is an outline of the possible hints associated with core Moose |
182 | type constraints. Hints are assumed to be inherited. |
183 | |
184 | Item[ |
185 | Default |
186 | EqualTo |
187 | NotEqualTo |
188 | ] |
189 | Bool |
190 | Maybe[`a] |
191 | Undef |
192 | Defined |
193 | Value[ |
194 | IsOneOf||IsNotOneOf |
195 | LessThanOrEqual || '<=' |
196 | LessThan || '<' |
197 | GreaterThanOrEqual || '>=' |
198 | GreaterThan || '>' |
199 | Between |
200 | NotBetween |
201 | ] |
202 | Num[ |
203 | Base |
204 | Precision |
205 | Scale |
206 | DDUNCAN |
207 | ] |
208 | Int[ |
209 | Signed |
210 | UnSigned |
b3469a41 |
211 | Even |
212 | Odd |
3659fb05 |
213 | ] |
3659fb05 |
214 | Str[ |
215 | ASCII* |
216 | Printable* |
b3469a41 |
217 | isDigits* (That is, a string that is a number like "123") |
3659fb05 |
218 | Word (must be a single 'word') |
219 | MaxLength |
220 | MinLength |
221 | EqualTo || 'eq' |
222 | ] |
223 | |
224 | * Might be better as a general encoding type. |
225 | |
226 | ClassName[ |
227 | PackageBase (For stuff like '::Plugin::MyPlugin') |
228 | ] |
229 | Ref |
230 | ScalarRef |
231 | ArrayRef[`a][ |
232 | MaxElements |
233 | MinElements |
b3469a41 |
234 | hasDuplicates |
235 | hasOnlyUnique |
3659fb05 |
236 | ] |
237 | HashRef[`a][ |
238 | HasAllKeys |
239 | ] |
240 | CodeRef |
241 | RegexpRef |
242 | GlobRef |
243 | FileHandle |
244 | Object[ |
245 | HasMethods |
246 | ISA |
247 | ] |
248 | Role[ |
249 | Requires |
250 | Does |
251 | ] |
b3469a41 |
252 | |
253 | ** |
254 | Might like to have some stuff from the Math packages, like to validate if a |
255 | value matches a given function, or is in the domain or range of a function. |