Introducing Modern Perl Dave Cross Magnum Solutions Ltd [email_address]
Modern Perl <ul><li>Perl has been around for a long time
Achieved great popularity in the 1990s
“The duct tape of the internet”
Perl has moved on a lot since then
Many people's perceptions of Perl haven't </li></ul>
Modern Perl <ul><li>Keeping up to date with Perl
Surveying modern modules and techniques
A lot to cover
Only an overview
Plenty of pointers to more information </li></ul>
What We Will Cover <ul><li>Template Toolkit
DateTime
DBIx::Class
TryCatch </li></ul>
What We Will Cover <ul><li>Moose
autodie
Catalyst
PSGI/Plack </li></ul>
Schedule <ul><li>09:00 – Begin
11:00 – Coffee break
13:00 – Lunch
14:00 – Begin
16:00 – Coffee break
17:00 – End </li></ul>
Resources <ul><li>Slides available on-line </li><ul><li>http://mag-sol.com/train/yapc/2010 </li></ul><li>Also see Slidesha...
Template Toolkit
Templates <ul><li>Many people use templates to produce web pages
Advantages are well known
Standard look and feel (static/dynamic)
Reusable components
Separation of code logic from display logic
Different skill-sets (HTML vs Perl) </li></ul>
DIY Templating <ul><li>Must be easy - so many people do it
See perlfaq4
“How can I expand variables in text strings?” </li></ul>
DIY Templating <ul><li>$text = 'this has a $foo in it and a $bar'; %user_defs = (   foo  => 23,   bar  => 19, ); $text =~ ...
Don't do that </li></ul>
Templating Options <ul><li>Dozens of template modules on CPAN
Text::Template, HTML::Template, Mason, Template Toolkit
Many, many more
Questions to consider </li><ul><li>HTML only?
Template language </li></ul></ul>
Template Toolkit <ul><li>http://tt2.org/
Very powerful
Both web and non-web
Simple template language
Plugins give access to much of CPAN
Can use Perl code if you want </li><ul><li>But don't do that </li></ul></ul>
Good Book Too!
The Template Equation <ul><li>Data + Template = Output
Data + Alternative Template = Alternative Output
Different views of the same data
Only the template changes </li></ul>
Simple TT Example <ul><li>use Template; use My::Object; my ($id, $format) = @ARGV; $format ||= 'html'; my $obj = My::Objec...
html.tt <ul><li><html>   <head>   <title>[% obj.name %]</title>   </head>   <body>   <h1>[% obj.name %]<h1>   <p><img src=...
text.tt <ul><li>[% obj.name | upper %] Image: [% obj.img %] [% obj.desc %] [% FOREACH child IN obj.children -%]   * [% chi...
Adding New Formats <ul><li>No new code required
Just add new output template
Perl programmer need not be involved </li></ul>
Equation Revisited <ul><li>Data + Template = Output </li><ul><li>Template Toolkit </li></ul><li>Template + Output = Data <...
DateTime
Dates & Times <ul><li>Perl has built-in functions to handle dates and times
time  – seconds since 1st Jan 1970
localtime  – convert to human-readable
timelocal  (in Time::Local) – inverse of localtime
strftime  (in POSIX) – formatting dates and times </li></ul>
Dates & Times on CPAN <ul><li>Look to CPAN for a better answer
Dozens of date/time modules on CPAN
Date::Manip is almost never what you want
Date::Calc, Date::Parse, Class::Date, Date::Simple, etc
Which one do you choose? </li></ul>
Perl DateTime Project <ul><li>http://datetime.perl.org/
&quot;The DateTime family of modules present a unified way to handle dates and times in Perl&quot;
&quot;unified&quot; is good
Dozens of modules that work together in a consistent fashion </li></ul>
Using DateTime <ul><li>use DateTime; my $dt = DateTime->now; say $dt; # 2010-08-02T10:06:07 say $dt->dmy; # 2010-08-02 say...
Using DateTime <ul><li>use DateTime; my $dt = DateTime->new(year  => 2010,   month => 8,   day  => 2); say $dt->ymd('/'); ...
Arithmetic <ul><li>A DateTime object is a point in time
For date arithmetic you need a duration
Number of years, weeks, days, etc </li></ul>
Arithmetic <ul><li>use DateTime; my $dt = DateTime->new(year => 2010,   month => 8,   day => 2); my $two_weeks = DateTime:...
Formatting Output <ul><li>use DateTime; my $dt = DateTime->new(year => 2010,   month => 4,   day => 14); say $dt->strftime...
strftime uses UNIX standards
Control input format with DateTime::Format::Strptime </li></ul>
Parsing & Formatting <ul><li>Ready made parsers and formatters for popular date and time formats
DateTime::Format::HTTP
DateTime::Format::MySQL
DateTime::Format::Excel
DateTime::Format::Baby </li><ul><li>the big hand is on... </li></ul></ul>
Alternative Calendars <ul><li>Handling non-standard calendars
DateTime::Calendar::Julian
DateTime::Calendar::Hebrew
DateTime::Calendar::Mayan
DateTime::Fiction::JRRTolkien::Shire </li></ul>
Calendar Examples <ul><li>use DateTime::Calendar::Mayan; my $dt = DateTime::Calendar::Mayan->now; say $dt->date; # 12.19.1...
use DateTime::Fiction::JRRTolkien::Shire; my $dt =    DateTime::Fiction::JRRTolkien::Shire->now; say $dt->on_date; # Trews...
DBIx::Class
Object Relational Mapping <ul><li>Mapping database relations into objects
Tables (relations) map onto classes
Rows (tuples) map onto objects
Columns (attributes) map onto attributes
Don't write SQL </li></ul>
SQL Is Tedious <ul><li>Select the id and name from this table
Select all the details of this row
Select something about related tables
Update this row with these values
Insert a new record with these values
Delete this record </li></ul>
Replacing SQL <ul><li>Instead of
SELECT * FROM  my_table WHERE  my_id = 10
and then dealing with the prepare/execute/fetch code </li></ul>
Replacing SQL <ul><li>We can write
use My::Object; # warning! not a real orm my $obj = My::Object->retrieve(10)
Or something similar </li></ul>
Writing An ORM Layer <ul><li>Not actually that hard to do yourself
Each class needs an associated table
Each class needs a list of columns
Create simple SQL for basic CRUD operations
Don't do that </li></ul>
Perl ORM Options <ul><li>Plenty of choices on CPAN
Class::DBI
Rose::DB::Object
ORLite
DBIx::Class </li><ul><li>The current favourite </li></ul><li>Fey::ORM </li></ul>
DBIx::Class <ul><li>Standing on the shoulders of giants
Learning from problems in Class::DBI
More flexible
More powerful </li></ul>
DBIx::Class Example <ul><li>Modeling a CD collection
Three tables
artist (artistid, name)
cd (cdid, artist, title)
track (trackid, cd, title) </li></ul>
Main Schema <ul><li>Define main schema class
Music/DB.pm </li></ul><ul><li>package Music::DB; use base qw/DBIx::Class::Schema/; __PACKAGE__->load_classes(); 1; </li></ul>
Object Classes - Artist <ul><li>Music/DB/Artist.pm </li></ul><ul><li>package Music::DB::Artist; use base qw/DBIx::Class/; ...
Object Classes- CD <ul><li>Music/DB/CD.pm </li></ul><ul><li>package Music::DB::CD; use base qw/DBIx::Class/; __PACKAGE__->...
Inserting Artists <ul><li>my $schema =   Music::DB->connect($dbi_str); my @artists = ('Florence + The Machine',   'Belle a...
Inserting CDs <ul><li>Hash of Artists and CDs </li></ul><ul><li>my %cds = (   'Lungs' =>   'Florence + The Machine',   'Th...
Inserting CDs <ul><li>Find each artist and insert CD </li></ul><ul><li>foreach (keys $cds) {   my ($artist) = $art_rs->sea...
Retrieving Data <ul><li>Get CDs by artist </li></ul><ul><li>my $name = 'Belle and Sebastian'; my ($artist) = $art_rs->sear...
Searching for Data <ul><li>Search conditions can be more complex
Alternatives </li></ul><ul><ul><li>$rs->search({year => 2006},   {year => 2007}); </li></ul></ul><ul><li>Like </li><ul><li...
Searching for Data <ul><li>Combinations </li></ul><ul><ul><li>$rs->search({forename =>   { 'like', 'Dav%' },   surname => ...
Don't Repeat Yourself <ul><li>There's a problem with this approach
Information is repeated
Columns and relationships defined in the database schema
Columns and relationships defined in class definitions </li></ul>
Repeated Information <ul><li>CREATE TABLE artist (   artistid INTEGER PRIMARY KEY,   name  TEXT NOT NULL  ); </li></ul>
Repeated Information <ul><li>package Music::DB::Artist; use base qw/DBIx::Class/; __PACKAGE__->load_components(qw/Core/); ...
Database Metadata <ul><li>Some people don't put enough metadata in their databases
Just tables and columns
No relationships. No constraints
You may as well make each column VARCHAR(255) </li></ul>
Database Metadata <ul><li>Describe your data in your database
It's what your database is for
It's what your database does best </li></ul>
No Metadata (Excuse 1) <ul><li>&quot;This is the only application that will ever access this database&quot;
Nonsense
All data will be shared eventually
Upcoming SlideShare
Loading in...5
×

Introducing Modern Perl

14,500

Published on

Published in: Technology
4 Comments
16 Likes
Statistics
Notes
  • perl tt
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • @avoronov: You're absolutely right, of course. Thanks for the correction.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • slide 47: maybe 'foreach (keys $cds) { ' should be 'foreach (keys %cds) {'?
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Congratulations !!! This is an excellent work.Thank you for sharing. We have selected your presentation for the reference in our group Slideshare 'BANK OF KNOWLEDGE'. We would be honored by your support through your membership. You are invited to join us ! I wish you a nice day. Greetings from France. Nadine

    http://www.slideshare.net/group/bank-of-knowledge
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
14,500
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
290
Comments
4
Likes
16
Embeds 0
No embeds

No notes for slide

Introducing Modern Perl

  1. 1. Introducing Modern Perl Dave Cross Magnum Solutions Ltd [email_address]
  2. 2. Modern Perl <ul><li>Perl has been around for a long time
  3. 3. Achieved great popularity in the 1990s
  4. 4. “The duct tape of the internet”
  5. 5. Perl has moved on a lot since then
  6. 6. Many people's perceptions of Perl haven't </li></ul>
  7. 7. Modern Perl <ul><li>Keeping up to date with Perl
  8. 8. Surveying modern modules and techniques
  9. 9. A lot to cover
  10. 10. Only an overview
  11. 11. Plenty of pointers to more information </li></ul>
  12. 12. What We Will Cover <ul><li>Template Toolkit
  13. 13. DateTime
  14. 14. DBIx::Class
  15. 15. TryCatch </li></ul>
  16. 16. What We Will Cover <ul><li>Moose
  17. 17. autodie
  18. 18. Catalyst
  19. 19. PSGI/Plack </li></ul>
  20. 20. Schedule <ul><li>09:00 – Begin
  21. 21. 11:00 – Coffee break
  22. 22. 13:00 – Lunch
  23. 23. 14:00 – Begin
  24. 24. 16:00 – Coffee break
  25. 25. 17:00 – End </li></ul>
  26. 26. Resources <ul><li>Slides available on-line </li><ul><li>http://mag-sol.com/train/yapc/2010 </li></ul><li>Also see Slideshare </li><ul><li>http://www.slideshare.net/davorg/slideshows </li></ul><li>Get Satisfaction </li><ul><li>http://getsatisfaction.com/magnum </li></ul></ul>
  27. 27. Template Toolkit
  28. 28. Templates <ul><li>Many people use templates to produce web pages
  29. 29. Advantages are well known
  30. 30. Standard look and feel (static/dynamic)
  31. 31. Reusable components
  32. 32. Separation of code logic from display logic
  33. 33. Different skill-sets (HTML vs Perl) </li></ul>
  34. 34. DIY Templating <ul><li>Must be easy - so many people do it
  35. 35. See perlfaq4
  36. 36. “How can I expand variables in text strings?” </li></ul>
  37. 37. DIY Templating <ul><li>$text = 'this has a $foo in it and a $bar'; %user_defs = ( foo => 23, bar => 19, ); $text =~ s/$(w+)/$user_defs{$1}/g;
  38. 38. Don't do that </li></ul>
  39. 39. Templating Options <ul><li>Dozens of template modules on CPAN
  40. 40. Text::Template, HTML::Template, Mason, Template Toolkit
  41. 41. Many, many more
  42. 42. Questions to consider </li><ul><li>HTML only?
  43. 43. Template language </li></ul></ul>
  44. 44. Template Toolkit <ul><li>http://tt2.org/
  45. 45. Very powerful
  46. 46. Both web and non-web
  47. 47. Simple template language
  48. 48. Plugins give access to much of CPAN
  49. 49. Can use Perl code if you want </li><ul><li>But don't do that </li></ul></ul>
  50. 50. Good Book Too!
  51. 51. The Template Equation <ul><li>Data + Template = Output
  52. 52. Data + Alternative Template = Alternative Output
  53. 53. Different views of the same data
  54. 54. Only the template changes </li></ul>
  55. 55. Simple TT Example <ul><li>use Template; use My::Object; my ($id, $format) = @ARGV; $format ||= 'html'; my $obj = My::Object->new($id) or die; my $tt = Template->new; $tt->process(&quot;$format.tt&quot;, { obj => $obj }, &quot;$id.$format&quot;) or die $tt->error; </li></ul>
  56. 56. html.tt <ul><li><html> <head> <title>[% obj.name %]</title> </head> <body> <h1>[% obj.name %]<h1> <p><img src=“[% obj.img %]” /><br /> [% obj.desc %]</p> <ul> [% FOREACH child IN obj.children -%] <li>[% child.name %]</li> [% END %] </body> </html> </li></ul>
  57. 57. text.tt <ul><li>[% obj.name | upper %] Image: [% obj.img %] [% obj.desc %] [% FOREACH child IN obj.children -%] * [% child.name %] [% END %] </li></ul>
  58. 58. Adding New Formats <ul><li>No new code required
  59. 59. Just add new output template
  60. 60. Perl programmer need not be involved </li></ul>
  61. 61. Equation Revisited <ul><li>Data + Template = Output </li><ul><li>Template Toolkit </li></ul><li>Template + Output = Data </li><ul><li>Template::Extract </li></ul><li>Data + Output = Template </li><ul><li>Template::Generate </li></ul></ul>
  62. 62. DateTime
  63. 63. Dates & Times <ul><li>Perl has built-in functions to handle dates and times
  64. 64. time – seconds since 1st Jan 1970
  65. 65. localtime – convert to human-readable
  66. 66. timelocal (in Time::Local) – inverse of localtime
  67. 67. strftime (in POSIX) – formatting dates and times </li></ul>
  68. 68. Dates & Times on CPAN <ul><li>Look to CPAN for a better answer
  69. 69. Dozens of date/time modules on CPAN
  70. 70. Date::Manip is almost never what you want
  71. 71. Date::Calc, Date::Parse, Class::Date, Date::Simple, etc
  72. 72. Which one do you choose? </li></ul>
  73. 73. Perl DateTime Project <ul><li>http://datetime.perl.org/
  74. 74. &quot;The DateTime family of modules present a unified way to handle dates and times in Perl&quot;
  75. 75. &quot;unified&quot; is good
  76. 76. Dozens of modules that work together in a consistent fashion </li></ul>
  77. 77. Using DateTime <ul><li>use DateTime; my $dt = DateTime->now; say $dt; # 2010-08-02T10:06:07 say $dt->dmy; # 2010-08-02 say $dt->hms; # 10:06:07 </li></ul>
  78. 78. Using DateTime <ul><li>use DateTime; my $dt = DateTime->new(year => 2010, month => 8, day => 2); say $dt->ymd('/'); # 2010/08/02 say $dt->month; # 8 say $dt->month_name; # August </li></ul>
  79. 79. Arithmetic <ul><li>A DateTime object is a point in time
  80. 80. For date arithmetic you need a duration
  81. 81. Number of years, weeks, days, etc </li></ul>
  82. 82. Arithmetic <ul><li>use DateTime; my $dt = DateTime->new(year => 2010, month => 8, day => 2); my $two_weeks = DateTime::Duration->new(weeks => 2); $dt += $two_weeks; say $dt; # 2010-08-16T00:00:00 </li></ul>
  83. 83. Formatting Output <ul><li>use DateTime; my $dt = DateTime->new(year => 2010, month => 4, day => 14); say $dt->strftime('%A, %d %B %Y'); # Wednesday, 14 April 2010
  84. 84. strftime uses UNIX standards
  85. 85. Control input format with DateTime::Format::Strptime </li></ul>
  86. 86. Parsing & Formatting <ul><li>Ready made parsers and formatters for popular date and time formats
  87. 87. DateTime::Format::HTTP
  88. 88. DateTime::Format::MySQL
  89. 89. DateTime::Format::Excel
  90. 90. DateTime::Format::Baby </li><ul><li>the big hand is on... </li></ul></ul>
  91. 91. Alternative Calendars <ul><li>Handling non-standard calendars
  92. 92. DateTime::Calendar::Julian
  93. 93. DateTime::Calendar::Hebrew
  94. 94. DateTime::Calendar::Mayan
  95. 95. DateTime::Fiction::JRRTolkien::Shire </li></ul>
  96. 96. Calendar Examples <ul><li>use DateTime::Calendar::Mayan; my $dt = DateTime::Calendar::Mayan->now; say $dt->date; # 12.19.17.9.13
  97. 97. use DateTime::Fiction::JRRTolkien::Shire; my $dt = DateTime::Fiction::JRRTolkien::Shire->now; say $dt->on_date; # Trewsday 14 Afterlithe 7474 </li></ul>
  98. 98. DBIx::Class
  99. 99. Object Relational Mapping <ul><li>Mapping database relations into objects
  100. 100. Tables (relations) map onto classes
  101. 101. Rows (tuples) map onto objects
  102. 102. Columns (attributes) map onto attributes
  103. 103. Don't write SQL </li></ul>
  104. 104. SQL Is Tedious <ul><li>Select the id and name from this table
  105. 105. Select all the details of this row
  106. 106. Select something about related tables
  107. 107. Update this row with these values
  108. 108. Insert a new record with these values
  109. 109. Delete this record </li></ul>
  110. 110. Replacing SQL <ul><li>Instead of
  111. 111. SELECT * FROM my_table WHERE my_id = 10
  112. 112. and then dealing with the prepare/execute/fetch code </li></ul>
  113. 113. Replacing SQL <ul><li>We can write
  114. 114. use My::Object; # warning! not a real orm my $obj = My::Object->retrieve(10)
  115. 115. Or something similar </li></ul>
  116. 116. Writing An ORM Layer <ul><li>Not actually that hard to do yourself
  117. 117. Each class needs an associated table
  118. 118. Each class needs a list of columns
  119. 119. Create simple SQL for basic CRUD operations
  120. 120. Don't do that </li></ul>
  121. 121. Perl ORM Options <ul><li>Plenty of choices on CPAN
  122. 122. Class::DBI
  123. 123. Rose::DB::Object
  124. 124. ORLite
  125. 125. DBIx::Class </li><ul><li>The current favourite </li></ul><li>Fey::ORM </li></ul>
  126. 126. DBIx::Class <ul><li>Standing on the shoulders of giants
  127. 127. Learning from problems in Class::DBI
  128. 128. More flexible
  129. 129. More powerful </li></ul>
  130. 130. DBIx::Class Example <ul><li>Modeling a CD collection
  131. 131. Three tables
  132. 132. artist (artistid, name)
  133. 133. cd (cdid, artist, title)
  134. 134. track (trackid, cd, title) </li></ul>
  135. 135. Main Schema <ul><li>Define main schema class
  136. 136. Music/DB.pm </li></ul><ul><li>package Music::DB; use base qw/DBIx::Class::Schema/; __PACKAGE__->load_classes(); 1; </li></ul>
  137. 137. Object Classes - Artist <ul><li>Music/DB/Artist.pm </li></ul><ul><li>package Music::DB::Artist; use base qw/DBIx::Class/; __PACKAGE__->load_components(qw/Core/); __PACKAGE__->table('artist'); __PACKAGE__->add_columns(qw/ artistid name /); __PACKAGE__->set_primary_key('artistid'); __PACKAGE__->has_many(cds => 'Music::DB::Cd'); 1; </li></ul>
  138. 138. Object Classes- CD <ul><li>Music/DB/CD.pm </li></ul><ul><li>package Music::DB::CD; use base qw/DBIx::Class/; __PACKAGE__->load_components(qw/Core/); __PACKAGE__->table('cd'); __PACKAGE__->add_columns(qw/ cdid artist title year /); __PACKAGE__->set_primary_key('cdid'); __PACKAGE__->belongs_to( artist => 'Music::DB:Artist'); 1; </li></ul>
  139. 139. Inserting Artists <ul><li>my $schema = Music::DB->connect($dbi_str); my @artists = ('Florence + The Machine', 'Belle and Sebastian'); my $art_rs = $schema->resultset('Artist'); foreach (@artists) { $art_rs->create({ name => $_ }); } </li></ul>
  140. 140. Inserting CDs <ul><li>Hash of Artists and CDs </li></ul><ul><li>my %cds = ( 'Lungs' => 'Florence + The Machine', 'The Boy With The Arab Strap' => 'Belle and Sebastian', 'Dear Catastrophe Waitress' => 'Belle and Sebastian', ); </li></ul>
  141. 141. Inserting CDs <ul><li>Find each artist and insert CD </li></ul><ul><li>foreach (keys $cds) { my ($artist) = $art_rs->search( { name => $cds{$_} } ); $artist->add_to_cds({ title => $_, }); } </li></ul>
  142. 142. Retrieving Data <ul><li>Get CDs by artist </li></ul><ul><li>my $name = 'Belle and Sebastian'; my ($artist) = $art_rs->search({ name => $name, }); foreach ($artist->cds) { say $_->title; } </li></ul>
  143. 143. Searching for Data <ul><li>Search conditions can be more complex
  144. 144. Alternatives </li></ul><ul><ul><li>$rs->search({year => 2006}, {year => 2007}); </li></ul></ul><ul><li>Like </li><ul><li>$rs->search({name => { 'like', 'Dav%' }}); </li></ul></ul>
  145. 145. Searching for Data <ul><li>Combinations </li></ul><ul><ul><li>$rs->search({forename => { 'like', 'Dav%' }, surname => 'Cross' }); </li></ul></ul>
  146. 146. Don't Repeat Yourself <ul><li>There's a problem with this approach
  147. 147. Information is repeated
  148. 148. Columns and relationships defined in the database schema
  149. 149. Columns and relationships defined in class definitions </li></ul>
  150. 150. Repeated Information <ul><li>CREATE TABLE artist ( artistid INTEGER PRIMARY KEY, name TEXT NOT NULL ); </li></ul>
  151. 151. Repeated Information <ul><li>package Music::DB::Artist; use base qw/DBIx::Class/; __PACKAGE__->load_components(qw/Core/); __PACKAGE__->table('artist'); __PACKAGE__->add_columns(qw/ artistid name /); __PACKAGE__->set_primary_key('artistid'); __PACKAGE__->has_many(cds => 'Music::DB::Cd'); 1; </li></ul>
  152. 152. Database Metadata <ul><li>Some people don't put enough metadata in their databases
  153. 153. Just tables and columns
  154. 154. No relationships. No constraints
  155. 155. You may as well make each column VARCHAR(255) </li></ul>
  156. 156. Database Metadata <ul><li>Describe your data in your database
  157. 157. It's what your database is for
  158. 158. It's what your database does best </li></ul>
  159. 159. No Metadata (Excuse 1) <ul><li>&quot;This is the only application that will ever access this database&quot;
  160. 160. Nonsense
  161. 161. All data will be shared eventually
  162. 162. People will update your database using other applications
  163. 163. Can you guarantee that someone won't use mysql to update your database? </li></ul>
  164. 164. No Metadata (Excuse 2) <ul><li>&quot;Database doesn't support those features&quot;
  165. 165. Nonsense
  166. 166. MySQL 3.x is not a database </li><ul><li>It's a set of data files with a vaguely SQL-like query syntax </li></ul><li>MySQL 4.x is a lot better
  167. 167. MySQL 5.x is most of the way there
  168. 168. Don't be constrained by using inferior tools </li></ul>
  169. 169. DBIC::Schema::Loader <ul><li>Creates classes by querying your database metadata
  170. 170. No more repeated data
  171. 171. We are now DRY
  172. 172. Schema definitions in one place
  173. 173. But...
  174. 174. Performance problems </li></ul>
  175. 175. Performance Problems <ul><li>You don't really want to generate all your class definitions each time your program is run
  176. 176. Need to generate the classes in advance
  177. 177. dump_to_dir method
  178. 178. Regenerate classes each time schema changes </li></ul>
  179. 179. Alternative Approach <ul><li>Need one canonical definition of the data tables
  180. 180. Doesn't need to be SQL DDL
  181. 181. Could be Perl code
  182. 182. Generate DDL from DBIx::Class definitions </li><ul><li>Harder approach
  183. 183. Might need to generate ALTER TABLE </li></ul></ul>
  184. 184. Conclusions <ul><li>ORM is a bridge between relational objects and program objects
  185. 185. Avoid writing SQL in common cases
  186. 186. DBIx::Class is the currently fashionable module
  187. 187. Caveat: ORM may be overkill for simple programs </li></ul>
  188. 188. More Information <ul><li>Manual pages (on CPAN)
  189. 189. DBIx::Class
  190. 190. DBIx::Class::Manual::*
  191. 191. DBIx::Class::Schema::Loader
  192. 192. Mailing list (Google for it) </li></ul>
  193. 193. TryCatch
  194. 194. Error Handling <ul><li>How do you handle errors in your code?
  195. 195. Return error values from subroutines
  196. 196. sub get_object { my ($class, $id) = @_; if (my $obj = find_obj_in_db($id)) { return $obj; } else { return; } } </li></ul>
  197. 197. Problems <ul><li>What if someone doesn't check return code?
  198. 198. my $obj = MyClass->get_object(100); print $obj->name; # error
  199. 199. Caller assumes that $obj is a valid object
  200. 200. Bad things follow </li></ul>
  201. 201. Basic Exceptions <ul><li>Throw an exception instead
  202. 202. sub get_object { my ($class, $id) = @_; if (my $obj = find_obj_in_db($id)) { return $obj; } else { die “No object found with id: $id”; } }
  203. 203. Now caller has to deal with exceptions </li></ul>
  204. 204. Dealing with Exceptions <ul><li>Use eval to catch exceptions
  205. 205. my $obj = eval { MyClass->get_object(100) }; if ($@) { # handle exception... } else { print $obj->name; # error } </li></ul>
  206. 206. Exceptions as Objects <ul><li>$@ can be set to an object
  207. 207. sub get_object { my ($class, $id) = @_; if (my $obj = find_obj_in_db($id)) { return $obj; } else { die MyException->new( type => 'obj_not_found', id => $id, ); } } </li></ul>
  208. 208. Try Catch <ul><li>TryCatch adds “syntactic sugar”
  209. 209. try { ... } catch ($e) { ... }
  210. 210. Looks a lot like many other languages </li></ul>
  211. 211. TryCatch with Scalar <ul><li>try { some_function_that_might_die(); } catch ($e) { if ($e =~ /some error/) { # handle error } else { die $e; } } </li></ul>
  212. 212. TryCatch with Object <ul><li>try { some_function_that_might_die(); } catch ($e) { if ($e->type eq 'file') { # handle error } else { die $e; } } </li></ul>
  213. 213. Better Checks <ul><li>try { some_function_that_might_die(); } catch (My::Error $e) { # handle error } </li></ul>
  214. 214. Even Better Checks <ul><li>try { some_function_that_might_die(); } catch (HTTP::Error $e where { $e->code == 404 }) { # handle 404 error } </li></ul>
  215. 215. Multiple Checks <ul><li>try { some_function_that_might_die(); } catch (HTTP::Error $e where { $e->code == 404 }) { # handle 404 error } catch (HTTP::Error $e where { $e->code == 500 }) { # handle 500 error } catch (HTTP::Error $e) { # handle other HTTP error } catch ($e) { # handle other error } </li></ul>
  216. 216. More Information <ul><li>perldoc TryCatch
  217. 217. See also Try::Tiny </li></ul>
  218. 218. Moose
  219. 219. Moose <ul><li>A complete modern object system for Perl 5
  220. 220. Based on Perl 6 object model
  221. 221. Built on top of Class::MOP </li><ul><li>MOP - Meta Object Protocol
  222. 222. Set of abstractions for components of an object system
  223. 223. Classes, Objects, Methods, Attributes </li></ul><li>An example might help </li></ul>
  224. 224. Moose Example <ul><li>package Point; use Moose; has 'x' => (isa => 'Int', is => 'ro'); has 'y' => (isa => 'Int', is => 'rw'); sub clear { my $self = shift; $self->{x} = 0; $self->y(0); } </li></ul>
  225. 225. Understanding Moose <ul><li>There's a lot going on here
  226. 226. use Moose </li><ul><li>Loads Moose environment
  227. 227. Makes our class a subclass of Moose::Object
  228. 228. Turns on strict and warnings </li></ul></ul>
  229. 229. Creating Attributes <ul><li>has 'x' => (isa => 'Int', is => 'ro') </li><ul><li>Creates an attribute called 'x'
  230. 230. Constrainted to be an integer
  231. 231. Read-only accessor </li></ul><li>has 'y' => (isa => 'Int', is => 'rw') </li></ul>
  232. 232. Defining Methods <ul><li>sub clear { my $self = shift; $self->{x} = 0; $self->y(0); } </li></ul><ul><li>Standard method syntax
  233. 233. Uses generated method to set y
  234. 234. Direct hash access for x </li></ul>
  235. 235. Subclassing <ul><li>package Point3D; use Moose; extends 'Point'; has 'z' => (isa => 'Int'); after 'clear' => sub { my $self = shift; $self->{z} = 0; }; </li></ul>
  236. 236. Subclasses <ul><li>extends 'Point' </li><ul><li>Similar to use base
  237. 237. Overwrites @ISA instead of appending </li></ul><li>has 'z' => (isa = 'Int') </li><ul><li>Adds new attribute 'z'
  238. 238. No accessor function - private attribute </li></ul></ul>
  239. 239. Extending Methods <ul><li>after 'clear' => sub { my $self = shift; $self->{z} = 0; };
  240. 240. New clear method for subclass
  241. 241. Called after method for superclass
  242. 242. Cleaner than $self->SUPER::clear() </li></ul>
  243. 243. Creating Objects <ul><li>Moose classes are used just like any other Perl class
  244. 244. $point = Point->new(x => 1, y => 2);
  245. 245. $p3d = Point3D->new(x => 1, y => 2, z => 3); </li></ul>
  246. 246. More About Attributes <ul><li>Use the has keyword to define your class's attributes
  247. 247. has 'first_name' => ( is => 'rw' );
  248. 248. Use is to define rw or ro
  249. 249. Omitting is gives an attribute with no accessors </li></ul>
  250. 250. Getting & Setting <ul><li>By default each attribute creates a method of the same name.
  251. 251. Used for both getting and setting the attribute
  252. 252. $dave->first_name('Dave');
  253. 253. say $dave->first_name; </li></ul>
  254. 254. Change Accessor Name <ul><li>Change accessor names using reader and writer
  255. 255. has 'name' => ( is => 'rw', reader => 'get_name', writer => 'set_name', );
  256. 256. See also MooseX::FollowPBP </li></ul>
  257. 257. Required Attributes <ul><li>Moose class attributes are optional
  258. 258. Change this with required
  259. 259. has 'name' => ( is => 'ro', required => 1, );
  260. 260. Forces constructor to expect a name
  261. 261. Although that name could be undef </li></ul>
  262. 262. Attribute Defaults <ul><li>Set a default value for an attribute with default
  263. 263. has 'size' => ( is => 'rw', default => 'medium', );
  264. 264. Can use a subroutine reference
  265. 265. has 'size' => ( is => 'rw', default => &rand_size, ); </li></ul>
  266. 266. Attribute Properties <ul><li>lazy </li><ul><li>Only populate attribute when queried </li></ul><li>trigger </li><ul><li>Subroutine called after the attribute is set </li></ul><li>isa </li><ul><li>Set the type of an attribute </li></ul><li>Many more </li></ul>
  267. 267. More Moose <ul><li>Many more options
  268. 268. Support for concepts like delegation and roles
  269. 269. Powerful plugin support </li><ul><li>MooseX::* </li></ul><li>Lots of work going on in this area </li></ul>
  270. 270. autodie
  271. 271. More on Exceptions <ul><li>Exceptions force callers to deal with error conditions
  272. 272. But you have to explicitly code to throw exceptions
  273. 273. open my $fh, '<', 'somefile.txt' or die $!;
  274. 274. What if you forget to check the return value? </li></ul>
  275. 275. Not Checking Errors <ul><li>open my $fh, '<', 'somefile.txt'; while (<$fh>) { # do something useful }
  276. 276. If the 'open' fails, you can't read any data
  277. 277. You might not even get any warnings </li></ul>
  278. 278. Automatic Exceptions <ul><li>use Fatal qw(open);
  279. 279. Comes with Perl since 5.003
  280. 280. Errors in built-in functions become fatal errors
  281. 281. This solves our previous problem
  282. 282. open my $fh, '<', 'somefile.txt'; </li></ul>
  283. 283. However <ul><li>Fatal.pm has its own problems
  284. 284. Unintelligent check for success or failure
  285. 285. Checks return value for true/false
  286. 286. Assumes false is failure
  287. 287. Can't be used if function can legitimately return a false value </li><ul><li>e.g. fork </li></ul></ul>
  288. 288. Also <ul><li>Nasty error messages
  289. 289. $ perl -MFatal=open -E'open my $fh, &quot;notthere&quot;' Can't open(GLOB(0x86357a4), notthere): No such file or directory at (eval 1) line 4 main::__ANON__('GLOB(0x86357a4)', 'notthere') called at -e line 1 </li></ul>
  290. 290. Enter autodie <ul><li>autodie is a cleverer Fatal
  291. 291. In Perl core since 5.10.1
  292. 292. Fatal needed a list of built-ins
  293. 293. autodie assumes all built-ins </li><ul><li>use autodie; </li></ul></ul>
  294. 294. Turning autodie on/off <ul><li>Lexically scoped </li><ul><li>use autodie;
  295. 295. no autodie; </li></ul><li>Turn off for specific built-ins </li><ul><li>no autodie 'open'; </li></ul></ul>
  296. 296. Failing Calls <ul><li>autodie has more intelligence about failing calls
  297. 297. Not just a boolean check
  298. 298. Understands fork, system, etc </li></ul>
  299. 299. Errors are Objects <ul><li>Errors thrown by autodie are objects
  300. 300. Can be inspected for details of the error
  301. 301. try { open my $fh, '<', 'not-there'; } catch ($e) { warn 'Error opening ', $e->args->[-1], &quot; &quot;; warn 'File: ', $e->file, &quot; &quot;; warn 'Function: ', $e->function, &quot; &quot;; warn 'Package: ', $e->package, &quot; &quot;; warn 'Caller: ', $e->caller, &quot; &quot;; warn 'Line: ', $e->line, &quot; &quot;; } </li></ul>
  302. 302. Nicer Errors Too <ul><li>$ perl -Mautodie -E'open my $fh, &quot;not-there&quot;' Can't open($fh, 'not-there'): No such file or directory at -e line 1 </li></ul>
  303. 303. More Information <ul><li>perldoc Fatal
  304. 304. perldoc autodie
  305. 305. autodie - The art of Klingon Programming </li><ul><li>http://perltraining.com.au/tips/2008-08-20.html </li></ul></ul>
  306. 306. Catalyst
  307. 307. MVC Frameworks <ul><li>MVC frameworks are a popular way to write applications </li><ul><li>Particularly web applications </li></ul></ul>
  308. 308. M, V and C <ul><li>Model </li><ul><li>Data storage & data access </li></ul><li>View </li><ul><li>Data presentation layer </li></ul><li>Controller </li><ul><li>Business logic to glue it all together </li></ul></ul>
  309. 309. MVC Examples <ul><li>Ruby on Rails
  310. 310. Django (Python)
  311. 311. Struts (Java)
  312. 312. CakePHP
  313. 313. Many examples in most languages
  314. 314. Perl has many options </li></ul>
  315. 315. MVC in Perl <ul><li>Maypole </li><ul><li>The original Perl MVC framework </li></ul><li>CGI::Application </li><ul><li>Simple MVC for CGI programming </li></ul><li>Jifty </li><ul><li>Developed and used by Best Practical </li></ul><li>Catalyst </li><ul><li>Currently the popular choice </li></ul></ul>
  316. 316. Newer MVC in Perl <ul><li>Dancer
  317. 317. Squatting
  318. 318. Mojolicious </li></ul>
  319. 319. Catalyst <ul><li>MVC framework in Perl
  320. 320. Building on other heavily-used tools
  321. 321. Model uses DBIx::Class
  322. 322. View uses Template Toolkit
  323. 323. These are just defaults
  324. 324. Can use anything you want </li></ul>
  325. 325. Simple Catalyst App <ul><li>Assume we already have model </li><ul><li>CD database from DBIx::Class section </li></ul><li>Use catalyst.pl to create project
  326. 326. $ catalyst.pl CD created &quot;CD&quot; created &quot;CD/script&quot; created &quot;CD/lib&quot; created &quot;CD/root&quot; ... many more ... </li></ul>
  327. 327. What Just Happened? <ul><li>Catalyst just generated a lot of useful stuff for us
  328. 328. Test web servers </li><ul><li>Standalone and FastCGI </li></ul><li>Configuration files
  329. 329. Test stubs
  330. 330. Helpers for creating models, views and controllers </li></ul>
  331. 331. A Working Application <ul><li>We already have a working application
  332. 332. $ CD/script/cd_server.pl ... lots of output [info] CD powered by Catalyst 5.7015 You can connect to your server at http://localhost:3000
  333. 333. Of course, it doesn't do much yet </li></ul>
  334. 334. Simple Catalyst App
  335. 335. Next Steps <ul><li>Use various helper programs to create models and views for your application
  336. 336. Write controller code to tie it all together
  337. 337. Many plugins to handle various parts of the process </li><ul><li>Authentication
  338. 338. URL resolution
  339. 339. Session handling
  340. 340. etc... </li></ul></ul>
  341. 341. Create a View <ul><li>$ script/cd_create.pl view Default TT exists &quot;/home/dave/training/cdlib/CD/script/../lib/CD/View&quot; exists &quot;/home/dave/training/cdlib/CD/script/../t&quot; created &quot;/home/dave/training/cdlib/CD/script/../lib/CD/View/Default.pm&quot; created &quot;/home/dave/training/cdlib/CD/script/../t/view_Default.t&quot; </li></ul>
  342. 342. Remove Default Message <ul><li>In lib/CD/Controller/Root.pm
  343. 343. sub index :Path :Args(0) { my ( $self, $c ) = @_; # Hello World $c->response_body($c->welcome_message); }
  344. 344. Remove response_body line
  345. 345. Default behaviour is to render index.tt
  346. 346. Need to create that </li></ul>
  347. 347. index.tt <ul><li>root/index.tt </li></ul><ul><li><html> <head> <title>CDs</title> </head> <body> <ul> [% FOREACH cd IN [ 1 .. 10 ] %] <li>CD [% cd %]</li> [% END %] </ul> </body> </html> </li></ul>
  348. 348. New Front Page
  349. 349. Adding Data <ul><li>Of course that's hard-coded data
  350. 350. Need to add a model class
  351. 351. And then more views
  352. 352. And some controllers
  353. 353. There's a lot to do
  354. 354. I recommend working through a tutorial </li></ul>
  355. 355. Easier Catalyst <ul><li>A lot of web applications do similar things
  356. 356. Given a database
  357. 357. Produce screens to edit the data
  358. 358. Surely most of this can be automated
  359. 359. It's called Catalyst::Plugin::AutoCRUD
  360. 360. (Demo) </li></ul>
  361. 361. Cat::Plugin::AutoCRUD <ul><li>Does a lot of work
  362. 362. On the fly
  363. 363. For every request
  364. 364. No security on table updates
  365. 365. So it's not right for every project
  366. 366. Very impressive though </li></ul>
  367. 367. Conclusions <ul><li>There's a lot to bear in mind when writing a web app
  368. 368. Using the right framework can help
  369. 369. Catalyst is the most popular Perl framework
  370. 370. As powerful as any other framework </li><ul><li>In any language </li></ul><li>Lots of work still going on
  371. 371. Large team, active development </li></ul>
  372. 372. Recommended Book <ul><li>The Definitive Guide to Catalyst </li><ul><li>Kieren Diment
  373. 373. Matt S Trout </li></ul></ul>
  374. 374. PSGI/Plack
  375. 375. PSGI/Plack <ul><li>“ PSGI is an interface between Perl web applications and web servers, and Plack is a Perl module and toolkit that contains PSGI middleware, helpers and adapters to web servers.” </li><ul><li>http://plackperl.org/ </li></ul></ul>
  376. 376. PSGI/Plack <ul><li>PSGI is a specification (based on Python's WSGI)
  377. 377. Plack is a reference implementation (based on Ruby's Rack) </li></ul>
  378. 378. The Problem <ul><li>There are many ways to write web applications
  379. 379. There are many ways to write web applications in Perl
  380. 380. Each is subtly different
  381. 381. Hard to move an application between server architectures </li></ul>
  382. 382. Server Architectures <ul><li>CGI
  383. 383. FastCGI
  384. 384. mod_perl
  385. 385. etc... </li></ul>
  386. 386. Frameworks <ul><li>There are many Perl web application frameworks
  387. 387. Each creates applications in subtly different ways
  388. 388. Hard to port applications between them </li></ul>
  389. 389. The Goal <ul><li>What if we had a specification that allowed us to easily move web applications between server architectures and frameworks
  390. 390. PSGI is that specification </li></ul>
  391. 391. PSGI Application <ul><li>my $app = sub { my $env = shift; return [ 200, [ ‘Content-Type’, ‘text/plain’ ], [ ‘Hello World’ ], ]; }; </li></ul>
  392. 392. PSGI Application <ul><li>A code reference
  393. 393. Passed a reference to an environment hash
  394. 394. Returns a reference to a three-element array </li><ul><li>Status code
  395. 395. Headers
  396. 396. Body </li></ul></ul>
  397. 397. A Code Reference <ul><li>my $app = sub { my $env = shift; return [ 200, [ ‘Content-Type’, ‘text/plain’ ], [ ‘Hello World’ ], ]; }; </li></ul>
  398. 398. A Code Reference <ul><li>my $app = sub { my $env = shift; return [ 200, [ ‘Content-Type’, ‘text/plain’ ], [ ‘Hello World’ ], ]; }; </li></ul>
  399. 399. Environment Hash <ul><li>my $app = sub { my $env = shift; return [ 200, [ ‘Content-Type’, ‘text/plain’ ], [ ‘Hello World’ ], ]; }; </li></ul>
  400. 400. Environment Hash <ul><li>my $app = sub { my $env = shift; return [ 200, [ ‘Content-Type’, ‘text/plain’ ], [ ‘Hello World’ ], ]; }; </li></ul>
  401. 401. Return Array Ref <ul><li>my $app = sub { my $env = shift; return [ 200, [ ‘Content-Type’, ‘text/plain’ ], [ ‘Hello World’ ], ]; }; </li></ul>
  402. 402. Return Array Ref <ul><li>my $app = sub { my $env = shift; return [ 200, [ ‘Content-Type’, ‘text/plain’ ], [ ‘Hello World’ ], ]; }; </li></ul>
  403. 403. Running PSGI App <ul><li>Put code in app.psgi
  404. 404. Drop in into a configured PSGI-aware web server
  405. 405. Browse to URL </li></ul>
  406. 406. PSGI-Aware Server <ul><li>Plack contains a simple test server called plackup
  407. 407. $ plackup app.psgi HTTP::Server::PSGI: Accepting connections at http://localhost:5000/ </li></ul>
  408. 408. Plack::Request <ul><li>Plack::Request turns the environment into a request object
  409. 409. use Plack::Request; use Data::Dumper; my $app = sub { my $req = Plack::Request->new(shift); return [ 200, [ 'Content-type', 'text/plain' ], [ Dumper $req ], ]; } </li></ul>
  410. 410. Plack::Response <ul><li>Plack::Response builds a PSGI response object
  411. 411. use Plack::Request; use Plack::Response; use Data::Dumper; my $app = sub { my $req = Plack::Request->new(shift); my $res = Plack::Response->new(200); $res->content_type('text/plain'); $res->body(Dumper $req); return $res->finalize; } </li></ul>
  412. 412. Middleware <ul><li>Middleware wraps around an application
  413. 413. Returns another PSGI application
  414. 414. Simple spec makes this easy
  415. 415. Plack::Middleware::*
  416. 416. Plack::Builder adds middleware configuration language </li></ul>
  417. 417. Middleware Example <ul><li>use Plack::Builder; use Plack::Middleware::Runtime; my $app = sub { my $env = shift; return [ 200, [ 'Content-type', 'text/plain' ], [ 'Hello world' ], ] }; builder { enable 'Runtime'; $app; } </li></ul>
  418. 418. Middleware Example <ul><li>$ HEAD http://localhost:5000 200 OK Date: Tue, 20 Jul 2010 20:25:52 GMT Server: HTTP::Server::PSGI Content-Length: 11 Content-Type: text/plain Client-Date: Tue, 20 Jul 2010 20:25:52 GMT Client-Peer: 127.0.0.1:5000 Client-Response-Num: 1 X-Runtime: 0.000050 </li></ul>
  419. 419. Middleware Example <ul><li>$ HEAD http://localhost:5000 200 OK Date: Tue, 20 Jul 2010 20:25:52 GMT Server: HTTP::Server::PSGI Content-Length: 11 Content-Type: text/plain Client-Date: Tue, 20 Jul 2010 20:25:52 GMT Client-Peer: 127.0.0.1:5000 Client-Response-Num: 1 X-Runtime: 0.000050 </li></ul>
  420. 420. Plack::App::* <ul><li>Ready-made solutions for common situations
  421. 421. Plack::App::CGIBin </li><ul><li>Cgi-bin replacement </li></ul><li>Plack::App::Directory </li><ul><li>Serve files with directory index </li></ul><li>Plack::App::URLMap </li><ul><li>Map apps to different paths </li></ul></ul>
  422. 422. Plack::App::* <ul><li>Many more bundled with Plack
  423. 423. Configured using Plack::Builder </li></ul>
  424. 424. Plack::App::CGIBin <ul><li>use Plack::App::CGIBin; use Plack::Builder; my $app = Plack::App::CGIBin->new( root => '/var/www/cgi-bin' )->to_app; builder { mount '/cgi-bin' => $app; }; </li></ul>
  425. 425. Plack::App::Directory <ul><li>use Plack::App::Directory; my $app = Plack::App::Directory->new( root => '/home/dave/Dropbox/psgi' )->to_app; </li></ul>
  426. 426. Framework Support <ul><li>Many modern Perl applications already support PSGI
  427. 427. Catalyst, CGI::Application, Dancer, Jifty, Mason, Maypole, Mojolicious, Squatting, Web::Simple
  428. 428. Many more </li></ul>
  429. 429. Catalyst Support <ul><li>Catalyst::Engine::PSGI
  430. 430. use MyApp; MyApp->setup_engine('PSGI'); my $app = sub { MyApp->run(@_) };
  431. 431. Also Catalyst::Helper::PSGI
  432. 432. script/myapp_create.pl PSGI </li></ul>
  433. 433. PSGI Server Support <ul><li>Many new web servers support PSGI
  434. 434. Starman, Starlet, Twiggy, Corona, HTTP::Server::Simple::PSGI
  435. 435. Perlbal::Plugin::PSGI </li></ul>
  436. 436. PSGI Server Support <ul><li>nginx support
  437. 437. mod_psgi
  438. 438. Plack::Handler::Apache2 </li></ul>
  439. 439. Plack::Handler::Apache2 <ul><li><Location /psgi> SetHandler perl-script PerlResponseHandler Plack::Handler::Apache2 PerlSetVar psgi_app /path/to/app.psgi </Location> </li></ul>
  440. 440. PSGI/Plack Summary <ul><li>PSGI is a specification
  441. 441. Plack is an implementation
  442. 442. PSGI makes your life easier
  443. 443. Most of the frameworks and server you use already support PSGI
  444. 444. No excuse not to use it </li></ul>
  445. 445. Further Information <ul><li>perldoc PSGI
  446. 446. perldoc Plack
  447. 447. http://plackperl.org/
  448. 448. http://blog.plackperl.org/
  449. 449. http://github.com/miyagawa/Plack
  450. 450. #plack on irc.perl.org </li></ul>
  451. 451. Modern Perl Summary <ul><li>Perl has changed a lot in the last five years
  452. 452. Perl continues to grow and change
  453. 453. Perl is not a “scripting language”
  454. 454. Perl is a powerful and flexible programming language
  455. 455. Perl is a great way to get your job done </li></ul>
  456. 456. That's all folks <ul><li>Any questions? </li></ul>
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×