Why MacRuby Matters
Patrick Thomson

C4[3]

September 2009
Edsger Dijkstra
“Go To Statement Considered Harmful”
Dijkstraʼs Algorithm
“The effective exploitation of the
powers of abstraction must be
regarded as one of the most vital
activities of a compute...
Objective-C
insufficiently powerful abstraction
What does Objective-C lack?
1. code reuse
inheritance alone
  is not enough
singletons
inheritance
In Objective-C:
1. create a static, shared instance
2. initialize once and only once
3. add an sharedInstance accessor met...
tedium sucks
mixins
units of behavior;
mix-in common functionality
 across unrelated classes
With mixins:

class Example
 include Singleton
 # your methods here
end
foo = Example.instance
not a new idea
Symbolics Lisp
1980
1. code reuse
2. safety
C is powerful.
C is powerful. (At the price of safety.)
We see Cʼs unsafeness throughout Objective-C.
raw pointers: bad news.
easily-confusable garbage collection
exceptions
creation, @try, and @throw are all expensive
Cocoa isnʼt exception-safe.
mishmash of NSError**,
NSException, and error codes
1. code reuse
2. safety
3. syntactic abstraction
[NSArray arrayWithObjects: @"a", @"b", @"c", nil];




                       vs.



                 ["a", "b", "c"]
[NSDictionary dictionaryWithObjectsAndKeys:
  @"Chicago", @"location", @"C4", @"event", nil];




                       v...
[foo compare:bar] == NSComparisonResultAscending




                      vs.



                   foo < bar
I want a language that lets
       Cocoa shine.
MacRuby.
new Ruby implementation
+



powered by LLVM
+               +



on top of CoreFoundation
irb(main):001:0> "c4".class
=> NSMutableString
irb(main):002:0> [1, 2, 3].class
=> NSMutableArray
irb(main):003:0> {:tollf...
irb(main):001:0>   3000.class
=> Fixnum
irb(main):002:0>   3000.is_a? NSNumber
=> true
irb(main):003:0>   3000.is_a? NSObj...
Laurent Sansonetti

Vincent Isambart


Rich Kilmer


Eloy Duran


Ben Stiglitz


Matt Aimonetti
MacRuby isnʼt…
a bridge
MacRuby != RubyCocoa
a toy
provides real solutions to
  real-world problems
example: threading
Global Interpreter Locks
only one system thread can touch
 the interpreter at any given time
prevents true multithreading
MacRuby has no GIL!
So why should you, as Cocoa
 developers, use MacRuby?
itʼs fast
historically slow
fib(40)
static int fib(int n)
{
  if (n < 3) {
    return 1;
  } else {
    return fib(n - 1) + fib(n - 2);
  }
}
@implementation Fib

- (int)fib:(int)n
{
  if (n < 3) {
    return 1;
  } else {
    return [self fib:n - 1] + [self fib:n...
C        Objective-C
                     4




                     3
execution time (s)




                     2




 ...
def fib(n)
  if n < 3
    1
  else
    fib(n-1) + fib(n-2)
  end
end
C   MacRuby      Objective-C
                     4




                     3
execution time (s)




                    ...
Faster than Objective-C‽
macruby --compile
first ahead-of-time compiler
         for Ruby
C       MacRuby (compiled)        Obj-C   MacRuby (interpreted)
                               JRuby   Ruby
              ...
C     MacRuby (compiled)       Obj-C          MacRuby (interpreted)

                           0.3
                      ...
itʼs beautiful
Objective-C:




[obj setValue:val forKey:key]
Bridges:



obj.setValue_forKey(val, key)


     READABILITY FAIL
no_underscores_plz_k_thx
MacRuby:



obj.setValue(val, forKey:key)
HotCocoa
mapping Cocoa to idiomatic Ruby
[[NSImage alloc] initWithContentsOfFile:path];




                   becomes




             image(:file => path)
[[NSGradient alloc]
 initWithStartingColor: [NSColor greyColor]
           endingColor: [NSColor blueColor]];



         ...
a real, robust macro system
itʼs conceptually sound
single inheritance
open classes
message-based object model
dynamically typed
garbage-collected
Objective-C is C with the Smalltalk
     object model attached.


 Ruby is Smalltalk with C syntax.
itʼs fully integrated with the
       Cocoa toolchain
Yeah, We Got That
itʼs 100% compatible with every
        Cocoa framework
BridgeSupport lets you call any
 C-based API from MacRuby
CFStringGetLength("hello C4")
=> 8
queue = Dispatch::Queue.concurrent

queue.dispatch do
  puts "Asynchronous dispatch FTW!"
end
it provides features that
    Objective-C lacks
regular expressions,
tail-call optimization,
     namespaces,
        mixins,
operator overloading,
runtime evaluation…
What do you lose when going
from Objective-C to MacRuby?
static typing
JVM : Scala   Cocoa : ?
Where are we now?
almost 0.5
SVN trunk is stable
  (macruby.org)
nightly builds for 10.6 at
 macruby.icoretech.org
What does the future hold?
Last summer I had an internship at Apple.
  My views here, however, represent that of an open-source contributor,
 and not...
performance improvements
compatibility
not yet
Possible? Yes.
 Certain? No
ahead-of-time compiler
MacRuby → x86/ARM machine code
full mode: packed with interpreter
restricted mode:
no eval(), no interpreter
compliant with Appleʼs restrictions
  on interpreters in iPhone apps
we need garbage collection on
         the iPhone
Stay Informed
                      @lrz
                 @importantshock
                   @benstiglitz
@MacRuby        ...
Modified "Why MacRuby Matters"
Modified "Why MacRuby Matters"
Modified "Why MacRuby Matters"
Modified "Why MacRuby Matters"
Modified "Why MacRuby Matters"
Upcoming SlideShare
Loading in...5
×

Modified "Why MacRuby Matters"

1,111

Published on

This is a set of modified MacRuby presentation slides given at the Pittsburgh Ruby Brigade meeting on Nov 5, 2009. The original presentation was given by Patrick Thomson at C4[3] in September, 2009. Slides 68 and 69 were added by me for the PghRB talk.

Patrick's original slides are available at http://www.slideshare.net/importantshock/why-macruby-matters

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

  • Be the first to like this

No Downloads
Views
Total Views
1,111
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
7
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide
  • Introductions. Thank Wolf. Banter.
  • Many of you may recognize this person.
  • contributions to structured programming
  • revolutionary graph-search algorithm that powers just about every network routing protocol
  • this quote of Dijkstra&amp;#x2019;s draws attention to a central conundrum facing Cocoa programmers today
  • the conundrum is this: Objective-C, the language we all know and love&amp;#x2026;
  • &amp;#x2026;provides us with an insufficiently powerful level of abstraction. We build the best desktop apps out there, but we do it in spite of ObjC.
  • So, as a language, what does ObjC lack?
  • I submit that perhaps the most fundamental problem with ObjC is its lack of support for code reuse. At the Objective-C level of abstraction, effective code reuse is like pulling teeth.
  • And sure, inheritance provides a good measure of code reuse. But for many use cases, it simply doesn&amp;#x2019;t apply.
  • Consider the singleton. It&amp;#x2019;s a great example of a common Objective-C pattern which cannot be abstracted out, forcing us to resort to boilerplate code.
  • Every singleton class needs its own static, shared instance. Single inheritance can&amp;#x2019;t solve the problem, and it&amp;#x2019;s questionable that multiple inheritance is the right solution in this case.
  • So, creating a singleton in Objective-C is really tedious. Add to the fact that very few people agree on the right way to go about this tedium - should you synchronize on sharedInstance, or init? Should you check for NULL or use pthread_once or dispatch_once? - and ensuring proper behavior seems hardly worth the trouble. It&amp;#x2019;s too tedious.
  • And tedium sucks.
  • A sufficiently powerful language would provide us with a code reuse mechanism like mixins.
  • Mixins, for those who haven&amp;#x2019;t heard of them, provide another level of abstraction into class definitions. Like classes, you define them and define method implementations in them; however, instead of including them in the class hierarchy, you mix them into classes - the resulting class copies the methods you defined in your mixin.
  • Here&amp;#x2019;s an example. In this language, the &amp;#x201C;include&amp;#x201D; keyword mixes a mixin into a class. By including the Singleton mixin in this class, it defines initialization and accessor methods as well as setting aside a static, shared instance.
  • Now we can access the shared instance. Easy.
  • And sure, I understand that the Objective-C language designers held off on implementing New and Fancy Ideas. And I agree with them.
  • But mixins have been around since Symbolics Lisp. Anyone remember that? I don&amp;#x2019;t. I wasn&amp;#x2019;t even born yet.
  • That was a while ago. And we, as Mac programmers, should ask more from our development tools.
  • Though Objective-C has a huge advantage in that it&amp;#x2019;s based on C, it also has a huge disadvantage - that it&amp;#x2019;s based on C.
  • Being a representation of a Von Neumann machine, C can solve any computable problem. But what&amp;#x2019;s interesting is that C is actually pretty expressive, even when you need to do fancy, comp-sci-sexy things. You&amp;#x2019;ve got a reasonable approximation of continuations with setjmp, of exceptions with sigsetjmp, and first-class functions with function pointers.
  • But all of these things are hideously unsafe. We&amp;#x2019;re programmers, not superheroes - if our tools allow us to make a mistake, we will make it.
  • And C&amp;#x2019;s unsafeness is visible in almost every part Objective-C.
  • We program in a high-level language. Why do we still have the ability to shoot ourselves so profoundly in the foot with raw pointers? You, or the library functions you call, can stomp all over memory silently and watch as you drown in bizarre and irreproducible behavior.
  • C&amp;#x2019;s ability to treat just about anything as a memory address means that you can bamboozle the hell out of the garbage collector.
  • Nowhere is the unsafe and backwards nature of C - and consequently, Objective-C - more apparent than exceptions. C really doesn&amp;#x2019;t play nice with the notion of exceptions at all, and has therefore hobbled ObjC exception support throughout ObjC&amp;#x2019;s lifetime.
  • ObjC exception handling is horrifically inefficient. Since on 32-bit exceptions are based on setjmp and longjmp, pretty much everything about them are expensive. Exception creation and try-blocks got much faster with 64-bit C++-compatible exceptions, but throwing them got even slower.
  • And the kicker is that the vast majority of Cocoa frameworks aren&amp;#x2019;t exception-safe. So there is absolutely no guarantee that objects will be cleaned up or finalized properly if you throw an exception.
  • So thanks to this, nobody ever checks for exceptions - honestly, when was the last time you checked for alloc throwing an NSMallocException? - and we make do with functions that take pointers to NSErrors and some functions that return either Carbon, Cocoa, POSIX, or Mach error codes.
  • Go ahead. Call me lazy.
  • But I&amp;#x2019;m tired of creating arrays manually. I want a language that has sufficient syntactic abstraction so that I can just create arrays inline. I mean, imagine if we had to do this to create NSStrings - it would be endlessly tedious!
  • And don&amp;#x2019;t even get me started on how tedious NSDictionary creation is. These may seem like syntactic quibbles to some of you, but I know that in the past I&amp;#x2019;ve avoided using dictionaries in favor of long if-else statements, just because creating dictionaries is so tedious!
  • Similarly, I want operator overloading, because it lets me say what I mean. I want the less-than sign to be transformed into a call to compare - and let&amp;#x2019;s be frank, operator overloading is never going to come to Objective-C.
  • Basically, I want a language that&amp;#x2019;s as well-designed as Cocoa.
  • And so far, the closest I have come to that ideal is when I write code in MacRuby.
  • For those of you that haven&amp;#x2019;t heard of it, MacRuby is a new implementation of Ruby, a scripting language from Japan.
  • MacRuby differs from the standard implementation of Ruby 1.9 in that its its virtual machine, optimization routines, bytecode generation, and just-in-time compilation are all implemented on top of LLVM, the Low-Level Virtual Machine project. LLVM is already blazingly fast, and I&amp;#x2019;m confident that its performance will only continue to improve.
  • In addition to replacing the Ruby 1.9 VM with the LLVM one, we also reimplemented the standard Ruby data structures on top of CoreFoundation. In addition to providing us with a set of memory-efficient, fast, and mature set of data structures, implementing MacRuby on top of Core Foundation also ensured that&amp;#x2026;
  • &amp;#x2026;Ruby objects are absolutely indistinguishable from Cocoa objects - they even respond to the same API calls! NS/CFStrings are Ruby strings, NS/CFArrays are Ruby arrays, NS/CFDictionaries are Ruby hashes.
  • But we go beyond what Objective-C offers, and return to a Smalltalk heritage where there are no primitive types. Everything descends from NSObject, even floats and integers.
  • Though MacRuby is an Apple-supported project, it is released under the Ruby license so you can embed it into your commercial applications.
  • The MacRuby team is one of the smartest and most focused I&amp;#x2019;ve ever seen. Laurent Sansonetti, an employee at Apple, started MacRuby just as an experiment to see how well Ruby would run on top of the Objective-C runtime and garbage collector. Yet in its current form, I truly believe that it&amp;#x2019;s stable enough
  • Clarification!
  • Bridges, as Tim pointed out in C4[1], are unreliable, difficult, and tend to be slow.
  • Explain the differences.
  • not a toy!
  • everyone has to deal with it sooner or later
  • explain what they are, what systems (Python, Ruby, Lua) use them
  • explain how much they suck
  • green threads suck
  • So. The big question.
  • MacRuby is fast. Not just Fast Enough, but fast.
  • This may surprise many of you, as most people know Ruby as &amp;#x201C;that weird, slow, Japanese space-Perl.&amp;#x201D; And the conception that Ruby is slower than other comparable languages has been true - up until now.
  • A good example of the speed boosts that we aim for is the Fibonacci sequence.
  • Take na&amp;#xEF;ve implementations of the Fibonacci sequence, using recursion, in C&amp;#x2026;
  • and in Objective-C (using ObjC message sending),
  • and, predictably, C is going to be a lot faster. And trying to get MacRuby faster than C for everything is beyond the scope of this project.
  • But take the same Fibonacci implementation in Ruby, run it under MacRuby&amp;#x2026;
  • and we see that MacRuby is, in this benchmark, faster than Objective-C.
  • The answer lies in the --compile flag. MacRuby can compile Ruby source down to Mach-O x86 executables.
  • This is hugely exciting. Nobody else has done this before. And up until now, shipping a closed-source desktop application written in Ruby has been a Bad Idea - but now you can hide your source code from prying eyes.
  • For completeness, let&amp;#x2019;s compare the C, compiled MacRuby and Objective-C performance and interpreted MacRuby, JRuby, and the stock OS X 10.6.1 ruby interpreter. OK... ouch. I dialed back from fib(40) to fib(35) because otherwise the stock ruby interpreter would still be calculating. JRuby&amp;#x2019;s better but still no competition for MacRuby (interpreted or compiled).
  • So let&amp;#x2019;s throw out the stock ruby and JRuby data to get a better look at MacRuby (compiled and interpreted) vs. C and Objective-C.
  • keyword syntax is readable.
  • worst of both worlds: verbosity of keyword syntax and unreadability of ALGOL-style syntax
  • syntactic extensions to make ObjC calls look gorgeous.
  • optional set of layers
  • to make common idioms concise
  • nestable, elegant, yet completely optional
  • like in Lisp and Scheme
  • Ruby and Objective-C are almost absurdly similar - how similar, you ask?
  • look at these features!
  • practically cousins
  • first-class environment
  • xcode templates, interface builder integration, instruments templates, dtrace scripts
  • leave nothing behind
  • mention that Laurent wrote it
  • contrived example, but talk about CoreImage, CoreGraphics, PDF documents
  • hooray for my GCD layer
  • talk about how things just work, and plug topfunky&amp;#x2019;s MacRuby screencast
  • hooray
  • whine about all of these features
  • be honest!
  • Though one can obviously treat Objective-C like a dynamic language, and give the &amp;#x2018;id&amp;#x2019; type to everything, well-written and idiomatic Objective-C code takes advantage of static typing to catch errors before they happen. And because Ruby is a duck-typed language, you&amp;#x2019;re going to lose some power.
  • And rather than viewing this as a problem for MacRuby, I think this is a great opportunity for another language to come in and apply all the innovations in the world of static typing - type inference, currying, existential types - to the world of Cocoa. So, uh, someone get on that.
  • The 0.5 release of MacRuby is almost upon us. This release will be the first one based on the new virtual machine.
  • If you want to play with the new features now, I recommend checking out the source from MacRuby.org
  • or get builds for Snowy.
  • DISCLAIMER!
    I&amp;#x2019;m here to talk about what is TECHNICALLY POSSIBLE, not what&amp;#x2019;s *going* to happen.
  • we want to be the fastest Ruby implementation around
  • we still need continuations, fibers, and to make all C exts compatible
  • The first thing people usually ask about MacRuby is &amp;#x201C;Does it run Rails yet?&amp;#x201D;
  • No. It doesn&amp;#x2019;t. And though we&amp;#x2019;d like to have it run Rails in the future, there&amp;#x2019;s still a lot of work to be done in that direction - revamping the sockets interface, improving IO speed and flexibility, and making sure that MacRuby supports all the maddening little metaprogramming quirks of which Rails takes advantage.
  • Much more interesting to me is the question of whether or not
  • it&amp;#x2019;s up to the lawyers
  • i mentioned this already
  • for metaprogramming and eval
  • hopefully that will come soon
  • Modified "Why MacRuby Matters"

    1. 1. Why MacRuby Matters Patrick Thomson C4[3] September 2009
    2. 2. Edsger Dijkstra
    3. 3. “Go To Statement Considered Harmful”
    4. 4. Dijkstraʼs Algorithm
    5. 5. “The effective exploitation of the powers of abstraction must be regarded as one of the most vital activities of a computer programmer.”
    6. 6. Objective-C
    7. 7. insufficiently powerful abstraction
    8. 8. What does Objective-C lack?
    9. 9. 1. code reuse
    10. 10. inheritance alone is not enough
    11. 11. singletons
    12. 12. inheritance
    13. 13. In Objective-C: 1. create a static, shared instance 2. initialize once and only once 3. add an sharedInstance accessor method …for every singleton class. Tedious.
    14. 14. tedium sucks
    15. 15. mixins
    16. 16. units of behavior; mix-in common functionality across unrelated classes
    17. 17. With mixins: class Example include Singleton # your methods here end
    18. 18. foo = Example.instance
    19. 19. not a new idea
    20. 20. Symbolics Lisp
    21. 21. 1980
    22. 22. 1. code reuse 2. safety
    23. 23. C is powerful.
    24. 24. C is powerful. (At the price of safety.)
    25. 25. We see Cʼs unsafeness throughout Objective-C.
    26. 26. raw pointers: bad news.
    27. 27. easily-confusable garbage collection
    28. 28. exceptions
    29. 29. creation, @try, and @throw are all expensive
    30. 30. Cocoa isnʼt exception-safe.
    31. 31. mishmash of NSError**, NSException, and error codes
    32. 32. 1. code reuse 2. safety 3. syntactic abstraction
    33. 33. [NSArray arrayWithObjects: @"a", @"b", @"c", nil]; vs. ["a", "b", "c"]
    34. 34. [NSDictionary dictionaryWithObjectsAndKeys: @"Chicago", @"location", @"C4", @"event", nil]; vs. {"location" => "Chicago", "event" => "C4"}
    35. 35. [foo compare:bar] == NSComparisonResultAscending vs. foo < bar
    36. 36. I want a language that lets Cocoa shine.
    37. 37. MacRuby.
    38. 38. new Ruby implementation
    39. 39. + powered by LLVM
    40. 40. + + on top of CoreFoundation
    41. 41. irb(main):001:0> "c4".class => NSMutableString irb(main):002:0> [1, 2, 3].class => NSMutableArray irb(main):003:0> {:tollfree => "bridging"}.class => NSMutableDictionary irb(main):004:0> "/usr/local/bin".pathComponents => ["/", "usr", "local", "bin"] Ruby objects Cocoa objects
    42. 42. irb(main):001:0> 3000.class => Fixnum irb(main):002:0> 3000.is_a? NSNumber => true irb(main):003:0> 3000.is_a? NSObject => true irb(main):003:0> 3000.class.is_a? NSObject => true Everything is an NSObject
    43. 43. Laurent Sansonetti Vincent Isambart Rich Kilmer Eloy Duran Ben Stiglitz Matt Aimonetti
    44. 44. MacRuby isnʼt…
    45. 45. a bridge
    46. 46. MacRuby != RubyCocoa
    47. 47. a toy
    48. 48. provides real solutions to real-world problems
    49. 49. example: threading
    50. 50. Global Interpreter Locks
    51. 51. only one system thread can touch the interpreter at any given time
    52. 52. prevents true multithreading
    53. 53. MacRuby has no GIL!
    54. 54. So why should you, as Cocoa developers, use MacRuby?
    55. 55. itʼs fast
    56. 56. historically slow
    57. 57. fib(40)
    58. 58. static int fib(int n) { if (n < 3) { return 1; } else { return fib(n - 1) + fib(n - 2); } }
    59. 59. @implementation Fib - (int)fib:(int)n { if (n < 3) { return 1; } else { return [self fib:n - 1] + [self fib:n - 2]; } } @end
    60. 60. C Objective-C 4 3 execution time (s) 2 1 0 fib(40)
    61. 61. def fib(n) if n < 3 1 else fib(n-1) + fib(n-2) end end
    62. 62. C MacRuby Objective-C 4 3 execution time (s) 2 1 0 fib(40)
    63. 63. Faster than Objective-C‽
    64. 64. macruby --compile
    65. 65. first ahead-of-time compiler for Ruby
    66. 66. C MacRuby (compiled) Obj-C MacRuby (interpreted) JRuby Ruby 9 execution time (seconds) 6 3 0 fib(35)
    67. 67. C MacRuby (compiled) Obj-C MacRuby (interpreted) 0.3 0.28 execution time (seconds) 0.24 0.2 0.2 0.15 0.1 0 fib(35)
    68. 68. itʼs beautiful
    69. 69. Objective-C: [obj setValue:val forKey:key]
    70. 70. Bridges: obj.setValue_forKey(val, key) READABILITY FAIL
    71. 71. no_underscores_plz_k_thx
    72. 72. MacRuby: obj.setValue(val, forKey:key)
    73. 73. HotCocoa
    74. 74. mapping Cocoa to idiomatic Ruby
    75. 75. [[NSImage alloc] initWithContentsOfFile:path]; becomes image(:file => path)
    76. 76. [[NSGradient alloc] initWithStartingColor: [NSColor greyColor] endingColor: [NSColor blueColor]]; becomes gradient(:start => color(:name => "grey"), :end => color(:name => "blue"))
    77. 77. a real, robust macro system
    78. 78. itʼs conceptually sound
    79. 79. single inheritance open classes message-based object model dynamically typed garbage-collected
    80. 80. Objective-C is C with the Smalltalk object model attached. Ruby is Smalltalk with C syntax.
    81. 81. itʼs fully integrated with the Cocoa toolchain
    82. 82. Yeah, We Got That
    83. 83. itʼs 100% compatible with every Cocoa framework
    84. 84. BridgeSupport lets you call any C-based API from MacRuby
    85. 85. CFStringGetLength("hello C4") => 8
    86. 86. queue = Dispatch::Queue.concurrent queue.dispatch do puts "Asynchronous dispatch FTW!" end
    87. 87. it provides features that Objective-C lacks
    88. 88. regular expressions, tail-call optimization, namespaces, mixins, operator overloading, runtime evaluation…
    89. 89. What do you lose when going from Objective-C to MacRuby?
    90. 90. static typing
    91. 91. JVM : Scala Cocoa : ?
    92. 92. Where are we now?
    93. 93. almost 0.5
    94. 94. SVN trunk is stable (macruby.org)
    95. 95. nightly builds for 10.6 at macruby.icoretech.org
    96. 96. What does the future hold?
    97. 97. Last summer I had an internship at Apple. My views here, however, represent that of an open-source contributor, and not an Apple employee. Any speculations on the future of MacRuby are entirely mine and are in no way representative of any plans, attitudes, or future directions that Apple may take. This presentation is neither sponsored nor endorsed by Apple. Apple: please donʼt sue me.
    98. 98. performance improvements
    99. 99. compatibility
    100. 100. not yet
    101. 101. Possible? Yes. Certain? No
    102. 102. ahead-of-time compiler
    103. 103. MacRuby → x86/ARM machine code
    104. 104. full mode: packed with interpreter
    105. 105. restricted mode: no eval(), no interpreter
    106. 106. compliant with Appleʼs restrictions on interpreters in iPhone apps
    107. 107. we need garbage collection on the iPhone
    108. 108. Stay Informed @lrz @importantshock @benstiglitz @MacRuby @vincentisambart @mattaimonetti @alloy
    1. A particular slide catching your eye?

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

    ×