0
Practical Domain-Specific
 Languages with Groovy

All the techniques to create your own DSLs

          Guillaume Laforge
...
Guillaume Laforge

• Groovy Project Manager
• JSR-241 Spec Lead
• Head of Groovy Development
  at SpringSource
• Initiator...
A few words about Groovy

• Groovy is a dynamic language for the JVM
  – with a Meta Object Protocol
  – compiles directly...
nd a
Ag e
       • The context and
         the usual issues we face
       • Some real-life examples of
         Domain-S...
The context
Subject Matter Experts,
  Business analysts...
Developer producing
           LOLCODE



HAI
CAN HAS STDIO?
I HAS A VAR
IM IN YR LOOP
   UP VAR!!1
   VISIBLE VAR
   IZ V...
Lots of languages...
And in the end...
...nobody understands each other
Expressing requirements...




                             11
DSL: a potential solution?

• Use a more expressive language than a general purpose one

• Share a common metaphore of und...
Towards more readability (1)




                               13
Towards more readability (1)




                               13
Towards more readability (1)




                               20%




                               13
Towards more readability (2)




                               14
Towards more readability (2)




                               14
Towards more readability (2)




                          80%

                                14
nd a
Ag e
       • The context and
         the usual issues we face
       • Some real-life examples of
         Domain-S...
A collection of DSLs


• In our everyday life, we’re surrounded by DSLs



  – Technical dialects

  – Notations

  – Busi...
Technical dialects




                     17
SQL
^[w-.]+@([w-]){2,4}$




                          19
Notations




            20
1. e4 e5
2. Nf3 Nc6
3. Bb5 a6
L2 U F-1 B L2 F B -1 U L2
Visual!
Business languages




                     24
Real-life Groovy examples

• Anti-malaria drug resistance simulation
• Human Resources employee skills representation
• In...
nd a
Ag e
       • The context and
         the usual issues we face
       • Some real-life examples of
         Domain-S...
A flexible & malleable syntax

• No need to write full-blown classes, use scripts
• Optional typing (def)
  – in scripts, ...
Scripts vs classes

• Hide all the boilerplate technical code
  – an end-user doesn’t need to know about classes


  – pub...
Optional typing

• No need to bother with types or even generics
  – unless you want to!
• Imagine an interest rate lookup...
Native syntax constructs

• Lists
  – [Monday, Tuesday, Wednesday]

• Maps
  – [CA: ‘California’, TX: ‘Texas’]

• Ranges
 ...
Optional parens & semis

• Make statements and expressions
  look more like natural languages



  – move(left);

  – move...
Named arguments

• In Groovy you can mix named and unnamed
  arguments for method parameters
    – named params are actual...
BigDecimal by default

• Main reason why financial institutions often decide
  to use Groovy for their business rules!
  –...
Custom control structures
Thanks to closures

• When closures are last, they can be put “out” of
  the parentheses surroun...
Operator overloading

a + b    a.plus(b)
a - b    a.minus(b)          • Currency amounts
a * b    a.multiply(b)        – 1...
Groovy’s dynamic heart:

The MOP!
MetaObject Protocol
Groovy’s MOP

• All the accesses to methods, properties,
  constructors, operators, etc. can be intercepted
  thanks to th...
GroovyObject

• All instances of classes created in Groovy
  implement the GroovyObject interface:

  –getProperty(String ...
MetaClass

• The core of Groovy’s MOP system
  –invokeConstructor()
  –invokeMethod() and invokeStaticMethod()
  –invokeMi...
ExpandoMetaClass

• A DSL for MetaClasses!
• MoneyAmount.metaClass.constructor = { ... }
  Number.metaClass.getDollars = {...
The Builder pattern
The Groovy MarkupBuilder

• def mkp = new MarkupBuilder()
  mkp.html {
      head {
          title “Groovy in Action”
   ...
A builder for HR

• softskills {
      ideas {
          capture 2
          formulate 3
      }
      ...
  }
  knowhow {...
A builder for HR

• softskills {
      ideas {
          capture 2
          formulate 3
      }
      ...
  }
  knowhow {...
Builders

• Builders are...
  – a mechanism for creating any tree-structered graph
  – the realization of the GoF builder ...
The clever trick

• GroovyObject#invokeMethod() is used to catch all
  non-existing method calls in the context of the
  b...
Adding properties to numbers

• Three possible approaches

  – create a Category
    •a category is a kind of decorator fo...
With a Category

• class DistanceCategory {
      static Distance getMeters(Integer self) {
          new Distance(self, U...
With an ExpandoMetaClass

• Number.metaClass.getMeters = {->
      new Distance(delegate, Unit.METERS)
  }

 100.meters

•...
Compile-time metaprogramming

• Groovy 1.6 introduced AST Transformations
• Compile-time == No runtime performance penalty...
AST Transformations

• Two kinds of transformations


  – Global transformations
    •applicable to all compilation units
...
Example #1: @Singleton

• Let’s revisit this evil (anti-)pattern
   !   public class Evil {
           public static final...
Example #2: @Delegate
Not just for managers!

• You can delegate to fields of your classes
  – class Employee {
        de...
Global transformations

• Implement ASTTransformation
• Annotate the transfo specifying a compilation phase

• @GroovyASTT...
Local transformations

• Same approach as Globale transformations
• But you don’t need the META-INF file
• Instead create ...
Example: the Spock framework

• Changing the semantics of the original code
• But keeping a valid Groovy syntax
• @Speck
 ...
nd a
Ag e
       • The context and
         the usual issues we face
       • Some real-life examples of
         Domain-S...
Various integration mechanisms

• Java 6’s javax.script.* APIs (aka JSR-223)
• Spring’s language namespace
• Groovy’s own ...
Java 6’s javax.script.* API

• Groovy 1.6 provides its own implementation of the
  javax.script.* API

• ScriptEngineManag...
Spring’s lang namespace

• POGOs (Plain Old Groovy Objects) can be pre-
  compiled as any POJO and used interchangeably
  ...
Groovy’s own mechanisms

• Eval
  – for evaluating simple expressions


• GroovyShell
  – for more complex scripts and DSL...
Eval


• Simple mechanism to evaluate math-like formulas

• Eval.me (           ‘3*4’)
  Eval.x (1,          ‘3*x + 4’)
  ...
GroovyShell


• A Binding provides a context of execution
  – can implement lazy evaluation if needed


• A base script cl...
GroovyClassLoader

• Most powerful mechanism
 – could also visit or change the AST
 – scripts & classes can be loaded from...
Externalize business rules

• Although Groovy DSLs can be embedded in normal
  Groovy classes, you should externalize them...
nd a
Ag e
       • The context and
         the usual issues we face
       • Some real-life examples of
         Domain-S...
Start small, with key concepts
Beware overengineering!
Grow your language progressively
Get your hands dirty
Play with the end-users
Let your DSL fly,
it’s not yours,
it’s theirs!
Tight feedback loop
Iterative process
Stay humble.
You can’t get it right the first time.
Don’t design alone at your desk
Involve the end users from the start
Playing it safe
in a sandbox
Various levels of               sandboxing



• Groovy supports the usual Java Security Managers

• Use metaprogramming tr...
Test, test, test!



• Don’t just test for nominal cases
  – Explicitly test for errors!


• Ensure end-users get meaningf...
nd a
Ag e

       • Summary

       • Questions & Answers




                               75
Summary

• Groovy’s a great fit for Domain-Specific Languages
  – Malleable & flexible syntax
  – Full object-orientation
...
?
I kan haz my cheezburgr naw?
 Or do ya reely haz keshtionz?
Appendix




           78
• http://www.flickr.com/photos/wheatfields/420088151/sizes/l/
• http://www.flickr.com/photos/therefromhere/518053737/sizes...
• http://www.flickr.com/photos/forezt/192554677/sizes/o/
• http://keremkosaner.files.wordpress.com/2008/04/
  softwaredeve...
GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge
Upcoming SlideShare
Loading in...5
×

GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge

2,143

Published on

Guillaume Laforge, Groovy Project Manager, presents how to implement your own Domain-Specific Languages in Groovy

Published in: Technology, Business
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,143
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
99
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

Transcript of "GR8Conf 2009: Practical Groovy DSL by Guillaume Laforge"

  1. 1. Practical Domain-Specific Languages with Groovy All the techniques to create your own DSLs Guillaume Laforge Head of Groovy Development
  2. 2. Guillaume Laforge • Groovy Project Manager • JSR-241 Spec Lead • Head of Groovy Development at SpringSource • Initiator of the Grails framework • Co-author of Groovy in Action • Speaker: JavaOne, QCon, JavaZone, Sun TechDays, Devoxx, The Spring Experience, JAX, Dynamic Language World, IJTC, and more... 3
  3. 3. A few words about Groovy • Groovy is a dynamic language for the JVM – with a Meta Object Protocol – compiles directly to bytecode, seamless Java interop • Open Source ASL 2 project hosted at Codehaus • Relaxed grammar derived from Java 5 – + borrowed good ideas from Ruby, Python, Smalltalk • Fast... for a dynlang on the JVM • Closures, properties, optional typing, BigDecimal by default, nice wrapper APIs, and more... 4
  4. 4. nd a Ag e • The context and the usual issues we face • Some real-life examples of Domain-Specific Languages • Groovy’s DSL capabilities • Integrating a DSL in your application • Considerations to remember when designing your own DSL 5
  5. 5. The context
  6. 6. Subject Matter Experts, Business analysts...
  7. 7. Developer producing LOLCODE HAI CAN HAS STDIO? I HAS A VAR IM IN YR LOOP UP VAR!!1 VISIBLE VAR IZ VAR BIGGER THAN 10? KTHXBYE IM OUTTA YR LOOP KTHXBYE
  8. 8. Lots of languages...
  9. 9. And in the end... ...nobody understands each other
  10. 10. Expressing requirements... 11
  11. 11. DSL: a potential solution? • Use a more expressive language than a general purpose one • Share a common metaphore of understanding between developers and subject matter experts • Have domain experts help with the design of the business logic of an application • Avoid cluttering business code with too much boilerplate technical code • Cleanly separate business logic from application code • Let business rules have their own lifecycle 12
  12. 12. Towards more readability (1) 13
  13. 13. Towards more readability (1) 13
  14. 14. Towards more readability (1) 20% 13
  15. 15. Towards more readability (2) 14
  16. 16. Towards more readability (2) 14
  17. 17. Towards more readability (2) 80% 14
  18. 18. nd a Ag e • The context and the usual issues we face • Some real-life examples of Domain-Specific Languages • Groovy’s DSL capabilities • Integrating a DSL in your application • Considerations to remember when designing your own DSL 15
  19. 19. A collection of DSLs • In our everyday life, we’re surrounded by DSLs – Technical dialects – Notations – Business languages 16
  20. 20. Technical dialects 17
  21. 21. SQL
  22. 22. ^[w-.]+@([w-]){2,4}$ 19
  23. 23. Notations 20
  24. 24. 1. e4 e5 2. Nf3 Nc6 3. Bb5 a6
  25. 25. L2 U F-1 B L2 F B -1 U L2
  26. 26. Visual!
  27. 27. Business languages 24
  28. 28. Real-life Groovy examples • Anti-malaria drug resistance simulation • Human Resources employee skills representation • Insurance policies risk calculation engine • Loan acceptance rules engine for a financial platform • Mathematica-like lingua for nuclear safety simulations • Market data feeds evolution scenarios • and more... 25
  29. 29. nd a Ag e • The context and the usual issues we face • Some real-life examples of Domain-Specific Languages • Groovy’s DSL capabilities • Integrating a DSL in your application • Considerations to remember when designing your own DSL 26
  30. 30. A flexible & malleable syntax • No need to write full-blown classes, use scripts • Optional typing (def) – in scripts, you can even omit the def keyword • Native syntax constructs • Parentheses & semi-colons are optional • Named arguments • BigDecimal by default for decimal numbers • Closures for custom control structures • Operator overloading 27
  31. 31. Scripts vs classes • Hide all the boilerplate technical code – an end-user doesn’t need to know about classes – public class Rule { public static void main(String[] args) { System.out.println(“Hello”); } } – println “Hello” 28
  32. 32. Optional typing • No need to bother with types or even generics – unless you want to! • Imagine an interest rate lookup table method returning some generified type: –Rate<LoanType, Duration, BigDecimal>[] lookupTable() { ... } def table = lookupTable() • No need to repeat the horrible generics type info! 29
  33. 33. Native syntax constructs • Lists – [Monday, Tuesday, Wednesday] • Maps – [CA: ‘California’, TX: ‘Texas’] • Ranges – def bizDays = Monday..Friday – def allowedAge = 18..65 – You can create your own custom ranges 30
  34. 34. Optional parens & semis • Make statements and expressions look more like natural languages – move(left); – move left 31
  35. 35. Named arguments • In Groovy you can mix named and unnamed arguments for method parameters – named params are actually put in a map parameter – plus optional parens & semis • take 1.pill, of: Chloroquinine, after: 6.hours • Corresponds to a method signature like: –def take(Map m, MedicineQuantity mq) 32
  36. 36. BigDecimal by default • Main reason why financial institutions often decide to use Groovy for their business rules! – Although these days rounding issues are overrated! • Java vs Groovy for a simple interpolation equation • BigDecimal uMinusv = c.subtract(a); BigDecimal vMinusl = b.subtract(c); BigDecimal uMinusl = a.subtract(b); return e.multiply(uMinusv) .add(d.multiply(vMinusl)) .divide(uMinusl, 10, BigDecimal.ROUND_HALF_UP); • (d * (b - c) + e * (c - a)) / (a - b) 33
  37. 37. Custom control structures Thanks to closures • When closures are last, they can be put “out” of the parentheses surrounding parameters • unless (account.balance > 100.euros, { account.debit 100.euros }) • unless (account.balance > 100.euros) { account.debit 100.euros } • Signature def unless(boolean b, Closure c) 34
  38. 38. Operator overloading a + b a.plus(b) a - b a.minus(b) • Currency amounts a * b a.multiply(b) – 15.euros + 10.dollars a / b a.divide(b) a % b a.modulo(b) • Distance handling a ** b a.power(b) – 10.kilometers - 10.meters a | b a.or(b) a & b a.and(b) • Workflow, concurrency a ^ b a.xor(b) – taskA | taskB & taskC a[b] a.getAt(b) a << b a.leftShift(b) • Credit an account a >> b a.rightShift(b) – account << 10.dollars +a a.positive() account += 10.dollars -a a.negative() account.credit 10.dollars ~a a.bitwiseNegate() 35
  39. 39. Groovy’s dynamic heart: The MOP! MetaObject Protocol
  40. 40. Groovy’s MOP • All the accesses to methods, properties, constructors, operators, etc. can be intercepted thanks to the MOP • While Java’s behavior is hard-wired at compile- time in the class • Groovy’s runtime behavior is adaptable at runtime through the metaclass. • Different hooks for changing the runtime behavior – GroovyObject, custom MetaClass implementation, categories, ExpandoMetaClass 37
  41. 41. GroovyObject • All instances of classes created in Groovy implement the GroovyObject interface: –getProperty(String name) –setProperty(String name, Object value) –invokeMethod(String name, Object[] params) –getMetaClass() –setMetaClass(MetaClass mc) • A GO can have “pretended” methods and properties 38
  42. 42. MetaClass • The core of Groovy’s MOP system –invokeConstructor() –invokeMethod() and invokeStaticMethod() –invokeMissingMethod() –getProperty() and setProperty() –getAttribute() and setAttribute() –respondsTo() and hasProperty() • MetaClasses can change the behavior of existing third-party classes — even from the JDK 39
  43. 43. ExpandoMetaClass • A DSL for MetaClasses! • MoneyAmount.metaClass.constructor = { ... } Number.metaClass.getDollars = { ... } Distance.metaClass.toMeters = { ... } Distance.metaClass.static.create = { ... } • To avoid repetition of Type.metaClass, you can pass a closure to metaClass { ... } • The delegate variable in closure represents the current instance, and it the default parameter 40
  44. 44. The Builder pattern
  45. 45. The Groovy MarkupBuilder • def mkp = new MarkupBuilder() mkp.html { head { title “Groovy in Action” } body { div(width: ‘100’) { p(class: ‘para) { span “Best book ever!” } } } } 42
  46. 46. A builder for HR • softskills { ideas { capture 2 formulate 3 } ... } knowhow { languages { java 4 groovy 5 } ... } 43
  47. 47. A builder for HR • softskills { ideas { capture 2 formulate 3 } ... } knowhow { languages { java 4 groovy 5 } ... } 43
  48. 48. Builders • Builders are... – a mechanism for creating any tree-structered graph – the realization of the GoF builder pattern at the syntax level in Groovy – simply a clever use of chained method invocation, closures, parentheses omission, and use of the GroovyObject methods • Existing builders – XML, Object graph, Swing, Ant, JMX, and more... 44
  49. 49. The clever trick • GroovyObject#invokeMethod() is used to catch all non-existing method calls in the context of the builder • The nesting of closures visually shows the level of nesting / depth in the tree • builder.m1(attr1:1, attr2:2, { builder.m2(..., {...}) } becomes equivalent to builder.m1(attr1:1, attr2:2) { m2(...) {...} } thanks to parens omission 45
  50. 50. Adding properties to numbers • Three possible approaches – create a Category •a category is a kind of decorator for default MCs – create a custom MetaClass •a full-blown MC class to implement and to set on the POGO instance – use ExpandoMetaClass •friendlier DSL approach but with a catch 46
  51. 51. With a Category • class DistanceCategory { static Distance getMeters(Integer self) { new Distance(self, Unit.METERS) } } use(DistanceCategory) { 100.meters } • Interesting scope: thread-bound & lexical • But doesn’t work across the hierarchy of classes – ie. subclasses won’t benefit from the new property 47
  52. 52. With an ExpandoMetaClass • Number.metaClass.getMeters = {-> new Distance(delegate, Unit.METERS) } 100.meters • Works for the class hierarchy for POJOs, and a flag exists to make it work for POGOs too • But the catch is it’s really a global change, so beware EMC enhancements collisions 48
  53. 53. Compile-time metaprogramming • Groovy 1.6 introduced AST Transformations • Compile-time == No runtime performance penalty! Transformation 49
  54. 54. AST Transformations • Two kinds of transformations – Global transformations •applicable to all compilation units – Local transformations •applicable to marked program elements •using specific marker annotations 50
  55. 55. Example #1: @Singleton • Let’s revisit this evil (anti-)pattern ! public class Evil { public static final Evil instance = new Evil (); private Evil () {} Evil getInstance() { return instance; } } • In Groovy ! @Singleton class Evil {} • Also a “lazy” version ! @Singleton(lazy = true) class Evil {} 51
  56. 56. Example #2: @Delegate Not just for managers! • You can delegate to fields of your classes – class Employee { def doTheWork() { “done” } } class Manager { @Delegate Employee slave = new Employee() } def god = new Manager() assert god.doTheWork() == “done” • Damn manager who will get all the praise... 52
  57. 57. Global transformations • Implement ASTTransformation • Annotate the transfo specifying a compilation phase • @GroovyASTTransformation(phase=CompilePhase.CONVERSION) public class MyTransformation implements ASTTransformation { public void visit(ASTNode[] nodes, SourceUnit unit) { ... } } • For discovery, create the file META-INF/services/ org.codehaus.groovy.transform.ASTTransformation • Add the fully qualified name of the class in that file 53
  58. 58. Local transformations • Same approach as Globale transformations • But you don’t need the META-INF file • Instead create an annotation to specify on which element the transformation should apply • @Retention(RetentionPolicy.SOURCE) @Target([ElementType.METHOD]) @GroovyASTTransformationClass( ["fqn.MyTransformation"]) public @interface WithLogging {...} 54
  59. 59. Example: the Spock framework • Changing the semantics of the original code • But keeping a valid Groovy syntax • @Speck class HelloSpock { def "can you figure out what I'm up to?"() { expect: name.size() == size where: name << ["Kirk", "Spock", "Scotty"] size << [4, 5, 6] } } 55
  60. 60. nd a Ag e • The context and the usual issues we face • Some real-life examples of Domain-Specific Languages • Groovy’s DSL capabilities • Integrating a DSL in your application • Considerations to remember when designing your own DSL 56
  61. 61. Various integration mechanisms • Java 6’s javax.script.* APIs (aka JSR-223) • Spring’s language namespace • Groovy’s own mechanisms • But a key idea is to externalize those DSL programs – DSL programs can have their own lifecycle – no need to redeploy an application because of a rule change – business people won’t see the technical code 57
  62. 62. Java 6’s javax.script.* API • Groovy 1.6 provides its own implementation of the javax.script.* API • ScriptEngineManager mgr = new ScriptEngineManager(); ScriptEngine engine = mgr.getEngineByName(“Groovy”); String result = (String)engine.eval(“2+3”); 58
  63. 63. Spring’s lang namespace • POGOs (Plain Old Groovy Objects) can be pre- compiled as any POJO and used interchangeably with POJOs in a Spring application • But Groovy scripts & classes can be loaded at runtime through the <lang:groovy/> namespace and tag • Reloadable on change • Customizable through a custom MetaClass • <lang:groovy id="events" script-source="classpath:dsl/ eventsChart.groovy" customizer-ref="eventsMetaClass" /> 59
  64. 64. Groovy’s own mechanisms • Eval – for evaluating simple expressions • GroovyShell – for more complex scripts and DSLs • GroovyClassLoader – the most powerful mechanism 60
  65. 65. Eval • Simple mechanism to evaluate math-like formulas • Eval.me ( ‘3*4’) Eval.x (1, ‘3*x + 4’) Eval.xy (1, 2, ‘x + y’) Eval.xyz(1, 2, 3, ‘x * y - z’) 61
  66. 66. GroovyShell • A Binding provides a context of execution – can implement lazy evaluation if needed • A base script class can be specified • def binding = new Binding() binding.mass = 22.3 binding.velocity = 10.6 def shell = new GroovyShell(binding) shell.evaluate(“mass * velocity ** 2 / 2”) 62
  67. 67. GroovyClassLoader • Most powerful mechanism – could also visit or change the AST – scripts & classes can be loaded from elsewhere – more control on compilation • GroovyClassLoader gcl = new GroovyClassLoader(); Class clazz = gcl.parseClass( new File(“f.groovy”)); GroovyObject instance = (GroovyObject)clazz.newInstance(); instance.setMetaClass(customMC); 63
  68. 68. Externalize business rules • Although Groovy DSLs can be embedded in normal Groovy classes, you should externalize them • Store them elsewhere – in a database, an XML file, etc. • Benefits – Business rules are not entangled in technical application code – Business rules can have their own lifecycle, without requiring application redeployments 64
  69. 69. nd a Ag e • The context and the usual issues we face • Some real-life examples of Domain-Specific Languages • Groovy’s DSL capabilities • Integrating a DSL in your application • Considerations to remember when designing your own DSL 65
  70. 70. Start small, with key concepts Beware overengineering!
  71. 71. Grow your language progressively
  72. 72. Get your hands dirty Play with the end-users
  73. 73. Let your DSL fly, it’s not yours, it’s theirs!
  74. 74. Tight feedback loop Iterative process
  75. 75. Stay humble. You can’t get it right the first time. Don’t design alone at your desk Involve the end users from the start
  76. 76. Playing it safe in a sandbox
  77. 77. Various levels of sandboxing • Groovy supports the usual Java Security Managers • Use metaprogramming tricks to prevent calling / instantiating certain classes • Create a special GroovyClassLoader AST code visitor to filter only the nodes of the AST you want to keep – ArithmeticShell in Groovy’s samples 73
  78. 78. Test, test, test! • Don’t just test for nominal cases – Explicitly test for errors! • Ensure end-users get meaningful error messages 74
  79. 79. nd a Ag e • Summary • Questions & Answers 75
  80. 80. Summary • Groovy’s a great fit for Domain-Specific Languages – Malleable & flexible syntax – Full object-orientation • Metaprogramming capabilities – Runtime metaprogramming – Compile-time metaprogramming • Groovy’s very often used for mission-critical DSLs 76
  81. 81. ? I kan haz my cheezburgr naw? Or do ya reely haz keshtionz?
  82. 82. Appendix 78
  83. 83. • http://www.flickr.com/photos/wheatfields/420088151/sizes/l/ • http://www.flickr.com/photos/therefromhere/518053737/sizes/l/ • http://www.flickr.com/photos/romainguy/230416692/sizes/l/ • http://www.flickr.com/photos/addictive_picasso/2874279971/sizes/l/ • http://www.flickr.com/photos/huangjiahui/3127634297/sizes/l/ • http://www.flickr.com/photos/25831000@N08/3064515804/sizes/o/ • http://www.flickr.com/photos/lanier67/3147696168/sizes/l/ • http://www.flickr.com/photos/ktb/4916063/sizes/o/ • http://www.flickr.com/photos/nathonline/918128338/sizes/l/ • http://www.flickr.com/photos/kevinsteele/39300193/sizes/l/ • http://commons.wikimedia.org/wiki/File:Brueghel-tower-of-babel.jpg • http://commons.wikimedia.org/wiki/File:Platypus.jpg • http://www.flickr.com/photos/joaomoura/2317171808/sizes/l/ • http://www.flickr.com/photos/wiccked/132687067/ • http://www.flickr.com/photos/xcbiker/386876546/sizes/l/ • http://www.flickr.com/photos/pietel/152403711/sizes/o/ 79
  84. 84. • http://www.flickr.com/photos/forezt/192554677/sizes/o/ • http://keremkosaner.files.wordpress.com/2008/04/ softwaredevelopment.gif • http://www.jouy.inra.fr • http://www.flickr.com/photos/ejpphoto/408101818/sizes/o/ • http://www.flickr.com/photos/solaro/2127576608/sizes/l/ • http://www.flickr.com/photos/biggreymare/2846899405/sizes/l/ • http://www.flickr.com/photos/timsamoff/252370986/sizes/l/ • http://www.flickr.com/photos/29738009@N08/2975466425/sizes/l/ • http://www.flickr.com/photos/howie_berlin/180121635/sizes/o/ • http://www.flickr.com/photos/yogi/1281980605/sizes/l/ • http://www.flickr.com/photos/dorseygraphics/1336468896/sizes/l/ 80
  1. A particular slide catching your eye?

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

×