Share
Embed
Go: Object Oriented and Concurrent (just not the usual way)
Go is object oriented and concurrent, just not the way other languages are. This talk explores the differences between Go and the approach of other languages for Object Orientation and Concurrency.
golang
Francesc Campoy Flores
September 12, 2014
Transcript
55 slides
-
Go: object oriented and concurrent Just not the usual way
Francesc Campoy Developer Advocate for Go and the Cloud -
Recording A recording of this talk is available on YouTube
youtu.be/Ng8m5VXsn8Q (http://youtu.be/Ng8m5VXsn8Q) -
What is Go? Go is an open-source programming language created
at Google, to solve Google-scale problems. -
Who uses Go? Google: YouTube dl.google.com Others: dotCloud (Docker) SoundCloud
Canonical CloudFlare Mozilla ... golang.org/wiki/GoUsers (http://golang.org/wiki/GoUsers) -
Go is Go is statically typed, part of the C
family, garbage collected, statically compiled, object oriented, concurrency friendly. And today I will speak about the last two points. -
Object oriented flavors Go is Object Oriented, but doesn't have
the keywords: c l a s s , e x t e n d s , or i m p l e m e n t s . -
All types are created equal You can define methods on
any type. t y p e D o o r s t r u c t { o p e n e d b o o l } f u n c ( d * D o o r ) O p e n ( ) { d . o p e n e d = t r u e } f u n c ( d * D o o r ) C l o s e ( ) { d . o p e n e d = f a l s e } -
All types are created equal (continued) Also on primitive types.
t y p e D o o r b o o l f u n c ( d * D o o r ) O p e n ( ) { * d = t r u e } f u n c ( d * D o o r ) C l o s e ( ) { * d = f a l s e } But only in types defined in the same package. f u n c ( s s t r i n g ) L e n g t h ( ) i n t { r e t u r n 2 * l e n ( s ) } No monkey patching allowed. -
Interfaces almost like Java An interface is a set of
methods. In Java: i n t e r f a c e S w i t c h { v o i d o p e n ( ) ; v o i d c l o s e ( ) ; } In Go: t y p e O p e n C l o s e r i n t e r f a c e { O p e n ( ) C l o s e ( ) } -
It's all about satisfaction Java interfaces are satisfied explicitly. Go
interfaces are satisfied implicitly. Picture by Gorupdebesanez CC-BY-SA-3.0 (http://creativecommons.org/licenses/by-sa/3.0) , via Wikimedia Commons (http://commons.wikimedia.org/wiki/File%3ARolling_Stones_09.jpg) -
Go: implicit satisfaction If a type defines all the methods
of an interface, the type satisfies that interface. Implicit satisfaction == No "implements" Benefits: fewer dependencies no type hierarchy organic composition -
Almost like duck typing Structural subtyping Think static duck typing,
verified at compile time. It doesn't just sound like a duck, it is a duck. -
FuncDraw: package parser Package p a r s e provides
a parser of strings into functions. f u n c P a r s e ( t e x t s t r i n g ) ( * F u n c , e r r o r ) { . . . } F u n c is a struct type, with an E v a l method. t y p e F u n c s t r u c t { . . . } f u n c ( p * F u n c ) E v a l ( x f l o a t 6 4 ) f l o a t 6 4 { . . . } -
FuncDraw: package draw Package draw generates images given a function.
f u n c D r a w ( f * p a r s e r . F u n c ) i m a g e . I m a g e { f o r x : = s t a r t ; x < e n d ; x + = i n c { y : = f . E v a l ( x ) . . . } } d r a w depends on p a r s e r makes testing hard Let's use an interface instead t y p e E v a l u a b l e i n t e r f a c e { E v a l ( f l o a t 6 4 ) f l o a t 6 4 } f u n c D r a w ( f E v a l u a b l e ) i m a g e . I m a g e { . . . } -
Inheritance vs composition Lots of articles have been written about
the topic. In general, composition is preferred to inheritance. Lets see why. -
Runner c l a s s R u n n
e r { p r i v a t e S t r i n g n a m e ; p u b l i c R u n n e r ( S t r i n g n a m e ) { t h i s . n a m e = n a m e ; } p u b l i c S t r i n g g e t N a m e ( ) { r e t u r n t h i s . n a m e ; } p u b l i c v o i d r u n ( T a s k t a s k ) { t a s k . r u n ( ) ; } p u b l i c v o i d r u n A l l ( T a s k [ ] t a s k s ) { f o r ( T a s k t a s k : t a s k s ) { r u n ( t a s k ) ; } } } -
RunCounter is a Runner that counts c l a s
s R u n C o u n t e r e x t e n d s R u n n e r { p r i v a t e i n t c o u n t ; p u b l i c R u n C o u n t e r ( S t r i n g m e s s a g e ) { s u p e r ( m e s s a g e ) ; t h i s . c o u n t = 0 ; } @ O v e r r i d e p u b l i c v o i d r u n ( T a s k t a s k ) { c o u n t + + ; s u p e r . r u n ( t a s k ) ; } @ O v e r r i d e p u b l i c v o i d r u n A l l ( T a s k [ ] t a s k s ) { c o u n t + = t a s k s . l e n g t h ; s u p e r . r u n A l l ( t a s k s ) ; } p u b l i c i n t g e t C o u n t ( ) { r e t u r n c o u n t ; } } -
Let's run and count What will this code print? R
u n C o u n t e r r u n n e r = n e w R u n C o u n t e r ( " m y r u n n e r " ) ; T a s k [ ] t a s k s = { n e w T a s k ( " o n e " ) , n e w T a s k ( " t w o " ) , n e w T a s k ( " t h r e e " ) } ; r u n n e r . r u n A l l ( t a s k s ) ; S y s t e m . o u t . p r i n t f ( " % s r a n % d t a s k s \ n " , r u n n e r . g e t N a m e ( ) , r u n n e r . g e t C o u n t ( ) ) ; Of course, this prints: r u n n i n g o n e r u n n i n g t w o r u n n i n g t h r e e m y r u n n e r r a n 6 t a s k s Wait! How many? -
My runner ran 6 tasks? Six? Inheritance causes: weak encapsulation,
tight coupling, surprising bugs. -
Solution: use composition c l a s s R u
n C o u n t e r { p r i v a t e R u n n e r r u n n e r ; p r i v a t e i n t c o u n t ; p u b l i c R u n C o u n t e r ( S t r i n g m e s s a g e ) { t h i s . r u n n e r = n e w R u n n e r ( m e s s a g e ) ; t h i s . c o u n t = 0 ; } p u b l i c v o i d r u n ( T a s k t a s k ) { c o u n t + + ; r u n n e r . r u n ( t a s k ) ; } p u b l i c v o i d r u n A l l ( T a s k [ ] t a s k s ) { c o u n t + = t a s k s . l e n g t h ; r u n n e r . r u n A l l ( t a s k s ) ; } / / c o n t i n u e d o n n e x t s l i d e . . . -
Solution: use composition (continued) p u b l i c
i n t g e t C o u n t ( ) { r e t u r n c o u n t ; } p u b l i c S t r i n g g e t N a m e ( ) { r e t u r n r u n n e r . g e t N a m e ( ) ; } } -
Solution: use composition (continued) Pros The bug is gone! R
u n n e r is completely independent of R u n C o u n t e r . The creation of the R u n n e r can be delayed until (and if) needed. Cons We need to explicitly define the R u n n e r methods on R u n C o u n t e r : p u b l i c S t r i n g g e t N a m e ( ) { r e t u r n r u n n e r . g e t N a m e ( ) ; } This can cause lots of repetition, and eventually bugs. -
There's no inheritance in Go Let's use composition directly: t
y p e R u n n e r s t r u c t { n a m e s t r i n g } f u n c ( r * R u n n e r ) N a m e ( ) s t r i n g { r e t u r n r . n a m e } f u n c ( r * R u n n e r ) R u n ( t T a s k ) { t . R u n ( ) } f u n c ( r * R u n n e r ) R u n A l l ( t s [ ] T a s k ) { f o r _ , t : = r a n g e t s { r . R u n ( t ) } } All very similar to the Java version. -
RunCounter R u n C o u n t e
r has a R u n n e r field. t y p e R u n C o u n t e r s t r u c t { r u n n e r R u n n e r c o u n t i n t } f u n c N e w R u n C o u n t e r ( n a m e s t r i n g ) * R u n C o u n t e r { r e t u r n & R u n C o u n t e r { r u n n e r : R u n n e r { n a m e } } } f u n c ( r * R u n C o u n t e r ) R u n ( t T a s k ) { r . c o u n t + + r . r u n n e r . R u n ( t ) } f u n c ( r * R u n C o u n t e r ) R u n A l l ( t s [ ] T a s k ) { r . c o u n t + = l e n ( t s ) r . r u n n e r . R u n A l l ( t s ) } f u n c ( r * R u n C o u n t e r ) C o u n t ( ) i n t { r e t u r n r . c o u n t } f u n c ( r * R u n C o u n t e r ) N a m e ( ) s t r i n g { r e t u r n r . r u n n e r . N a m e ( ) } -
Composition in Go Same pros and cons as the composition
version in Java. We also have the boilerplate to proxy methods from R u n n e r . f u n c ( r * R u n C o u n t e r ) N a m e ( ) s t r i n g { r e t u r n r . r u n n e r . N a m e ( ) } But we can remove it! -
Struct embedding Expressed in Go as unnamed fields in a
struct. It is still composition. The fields and methods of the embedded type are defined on the embedding type. Similar to inheritance, but the embedded type doesn't know it's embedded. -
Example of struct embedding Given a type P e r
s o n : t y p e P e r s o n s t r u c t { N a m e s t r i n g } f u n c ( p P e r s o n ) I n t r o d u c e ( ) { f m t . P r i n t l n ( " H i , I ' m " , p . N a m e ) } We can define a type E m p l o y e e embedding P e r s o n : t y p e E m p l o y e e s t r u c t { P e r s o n E m p l o y e e I D i n t } All fields and methods from P e r s o n are available on E m p l o y e e : v a r e E m p l o y e e e . N a m e = " P e t e r " e . E m p l o y e e I D = 1 2 3 4 e . I n t r o d u c e ( ) -
Struct embedding t y p e R u n C
o u n t e r 2 s t r u c t { R u n n e r c o u n t i n t } f u n c N e w R u n C o u n t e r 2 ( n a m e s t r i n g ) * R u n C o u n t e r 2 { r e t u r n & R u n C o u n t e r 2 { R u n n e r { n a m e } , 0 } } f u n c ( r * R u n C o u n t e r 2 ) R u n ( t T a s k ) { r . c o u n t + + r . R u n n e r . R u n ( t ) } f u n c ( r * R u n C o u n t e r 2 ) R u n A l l ( t s [ ] T a s k ) { r . c o u n t + = l e n ( t s ) r . R u n n e r . R u n A l l ( t s ) } f u n c ( r * R u n C o u n t e r 2 ) C o u n t ( ) i n t { r e t u r n r . c o u n t } -
Is struct embedding like inheritance? No, it is better! It
is composition. You can't reach into another type and change the way it works. Method dispatching is explicit. It is more general. Struct embedding of interfaces. -
Is struct embedding like inheritance? Struct embedding is selective. /
/ W r i t e C o u n t e r t r a c k s t h e t o t a l n u m b e r o f b y t e s w r i t t e n . t y p e W r i t e C o u n t e r s t r u c t { i o . R e a d W r i t e r c o u n t i n t } f u n c ( w * W r i t e C o u n t e r ) W r i t e ( b [ ] b y t e ) ( i n t , e r r o r ) { w . c o u n t + = l e n ( b ) r e t u r n w . R e a d W r i t e r . W r i t e ( b ) } WriteCounter can be used with any i o . R e a d W r i t e r . f u n c m a i n ( ) { b u f : = & b y t e s . B u f f e r { } w : = & W r i t e C o u n t e r { R e a d W r i t e r : b u f } f m t . F p r i n t f ( w , " H e l l o , g o p h e r s ! \ n " ) f m t . P r i n t f ( " P r i n t e d % v b y t e s " , w . c o u n t ) } Run -
Easy mocking What if we wanted to fake a part
of a n e t . C o n n ? t y p e C o n n i n t e r f a c e { R e a d ( b [ ] b y t e ) ( n i n t , e r r e r r o r ) W r i t e ( b [ ] b y t e ) ( n i n t , e r r e r r o r ) C l o s e ( ) e r r o r L o c a l A d d r ( ) A d d r R e m o t e A d d r ( ) A d d r S e t D e a d l i n e ( t t i m e . T i m e ) e r r o r S e t R e a d D e a d l i n e ( t t i m e . T i m e ) e r r o r S e t W r i t e D e a d l i n e ( t t i m e . T i m e ) e r r o r } I want to test h a n d l e C o n : f u n c h a n d l e C o n n ( c o n n n e t . C o n n ) { We could create a f a k e C o n n and define all the methods of C o n n on it. But that's a lot of boring code. -
Struct embedding of interfaces WARNING : Cool stuff If a
type T has an embedded field of a type E, all the methods of E will be defined on T. Therefore, if E is an interface T satisfies E. -
Struct embedding of interfaces (continued) We can test h a
n d l e C o n with the l o o p B a c k type. t y p e l o o p B a c k s t r u c t { n e t . C o n n b u f b y t e s . B u f f e r } Any calls to the methods of n e t . C o n n will fail, since the field is nil. We redefine the operations we support: f u n c ( c * l o o p B a c k ) R e a d ( b [ ] b y t e ) ( i n t , e r r o r ) { r e t u r n c . b u f . R e a d ( b ) } f u n c ( c * l o o p B a c k ) W r i t e ( b [ ] b y t e ) ( i n t , e r r o r ) { r e t u r n c . b u f . W r i t e ( b ) } -
Concurrency paradigms: Locks and Mutexes Acquire and release: m u
t e x . a c q u i r e ( ) / / d o s o m e t h i n g m u t e x . r e l e a s e ( ) Harder than it seems: t r y { m u t e x . a c q u i r e ( ) ; t r y { / / d o s o m e t h i n g } f i n a l l y { m u t e x . r e l e a s e ( ) ; } } c a t c h ( I n t e r r u p t e d E x c e p t i o n i e ) { / / . . . } The correctness of the code is hard to prove. -
Concurrency paradigms: Async Callback based: Ruby's EventMachine Python's Twisted NodeJS
Doesn't play well with parallelism. Code is hard to follow and to debug. Best practices help: callbackhell.com (http://callbackhell.com) -
Go concurrency It is part of the language, not a
library. Based on two concepts: goroutines: lightweight threads channels: typed pipes used to communicate and synchronize between goroutines So cheap you can use them whenever you want. -
Sleep and talk f u n c s l e
e p A n d T a l k ( t t i m e . D u r a t i o n , m s g s t r i n g ) { t i m e . S l e e p ( t ) f m t . P r i n t f ( " % v " , m s g ) } We want a message per second. What if we started all the s l e e p A n d T a l k concurrently? Just add g o ! f u n c m a i n ( ) { s l e e p A n d T a l k ( 0 * t i m e . S e c o n d , " H e l l o " ) s l e e p A n d T a l k ( 1 * t i m e . S e c o n d , " G o p h e r s ! " ) s l e e p A n d T a l k ( 2 * t i m e . S e c o n d , " W h a t ' s " ) s l e e p A n d T a l k ( 3 * t i m e . S e c o n d , " u p ? " ) } Run -
Concurrent sleep and talk That was fast ... When the
m a i n goroutine ends, the program ends. f u n c m a i n ( ) { g o s l e e p A n d T a l k ( 0 * t i m e . S e c o n d , " H e l l o " ) g o s l e e p A n d T a l k ( 1 * t i m e . S e c o n d , " G o p h e r s ! " ) g o s l e e p A n d T a l k ( 2 * t i m e . S e c o n d , " W h a t ' s " ) g o s l e e p A n d T a l k ( 3 * t i m e . S e c o n d , " u p ? " ) } Run -
Concurrent sleep and talk with more sleeping But synchronizing with
S l e e p is a bad idea. f u n c m a i n ( ) { g o s l e e p A n d T a l k ( 0 * t i m e . S e c o n d , " H e l l o " ) g o s l e e p A n d T a l k ( 1 * t i m e . S e c o n d , " G o p h e r s ! " ) g o s l e e p A n d T a l k ( 2 * t i m e . S e c o n d , " W h a t ' s " ) g o s l e e p A n d T a l k ( 3 * t i m e . S e c o n d , " u p ? " ) t i m e . S l e e p ( 4 * t i m e . S e c o n d ) } Run -
Communicating through channels s l e e p A n
d T a l k sends the string into the channel instead of printing it. f u n c s l e e p A n d T a l k ( s e c s t i m e . D u r a t i o n , m s g s t r i n g , c c h a n s t r i n g ) { t i m e . S l e e p ( s e c s * t i m e . S e c o n d ) c < - m s g } We create the channel and pass it to s l e e p A n d T a l k , then wait for the values to be sent. f u n c m a i n ( ) { c : = m a k e ( c h a n s t r i n g ) g o s l e e p A n d T a l k ( 0 , " H e l l o " , c ) g o s l e e p A n d T a l k ( 1 , " G o p h e r s ! " , c ) g o s l e e p A n d T a l k ( 2 , " W h a t ' s " , c ) g o s l e e p A n d T a l k ( 3 , " u p ? " , c ) f o r i : = 0 ; i < 4 ; i + + { f m t . P r i n t f ( " % v " , < - c ) } } Run -
Let's count on the web We receive the next id
from a channel. v a r n e x t I D = m a k e ( c h a n i n t ) f u n c h a n d l e r ( w h t t p . R e s p o n s e W r i t e r , q * h t t p . R e q u e s t ) { f m t . F p r i n t f ( w , " < h 1 > Y o u g o t % v < h 1 > " , < - n e x t I D ) } We need a goroutine sending ids into the channel. localhost:8080/next (http://localhost:8080/next) f u n c m a i n ( ) { h t t p . H a n d l e F u n c ( " / n e x t " , h a n d l e r ) g o f u n c ( ) { f o r i : = 0 ; ; i + + { n e x t I D < - i } } ( ) h t t p . L i s t e n A n d S e r v e ( " l o c a l h o s t : 8 0 8 0 " , n i l ) } Run -
Let's fight! s e l e c t allows us
to chose among multiple channel operations. Go - localhost:8080/fight?usr=go (http://localhost:8080/fight?usr=go) Java - localhost:8080/fight?usr=java (http://localhost:8080/fight?usr=java) v a r b a t t l e = m a k e ( c h a n s t r i n g ) f u n c h a n d l e r ( w h t t p . R e s p o n s e W r i t e r , q * h t t p . R e q u e s t ) { s e l e c t { c a s e b a t t l e < - q . F o r m V a l u e ( " u s r " ) : f m t . F p r i n t f ( w , " Y o u w o n ! " ) c a s e w o n : = < - b a t t l e : f m t . F p r i n t f ( w , " Y o u l o s t , % v i s b e t t e r t h a n y o u " , w o n ) } } Run -
Chain of gophers f u n c f ( l
e f t , r i g h t c h a n i n t ) { l e f t < - 1 + < - r i g h t } f u n c m a i n ( ) { s t a r t : = t i m e . N o w ( ) c o n s t n = 1 0 0 0 l e f t m o s t : = m a k e ( c h a n i n t ) r i g h t : = l e f t m o s t l e f t : = l e f t m o s t f o r i : = 0 ; i < n ; i + + { r i g h t = m a k e ( c h a n i n t ) g o f ( l e f t , r i g h t ) l e f t = r i g h t } g o f u n c ( c c h a n i n t ) { c < - 0 } ( r i g h t ) f m t . P r i n t l n ( < - l e f t m o s t , t i m e . S i n c e ( s t a r t ) ) } Run -
Concurrency is very powerful And there's lots to learn! Go
Concurrency Patterns (http://talks.golang.org/2012/concurrency.slide#1) , by Rob Pike Advanced Concurrency Patterns (http://talks.golang.org/2013/advconc.slide#1) , by Sameer Ajmani Concurrency is not Parellelism (http://talks.golang.org/2012/waza.slide#1) , by Rob Pike -
What to do next? Learn Go on your browser with
tour.golang.org (http://tour.golang.org) Find more about Go on golang.org (http://golang.org) Join the community at golang-nuts (https://groups.google.com/forum/#!forum/Golang-nuts) Link to the slides campoy.cat/openlate (http://campoy.cat/openlate) -
Thank you Francesc Campoy Developer Advocate for Go and the
Cloud @francesc (http://twitter.com/francesc) [email protected] (mailto:[email protected])