Player is loading...

Embed

Copy embed code

Transcriptions

Note: this content has been automatically generated.
00:00:00
oh
00:00:07
wow it is bright miles warned me about this
00:00:14
well cool thank you for coming i'm really excited to be here i think this
00:00:18
this call days gets better every year in and like the quality
00:00:22
of the talks this time is to blow me away and it's it's
00:00:25
just a fantastic conference so i'm on it to be here uh
00:00:29
and i hope i can i tell you something interesting um let's see
00:00:36
uh
00:00:38
kirk is going up or go um so this is the near infrared image of a nebula
00:00:46
called g. g. g. g. d. twenty seven i was taken
00:00:50
with an instrument called flamingos to the german itself telescope in chile
00:00:58
so um my name is rob uh i'm really easy to
00:01:02
find online empty poke at everywhere um i am a type
00:01:06
of remember i work on some the title projects and i
00:01:10
am your community representative of this call centre along with build enters
00:01:15
so uh if you have a questions or uh it
00:01:19
ideas about what a scholar centres working on or should be working
00:01:23
on a please reach out and talk to me or uh or portable
00:01:30
this is where i work uh this is the gemini observatory uh this was taken at night but the moon's up so it's kind of bright
00:01:37
um we have two eight and a half meter telescopes ones in chile one is and why
00:01:41
uh and they are amazing machines and my job is to write software
00:01:45
that helps scientists use them to look up and and uh the new things
00:01:54
and uh we're higher so but if
00:01:58
science programming and database programmers have not it's rick assigns programming and functional programming interest you
00:02:05
um uh let me know uh because uh uh i think it's really interesting work we do
00:02:13
so let's talk about database programming uh on the j. v. m. so i to motivate
00:02:19
this talk about uh put in some context so here's the timeline of twenty nineteen uh
00:02:26
so that's one point though came out a couple years ago twenty seventeen
00:02:32
to go to twelve came out i sure that uh to thirteen uh
00:02:37
can a twenty sixteen uh do b. which is a database library uh that worked on a live their
00:02:43
work on the we're gonna talk about a little bit um the first usable version cannot in twenty fifteen
00:02:49
slick another great database library came out and twenty thirteen
00:02:53
shape list one point o. came up and twenty eleven we're starting to get into a deep history
00:02:59
applicator funk there's we're in that in two thousand eight
00:03:04
i phoned came out in two thousand certainly ipod came out in two thousand one everybody remember that
00:03:11
google was founded in nineteen ninety eight
00:03:15
okay in j. d. c. came out in nineteen ninety six alright that's
00:03:21
just one year after java came out okay so my point here is the
00:03:27
j. v. c. is old it's really old technology um so it's got a
00:03:32
budget issues so it's not it's blocking unpredictably you never know what's gonna block
00:03:38
uh not very type safe um really easy to get
00:03:42
class cast exceptions there are lots of integer flags and stuff
00:03:45
they're easy to uh get mixed up i it's it's not the way you write a a modern a. p. i.
00:03:52
i'm not a knowles exceptions um
00:03:57
watson hater depends on the underlying database so even though you get a common a. p.
00:04:02
i. for everything depending on which database you're using a your program made behave very differently
00:04:09
i'm obviously not functional at all on
00:04:14
and it's not fun i mean it's just and that's really important to me
00:04:19
um i i think it's important to enjoy the work we do and we deserve libraries
00:04:23
that are fun to use uh in g. d. c. doesn't doesn't really do it for me
00:04:28
so a few years ago or library a call the the b. uh that kind of
00:04:34
try to fix some of those problems and and it it it's not that um
00:04:39
so it's a pure functional g. d. c. libraries learn of side effects
00:04:44
um spilled on the free monad so you can have custom interpreters which is kinda cool if you wanna do testing
00:04:49
uh and want to provoke things that are really hard to to do in in with a real database
00:04:55
the operations map one to one with j. d. c. so if you understand
00:04:59
g. d. c. you can make sense of the d. v. probe program um
00:05:04
and and the best thing is that all these we factoring tricks that we
00:05:08
get with functional programming which is kind of the whole point of doing functional programming
00:05:12
you can do all this stuff now with your g. d. c. programs which you
00:05:15
can't do you know when job or whatever so so that's that's a big one
00:05:20
um but there are also some issues still um you are still using j. d. c. j.
00:05:25
d. b. c. is that um and there's only so much you can sweep under the rug
00:05:31
a diagnostics are really bad when something goes wrong it's hard to know uh uh
00:05:35
what's gone wrong that's partly the folder g. d. c. probably the folder to be um
00:05:42
i think although derivation of code x. is probably bad
00:05:46
i think it's a mistake to design mistake um you end up spending a lot of time
00:05:51
a convincing the derivation machinery to derive the thing you
00:05:55
want uh so uh i think i can improve on that
00:05:59
and i you know it works it's not that fine right it's it's better but it's still not a joy to use it
00:06:05
i would you see the bars really low it's better than that but it's it's not what i want
00:06:11
so last year i started thinking about how i would build one of these things just up from nothing on it
00:06:17
the it guy but i want with the behaviour that i want um a using new technologies so
00:06:26
a circuit in new project called skunk and there's no logo yet
00:06:32
in here the big ideas um first of all i care about is post press that's database i
00:06:37
use that's what most people uh seem to use uh so that's on worrying about the supporting postscript
00:06:43
there is new indirection through driver it talks straight to the database over the network
00:06:50
uh so uh you get very high fidelity
00:06:53
uh uh with uh the semantics of postscript
00:06:59
completely non blocking so you don't have to worry about the tools and stuff like that
00:07:04
uh the a. p. i. uh that mirrors the way close press like to think about things so you don't
00:07:09
have the sort of least common denominator layer that everything gets squeezed like you do with j. d. b. c.
00:07:16
uh have a lot of effort into error recording and i'm issues and demonstrations that uh right at the end
00:07:23
and uh oh yeah an adult with really good technology so just in
00:07:28
the last like two years we've gotten to the point we can build really
00:07:32
just complete applications using full uh you using a
00:07:36
pure functional style uh that was typed little libraries uh
00:07:40
and really these libraries are doing all the heavy lifting this
00:07:44
is just an application of these libraries all hard stuff uh is
00:07:48
kind of given to me for free uh by using these things so um so that's what i want to talk about today
00:07:56
um our goals um when a little bit about how postscript work setting it's kind of interesting
00:08:01
uh see some a. p. i. designing a tackle styles that's a fact
00:08:06
uh the functional network programming some demos and and i i be
00:08:12
inspired to uh_huh to look into this stuff and and think about this
00:08:15
kind of programming i'm even if you're not in the market for database
00:08:18
library um i hope there'll be something here uh for you find interesting
00:08:26
oh
00:08:28
so um the compositional nature of functional programming kind of a type tends toward bottom
00:08:35
up programming the heck technical programs you compose and make bigger programs and can build things
00:08:40
up um so we're gonna start uh the talk uh doing some bottom up work and
00:08:45
then we'll come back you gotta destination given where you're going so we're gonna we're gonna
00:08:49
flip over and look and look at the high level stuff and and and and see what it looks like in the second half the top
00:08:59
right so i first pro talk we're gonna talk about uh talking to postscript
00:09:05
so here's the big picture or uh clients and servers communicate every t. c. pieces socket programming we're doing
00:09:12
uh out and they communicate by sending these a tag link prefixed
00:09:18
messages back and forth okay and the payload is interpreted in different ways depending on
00:09:24
what that message type tag is so logically this is just an e. d. t.
00:09:29
they're fifty three different messages uh to the to the different tags
00:09:33
and depending on the tag at the payload meets a different things
00:09:37
uh and that's it so everything you can do with post press can be done by exchanging those messages
00:09:43
and it's documented it's so awesome it's one of the reason for
00:09:46
post press is really good software's their doctors really good chapter fifty two
00:09:51
uh it specifies this there's no secret knowledge here there's no
00:09:55
reverse engineering it's all very plainly spelled out which is fantastic
00:10:01
right so we need to do some for functional socket programming um and
00:10:05
was think about what a scholar interface for a socket might look like
00:10:09
okay well here's one of this attack was interface uh which is a style of programming the parameter
00:10:14
is is everything over the effect and which values are computed so that that is the fact that
00:10:20
um so it's probably gonna be something like io in the
00:10:23
real world um in a real uh no instantiation of the straight
00:10:28
um but you can mark something up using you know state t. v. or or
00:10:32
or something like that if you wanted to simulate stuff or test or test things
00:10:37
um i need to fundamental operations we to read some number bites me
00:10:42
to write some number bites uh and both of those operations can time out
00:10:46
uh in which case and an error or will be raised in this in this stuff effect
00:10:52
so it it turns out there's already a an implementation a lot like this it's provided by the first two
00:10:58
so they have a socket um and looks a lot like uh what i was looking for
00:11:04
um uses a single the chunk unstable by the razor chunk chunk
00:11:08
it just kind of it's like a it's like an immutable right
00:11:12
um another difference is that when we read a chunk we get back nine it uh if the socket is terminated uh before we get that
00:11:18
many bytes so it's not exactly what we want virtually close so we'll
00:11:22
we'll start with this look look at how we construct one of these sockets
00:11:27
so there's there's a um is the factory method that takes an i. net socket address
00:11:33
uh and a bunch of other things that have reasonable defaults and a bunch of constraints
00:11:37
and if you do programming with cats a fact you'll know about concurrent in context just
00:11:41
uh uh if you don't oh i'm gonna come back to him in a few minutes
00:11:45
um okay like what but what is that um that's kind of an annoyance we have to
00:11:50
deal with we need to to get an asynchronous channel group somehow and that's an an io thing
00:11:55
uh that the tapestry needs in order to construct this non blocking a an io socket
00:12:00
down down and it gets uh so we gotta deal with that um and
00:12:05
they're improving that right now actually so this is a little bit up tape
00:12:08
um and then there's this thing um so this factory method does not return a
00:12:13
socket and doesn't even return after socket or translate resource of half of socket um
00:12:20
so so what's that okay so resource is defined kind of like this um
00:12:27
it's close enough so um it's a monad which is useful you can get to
00:12:35
well only backup so what resource is a it is a
00:12:39
dated five three cats affecting it for for working with um
00:12:43
lifetime manage values okay that need to be cleaned up when you're done using okay um
00:12:49
so it's a monad which means you can take resources in compose them
00:12:52
together so you get three resources you can flat map them together and
00:12:56
you get a new big resource and when you're done with that all
00:12:58
three components will get cleaned up when you're done was nice compositional properties
00:13:03
um and this is the important bit right here um when the user
00:13:08
resources can see this uh manage value winning compute some value in half
00:13:13
what you get back from this call isn't half okay and i
00:13:18
uh this lets you can get out of resource and back to your normal uh a fact type and
00:13:23
this usage has during feed clean up so no matter what happens no matter what
00:13:27
have does even if i have never i didn't have gets cancel the never completes
00:13:32
um the uh uh the resourceful be cleaned up for so it's really nice abstraction
00:13:39
okay so i just look at application uh right now because
00:13:44
we need this asynchronous channel group and and ways thing works is
00:13:47
you got a construct this thing it on the thread pool and then when you're done with the you
00:13:51
have to shut it down so that's a good application resource so let's see how we might uh construct one
00:13:58
okay sir resource the yields an asynchronous channel group looks like this
00:14:03
uh you have two parts first yeah this'll allocation program other computes the
00:14:08
manage value and what we're doing is we're constructing it inside sink the way
00:14:13
okay so let's let's look at what sink does that sink is uh something from
00:14:18
cats effect that's kind of the interface with the side effecting part of the language
00:14:23
so it takes a side effect as an argument and then what it
00:14:26
returns a is a value it will compute that side effect at run time
00:14:31
and that whole itself is not a side effect so uh so our code is is it still pure
00:14:37
okay so we have that allocation that creates the channel group um and then we have a
00:14:42
free program at it takes an shuts it down again and away because that's a side effect
00:14:48
okay and then uh we can just a resource dot make with the allocation and
00:14:53
the free and that gives us a resource uh the well you'll asynchronous channel groups
00:14:59
and that we have one of those we can construct a stock
00:15:02
so here we go we've got a method detects hosting the string and uh it creates the asynchronous channel
00:15:09
group them flat maps that into the socket constructor that
00:15:13
there's two provides gay and the result is a resource
00:15:17
it gives us a socket when we're done using at the soccer will be closing the channel group will be close
00:15:23
this doesn't compile 'cause we don't have the right constraints so
00:15:27
the compiler errors will tell us that we need concurrent in context
00:15:34
okay and and when i write code in this style intact with style this is what i do a radical but i want
00:15:40
and then i rely on the compiler errors to tommy which
00:15:43
constraints i need a so that i don't accidentally over constrain stuff
00:15:48
so we're finally ready to write a program that actually uses the socket
00:15:52
okay so here's a program uh that is in iowa up
00:15:56
okay uh let's talk for men about what i overlap is
00:16:02
so um it's a trade provided by cats affected you pure functional in three point for your program
00:16:08
so it just computes an i. o. value and then io apt takes care of actually side
00:16:13
effecting in running it for use of the b. side effect is not part of your program
00:16:18
uh it also gives you some type class instances that are useful it's your
00:16:22
timer for i allude to the non blocking to a non blocking a sleeping
00:16:27
uh it gives you context chef that's what allows computations to shift
00:16:31
between fred pools uh and because that's available you get a concurrent
00:16:36
as for io and that lets you run things in parallel that she for controlling computations
00:16:43
oh okay so once this do
00:16:46
uh we create a socket and then we use it you're gonna pit bull because we don't have enough structure to talk to posters yet
00:16:54
um then we're just gonna arbitrator arbitrarily read that the first two hundred fifty six bytes we get back
00:17:00
uh and that gives an optional traverse the option uh to
00:17:03
print out whatever we've gotten we'll just turned into an ascii string
00:17:07
okay so this works but um the way i like to write these programs is
00:17:12
instead of having run do everything already run f. program or write a run of method
00:17:17
uh that is parameter eyes on its effect okay nice in
00:17:21
in in the run method i instantiate run after high oh
00:17:25
you can reason i do that is because uh when i re factor things
00:17:28
out of this main method into my larger program i don't end up with bits
00:17:31
and pieces there specialist i oh i want as much as like it i
00:17:35
want it as much of my clothes possible to be parameter eyes on the effect
00:17:40
um the compiler tells us with that we need the concurrent
00:17:43
contact them okay so we can run this uh and it works
00:17:51
okay so this is fine um but we don't wanna be dealing in chunks uh we wanna be dealing a binary data
00:17:57
type it's easy to work with because uh we need to encode these clothes crest messages going to talk about but the actors
00:18:06
so here's a current socket interface and what we want is something
00:18:11
like this okay so instead of going with chunks were young bit vectors
00:18:15
because that's what s. codec uses for encoding and decoding uh binary data
00:18:20
and that's what we're gonna be using it was talk about that picture
00:18:23
um bit vector acts like a sequence of boolean it's okay but the data is all
00:18:27
packed into bytes on and easy to convert back and forth to to buy the rights
00:18:33
really cool feature um is that this concatenation operation is constant time
00:18:38
so if you're familiar with the chain data type from cats it's a lot like that um it's cheaper cheaper optimist uh for
00:18:44
dealing with binary data so if that is what you're doing this is the data type you wanna be using uh it's it's fantastic
00:18:52
um and also i i internalise this time out some just gonna say what can i create one
00:18:56
of these things all specify my time it out so i don't have to to to do that everywhere
00:19:01
okay and instead of returning option a on read we're just gonna
00:19:05
deal with the end of uh the end of socket error then the
00:19:08
file a as an error and it gives us a simpler interface to
00:19:13
given a socket we should be able to make one of these things
00:19:17
okay so here's our method uh we have a socket and a
00:19:21
couple durations and what we wanna get back is a bit vector socket
00:19:25
okay so how can we implement this so for we eat what we're gonna do is
00:19:30
or read the bites and if we got a chunk will turn into a bit vector
00:19:35
and we didn't get one we're going to gently raise an error and have we're not
00:19:39
throwing yeah we're we're handing it to often have is gonna continue uh in a normal uh
00:19:44
normal functional execution uh it'll just have this this or an utter channel
00:19:50
okay and and then the compiler will tell us we need the monad yeah yeah
00:19:55
we need more net error uh because we are flat mapping and we're raising errors
00:20:00
then the right method is easy just take the big picture in turns it into a chunk and then writes about
00:20:05
okay so now we can either method that constructs this but the actor a socket by constructing wrapping regular socket
00:20:13
okay so we take the host port time outs and
00:20:16
uh we create a socket which is resource uh uh we
00:20:22
the the yeah which create the resource and then we just use map to turn the socket into the picture socket
00:20:27
so now we have the type we want when we're done with all the
00:20:29
underlying stuff will be cleaned up and the compiler tells us we need those constraints
00:20:36
so here's our original program uh the user regular
00:20:40
socket and here's one is a bit that socket okay
00:20:45
um it doesn't look that different except now we're using this thing called u. t. f. eight from uh s. codec
00:20:51
uh and that's a thing that knows how to encode a strings into into a bit
00:20:57
actors and decode strings from pictures will come back to this uh in a little while
00:21:03
any case um we run this thing and uh the networks the same thing looks the same but
00:21:09
now we have a better a t. i. because we're talking bit vectors we can start encoding messages
00:21:16
so talk about messages again uh they look like this there's a tag any length prefixed a lot
00:21:23
so it turns out when you connect the post grass you open socket and the first
00:21:27
thing you do is you stand the startup message so this is how we're gonna represent um
00:21:32
words for now we're just gonna send the user name in the database uh inner started message
00:21:37
and if you have security set up in a very lax way it'll say okay you're logged in and that's and that's all you need so that's what we're gonna do right now
00:21:44
um so now we the way to encode this for the network and we're going to
00:21:47
use it and as codec encode or to do this so this is one colour looks like
00:21:52
uh it's a conquering in a data type and as a matter that says given and
00:21:57
they give me an attempt a bit vector an attempt is just user error or bit vector
00:22:04
so this just gives you away to fail i think
00:22:08
so here's a start at message and here is how we can encode the pay
00:22:12
what okay this is the part of the specific to uh to the start of message
00:22:17
so the spec says to do it this way so the first thing we're gonna do is in code
00:22:22
a a a i a thirty two bit integer with this magic
00:22:25
number and that number just specifies the protocol version that we're speaking
00:22:28
um and then we're gonna encode a series of key value pairs
00:22:33
which are uh see their c. strings are there null terminated strings
00:22:38
so user and the database and then we and the whole thing uh with an all with the zero
00:22:43
okay and then each of these things that we're encoding a a a returns a bit
00:22:48
vector and we use this awesome constant time and to uh adam altogether and we're done
00:22:55
um we have some commonality here in between the way we're treating
00:22:59
user and database so we can make a commentator to deal with that
00:23:03
so uh this takes a key and i guess beckoning coder for string uh
00:23:07
that one could be key and the strain uh as a is null terminated strings
00:23:14
so here's our old encode or uh and we can create new one that's a little shorter it's just an example of how you can
00:23:21
re factor stuff and and and and and uh a factor stuff out really easily and functional code
00:23:28
um the start message is a special case it doesn't actually have a tag
00:23:32
so we don't need to worry about that but it does have a link prefix
00:23:35
uh so we can add another commentator to do that so there's a link prefix
00:23:40
commentator takes in colour and returns an equivalent in code or a bit prefixes the payload
00:23:45
uh with the length so what are we gonna do we're gonna uh run the original encode or get a a bit vector back
00:23:52
then we're going to encode a thirty two bit in a jerk uh it is the
00:23:56
it's the the size of the table divided by eight because the size of it
00:24:00
uh and then we add for because it's the length think
00:24:03
the links of the entire message including the links of the message
00:24:09
right so our final encode what's like that so it's just a link prefixed encode or for the people
00:24:16
your previous method looks like this uh and we're speaking h. t. t. p. and now we're gonna
00:24:22
talk to to a postscript case we're gonna connect to the post press pork were news that's socket
00:24:29
okay so we're gonna create the startup message will use the start up in coder to get
00:24:33
a bit vector out of it when you're right it's the socket and then do the same thing
00:24:37
uh with the results were just gonna read uh the the first twenty thirty six bytes
00:24:42
and if it works so we get it's something back right
00:24:47
post presses answering it's getting something giving us something that looks like
00:24:51
you know it it it might be meaningful so what we need to do now is figure out how to decode those messages
00:24:58
so uh there fifty three messages uh altogether but a good
00:25:03
way to start is just the code everything the same way
00:25:06
so will decode every message like this uh just a a tag which is a single byte
00:25:12
uh but they end up being a ascii characters will will turn
00:25:15
into character uh and then the data just as a robot factor
00:25:20
so how do we the code a bit vector into the data
00:25:22
type i'll use it the colours of the contrary and a data type
00:25:27
also from us good like this just the dual of encode or so it takes bits and tries to turn them into when i
00:25:35
so how we read on a message
00:25:38
well here's how we do it so given a bit vector socket we read five bytes
00:25:45
okay and then what we're gonna do is we're gonna create
00:25:50
a a decoder by uh by composing these two things so uh
00:25:55
by it is a a decoder the with the because single
00:25:58
byte in thirty two will decode a a thirty two bit integer
00:26:03
we can compose them together with the school twiddle thing uh
00:26:06
which basically uh is a way to construct left leaning nested pairs
00:26:11
okay so what we get back is a uh the tag in the
00:26:15
length and we can also destruction using that little which is kind of nice
00:26:19
um and then we're gonna do is we're gonna read length minus four
00:26:24
bytes that because we have to subtract the four bytes that we've already read
00:26:28
and we're gonna constructor and and uh and let's see what this looks like an poses we need that for that
00:26:35
so um we don't know how many messages we're getting back after we send the start
00:26:39
of message so we're just gonna write when pure they'll just uh read an unknown message
00:26:45
uh and then print it out and then do the same thing forever so we'll just read forever and it controls seated to stop or program
00:26:53
so here is our program that we had before that reads two hundred fifty six bytes in
00:26:57
print some out and one just calls this dinner started method that we were to run yes
00:27:04
um okay now we're getting somewhere right we've got a a a a bunch of messages you get something
00:27:10
tag within are a bunch of things tag that asinine okay in the noisy so we're getting something that
00:27:18
so let's step up another level
00:27:22
so we know how to read and write bit vectors okay we know how to encode and decode messages
00:27:27
so really just a matter of writing our um all the cases and we can make something like this
00:27:33
okay message socket and this is from skunk uh and this is where we really
00:27:38
wanna be because uh this uh this the socket that speaks in terms of postscript messages
00:27:43
your front yeah back and messages that come from database in front and messages that you
00:27:47
send to the database okay i don't have time to go through this phone fermentation but
00:27:51
i hope you can see that we have all the pieces we need uh to to
00:27:55
build this stuff so you can uh draw the rest of the all in your head
00:28:01
and let's talk about what really is happening uh when we do the startup exchange
00:28:06
so we sent us target message suppose grass and then we're gonna get back
00:28:10
an authentication okay a message and a whole bunch of these parameters status message
00:28:14
and these tell us things about the run time state of the database
00:28:18
and about the properties of our sessions we get things like the times um
00:28:22
and uh and the uh the style in which dates are formatted and
00:28:26
that sort of thing can we get some backing key data this is used
00:28:31
cancel long running operations you can connect on another socket and
00:28:35
pass this secret information back and we'll cancel what you're doing
00:28:39
and then finally get ready for querying that means okay we're all set we're done
00:28:42
and and we're we're our connection is established and now we can start doing database that
00:28:49
so here's our all finish startup routine uh and now we can do better
00:28:57
so what we can do is now though if we have a message socket we can pattern matcher messages that
00:29:01
we are getting back to uh and we can uh uh do different things i depending which messages we get it
00:29:10
so what are we doing here uh we've got a loop uh it
00:29:16
what we're gonna do it build up a map of all those parameter all those database parameters we get back so
00:29:23
we will receive the message uh if it's ready for query then we're done with this can return the accumulated map
00:29:28
if we get a parameter status message we will add it to the map and go back and re person to another
00:29:34
uh do another received otherwise we'll just ignore the message and and and the back and we'll start with it in the map
00:29:41
or so before uh these are all program with the bit vector socket
00:29:47
and here's the new one uh this is this is a a or
00:29:51
this is real a skunk types so make a bit vector socket from
00:29:55
that we make a message socket we're gonna send the startup message directly
00:30:00
uh and then uh we'll call our finish startup routine uh which is gonna
00:30:05
return all those parameters that we read normal traverse the listing for them out
00:30:10
and we were when we run that i'm i'm doing this with an instrumental version of the code
00:30:15
that shows the messages going back and forth and there's the the result that we got back okay
00:30:20
so we're we're we're very close to being able to something useful let's let's try to run a query
00:30:25
um the query exchange for simple the simple query protocol looks like this you send a query message the query in it
00:30:33
you get back a row description that tells you information about the columns and their types and their names
00:30:39
you get a whole bunch of row data back and it's just a huge rose a list of noble strings
00:30:44
uh you get something that says there no more rows back and then you get ready for period
00:30:49
and i'm ready to to do the next thing so let's talk about how we can do that
00:30:55
so given a message socket oh let's build up let let's let's make a useful commentator here so
00:31:04
this expect f. commentator is kind of what flat map what it does
00:31:07
is it takes a partial function instead of uh instead of a regular function
00:31:11
also like we're gonna receive this thing uh we're gonna see the
00:31:15
message and if we have a handler for will run the handler otherwise
00:31:17
will raise an error i. n. f. and that way we can be a little bit more optimistic about the way we're dealing with messages
00:31:24
uh we can make another one uh that is the kind that
00:31:28
the the the did similar it's somewhat to map is equivalent to map and we divide internal expect to have
00:31:35
okay so we'll run a query we're gonna get better rose but your bro davis back
00:31:40
so here we have a a general method uh there's been accumulate all those rows back
00:31:46
okay and it's similar to what we had before will call expect that instead of flat map
00:31:51
if we have a row data uh we will work curse and adding the row data uh to our list
00:31:57
if we have command complete that means they're no more rows we can reverse the listing return so it's very similar to what we did before
00:32:06
okay and now here is our final uh sort of sub program uh for processing results
00:32:12
so we send the query and then we get back a row description which will ignore
00:32:16
will unroll the raw data and then we will assume uh we we will expect
00:32:21
to get ready for query message and then assuming we do uh will yield the rose
00:32:28
so here's the message areas the program we had before uh but now it after we uh do the finnish start
00:32:35
up a a instead of printing that stuff our we're gonna do is run a query so we'll send a query message
00:32:41
uh with a a a simple query to our database and then we'll call process resulting from the results out
00:32:49
i only run this a bunch of messages go by and we yeah these are the
00:32:57
uh all the stuff that's going past and here's the data that we've collected okay so
00:33:03
we have gone from zero or like the literally nothing going
00:33:07
from zero to being able to run a query uh again
00:33:10
suppose grows with node g. d. b. c. driver okay um and it hasn't been a whole lot of trouble to do this
00:33:16
so i i hope you can see how we can we can sort of take this this
00:33:20
pretty simple bases and and and and and built up something but assuming it's it's uh it's useful
00:33:27
on this ended up being way easier than expected it to be a so that we talk about that kind of a whole
00:33:34
stack of stuff uh that we've got so at the very bottom
00:33:39
with that socket and abstracts away that the t. c. p. stuff
00:33:43
i noticed two provides that none with a bit vector socket that we created
00:33:46
it abstracts away the by there is a message socket abstracts away the bit vectors
00:33:54
i'm not gonna show you this one uh a buffer unless it's stock it is interesting though it deals with asynchronous
00:34:01
messages turn suppose cross can send you messages uh the
00:34:05
uh i better not in response to anything that you've done
00:34:09
um and they tell you interesting things and i'm a show you an example of of
00:34:12
using that uh the cats affect uh the concurrent stuff uh uh is really important here
00:34:19
on top of that we got a protocol layer and what that does is
00:34:22
a abstract so all these message exchanges and turns them into atomic uh operations
00:34:28
uh 'cause while you're doing the simple query you can't be doing anything else
00:34:33
so that's kind of one atomic action and then at the very top there's the session object which is the the high level of good people use
00:34:40
so this is much some gonna talk about the low level
00:34:44
implementation it because there's a hundred slides initially another pretty picture
00:34:53
this is another near infrared image is taking attendance
00:34:57
record gems with g. s. a. ally uh adaptive optics
00:35:01
the star formation um star formation feel that in uh the large much black cloud which is a a
00:35:08
a little satellite galaxy the milky way it's about a hundred and fifty three
00:35:12
hundred fifty eight thousand light years from here uh so it's basically next door
00:35:20
alright so um i wanna talk about the end user a. p. i. now
00:35:24
uh because this is really the ergonomics i'm just gonna show you some some demos
00:35:30
so i need to do some things i'm on it for
00:35:38
and begin the begin of okay so here is a minimal skunk program
00:35:50
uh it connects the database and gets the current date imprints about okay
00:35:55
so uh let's look at the different pieces and io at uh we talked about iowa up
00:36:00
uh the first thing we're gonna do is that we're gonna construct a resource that will yield
00:36:05
sessions so we just give it the host and user and database and that's all we need
00:36:11
so here's our main method we're gonna do is take that session will use it so this as thing here's a session
00:36:18
no no let's do this one inside out so start off command
00:36:23
levels um so with a simple statement that is just select current date
00:36:28
uh we are saying this is a query and it it's post breast height the
00:36:33
road type that we're getting back is the date okay so this the post crest date
00:36:37
if we look at a a is a kodak for local date that means uh it will be decoded into
00:36:43
these colour type local day so if you've used to be a you'll remember the a. b. c. dot query
00:36:50
you pass a type person argument and looks up the decoder based on the type and it's all type driven um
00:36:55
i decided i don't like that pattern so in in in skunk you just uh
00:37:00
you pass decoders uh that are that speak in terms of postscript types
00:37:07
we're talking the session that we expect this uh this great return single results and we're gonna printed out
00:37:15
so let's see what that looks like one
00:37:21
okay then then huh okay so that worked printed out the
00:37:24
current date if we to uh because the body was true
00:37:30
to this resource uh we'll see all the messages to go back and forth so you can see
00:37:34
it's a lot like what we we've seen before okay any questions about this one before i move on
00:37:43
it's pretty easy okay
00:37:47
it's like uh this is okay um this one has a a little more ball together session like before
00:37:56
customer creating constructing a data type uh we get it is that it has a
00:38:01
a country code in a minute population now we're gonna create a decoder for in this looks
00:38:06
a lot like a way as codec decoders work because i stole stuff from from is correct
00:38:12
so uh this is gonna take a post press a blank padded char
00:38:18
a column uh of link three end of march are in it for column
00:38:23
and it will map that it will take the values that are are are read from those those types
00:38:30
uh uh and a pattern match on the results in construct the country so
00:38:36
this is the correct string is a codec of string uh this is a codec of yet
00:38:41
knows that they're different types here um for
00:38:45
the both of these things mapped to scholar strings
00:38:49
okay but they have to be represented with their underlying schema types because it's very important to me uh that the type mapping
00:38:55
uh use exact so we know exactly what we're we're what we're getting back and what we're and we're putting in the database
00:39:02
or select statement here uh has an argument uh
00:39:06
as a parameter so what were inter plating here
00:39:09
in do the interplay values in skunk interplay uh and coders and march arsenic coder that takes a string
00:39:17
a codec is a super type of them coder and decoder so you can use it for both and then the rose the road type is country
00:39:25
okay so we expect to get countries back okay because this is
00:39:30
a prepared statement we have a different usage pattern we use the session
00:39:34
and then we prepared a statement we're gonna get back is prepared query
00:39:39
okay then the prepared query is is able to stream results
00:39:43
so we're gonna stream this thing would pass the argument which
00:39:47
is we're gonna say any any country that start with a
00:39:51
uh is the pattern we're looking for so organised green this thing in chunks of the countries and we're gonna print 'em all
00:39:57
out so this is my first two string that we have here so all the stuff to uh can i get for free
00:40:03
um so if we run this one
00:40:10
we get these countries back you know if we look at the message exchange
00:40:21
uh we'll see okay so we'll see that we
00:40:27
we really are screaming this thing in chunk in small chunks so we only have
00:40:32
eight countries in memory at a time so we read eight uh and then uh
00:40:37
and then send them up the stream and predominant read another a so this let you string stuff in constant time
00:40:44
oh oh okay i'm all i do a couple more this one is kind of a party truck i like this one
00:40:55
okay so post grass has this has this concept call the channel
00:41:00
and uh the way that works is that you can create a channel
00:41:04
and you can send messages to it and then you can also subscribe to
00:41:09
okay and these uh end of someone publishes a message which channel you get a notification i synchronously
00:41:16
so let's run this one
00:41:22
okay nothing's happening what's look at what it does for a run it so we use
00:41:26
the session we're going for creating a we're saying look we want identify the channel called through
00:41:31
we're gonna listen to that channel and forty two is just the it's just that the buffer length that's
00:41:36
how long at so many things accumulate for blocks will take the first three of them and will print amount
00:41:42
okay so this program is highlights not really do anything doing anything right now it's not blocking any threads um
00:41:50
so now i'm over my post crush all i can say no other far if ooh
00:41:56
hello uh to okay i get my notification up here
00:42:02
okay
00:42:03
i mean started transaction
00:42:11
and send a notification nothing happens
00:42:15
into like a minute and then the notification comes through okay so um
00:42:21
and the fire notify again that program waterman accessible during the first three yeah so um
00:42:27
but you can do here is you can have you can use posters rules system to attach
00:42:32
a it to attach handlers to table so you answer road and it will notify some channel
00:42:38
writes your database program can get notified when things are happening on the
00:42:42
database you don't have to paul you don't have to re query and
00:42:44
stuff we think really powerful i've i've never seen anybody use it but
00:42:48
i think it's it's just it's a fantastically a a sort of televising feature
00:42:56
okay the last thing i wanna show you is error handling
00:43:02
so i've got a a query here and it's got some problems in it
00:43:07
um and i just wanna are trying to run this thing and see what happens
00:43:14
so okay so try to run this thing and it's
00:43:20
time we all lot of stuff about what's wrong um so
00:43:26
it's not what the error code is the fact that it is an error where was
00:43:30
raised in the post press source if you really wanna they can see what's going on
00:43:35
uh it says that con doesn't exist maybe this other one does exist it was where the statement
00:43:41
was the fine so we can uh we can click on that and get back to our source
00:43:47
uh it it identifies the position in the query where the error happened and
00:43:51
even tells you um if it turns out that's an error that you wanna
00:43:54
trap in your program uh this is the name of the error and this
00:43:58
is the way you can trap in your program and and handle it okay
00:44:02
so uh let's correct the error a word so
00:44:08
how you wish and traded yeah and it um
00:44:13
okay so now it's telling us that this operator doesn't exist
00:44:16
you can't compare integers and vouchers okay and the reason uh
00:44:21
it it knows the stuff that knows the exact types uh
00:44:24
of the uh uh of the parameters because it specify them um
00:44:30
so what good programs with this cast this to work for a contract and
00:44:37
now what happens injured proving you're right uh sorry other way around
00:44:49
i
00:44:52
okay uh now it gets a little bit farther and not saying
00:44:56
the the uh the actual and uh uh sorted column types are
00:44:59
different so we're saying that we want to read down here a
00:45:03
bar chart fall binding for that's not apart or um or or uh
00:45:09
this is not a uh this is not an for a this is a a point had a chore
00:45:15
so we can fix that okay so uh it's sort of it's sort of guiding us toward um
00:45:24
fixing all these programs and hopefully giving us up clues know what to do okay so now it's got a little farther and
00:45:29
it says and work um that's not a valid integer and
00:45:33
string through uh and it's showing us where what the arguments are
00:45:38
and also times where the argument for supply because the query and the
00:45:42
arguments may appear in different parts of the programs you get that information to
00:45:48
i think we change this to actual yeah and it will
00:45:55
what ah oh yeah so now it's saying uh exactly one rose expect
00:46:02
but we got more so we said this is gonna be a unique but it's not something
00:46:06
we can say when they want to make sure we only get one or a back at most
00:46:12
or exactly one okay now the finale is it doesn't it doesn't actually do anything but all the years ago
00:46:20
so okay so that's all i got i think about time to it so um
00:46:27
i hope this is interesting i think uh kind of under delivered on the yeah
00:46:35
uh on the abstract sort of because didn't actually read it before you came to talk
00:46:41
uh what happened there we go
00:46:44
have some contact information are so if you're interested in this project um some
00:46:48
i get her uh uh it is not ready to use do not use it
00:46:52
uh but i want to get a a potentially usable version of it out uh in
00:46:57
the next few weeks working on the documentation i think this call doc is really good
00:47:02
um so uh if the stuff interests you i take a look and
00:47:06
feel free to experiment get in touch i'm happy to talk about this stuff
00:47:10
uh and that's all i got thank you
00:47:19
um and i'm i'm happy to take questions if anybody has any
00:47:24
it's if you
00:47:30
uh_huh
00:47:34
uh_huh
00:47:38
i think the only question i have is about longer on programs
00:47:45
for example location how do you do ices management in this case
00:47:51
you are okay so for a long running program something i didn't show you is is um pool connections
00:47:56
so i in these programs creating a single connection but um you can
00:48:01
uh there's also cool and it's it's it's the interface looks the same as a resource to
00:48:05
get you string gives you sessions but when you're done with them they get they get sort
00:48:10
of cleaned up and reset and and a check is done the make sure there's still healthy
00:48:14
and put them back in the pool so what you would do in a case like that
00:48:17
um is uh like it it's easy to press the you're using for instance uh it
00:48:22
your program is just gonna be basically a resource and part of one of the things you're constructing
00:48:28
is gonna be the session pool and another will be your web server and and and and so
00:48:33
uh essentially it will run for the whole like their program a new control c.
00:48:37
and everything i get shut down which you don't really care about the basically um
00:48:42
this you would create a session at the outermost shell of your program and pass that down and
00:48:47
and use it that way if you're not using something like the city press if using you know uh
00:48:53
some some other web server uh you can create this pool and just side effect locally
00:48:59
to get about to get the the the resource out and and and pass that around
00:49:04
yeah as long as you only do that once you're you're you're okay so at the edge your program
00:49:08
sometimes you nasty stuff like that if you say to be for us then everything's pure all the way through
00:49:15
yes oh thanks for talking i was wondering is done or
00:49:22
core of your lot rooted you could extract um for protocol development
00:49:29
um i i wouldn't i wouldn't go that far it's pretty um
00:49:38
it's pretty specific to this use case it would be nice to be
00:49:41
able to do that there's a hundred research in in uh uh doing protocol
00:49:47
stuff uh with much more specific much more precise types and what i'm using
00:49:51
so you can you can write code uh where the but the types uh
00:49:56
the types drive these these exchanges you you you
00:50:00
can uh stuff doesn't compile a few things on on
00:50:04
it's a bit is it kind of research out there what i'm doing is gonna be nice but i can get away with
00:50:09
so maybe some day i i i i wouldn't i wouldn't say good right now
00:50:21
i'm curious if you might uh reconsider your should reach a some
00:50:28
talks a are right so um the problem with derivation is not
00:50:36
it's not a speed so much it's uh it's so what will happen all the time is
00:50:44
uh for instance uh with an example you get a string right and you won a uh
00:50:51
you want you want to map a some database type into a string
00:50:55
there are different data type it database types that might happen the string
00:50:59
i and vice versa uh and uh in order to for instance in
00:51:03
order make duties query tracker happy you might have to create a new type
00:51:08
uh it just to convince the derivation machinery to pick a
00:51:11
different underlying g. d. c. type uh too so six string with
00:51:15
um and and what i've found in in writing to
00:51:18
be programs i spend a lot of time constructing new types
00:51:22
uh in order to select new instances just in order to get
00:51:26
the derivation machinery to give me the instance that i want so um
00:51:31
it's the kind of thing that we could you know i can yeah i
00:51:34
could do it uh we could uh automate some of this stuff and i might
00:51:39
decide that the way i have a a where it's all kind of uh uh explicit
00:51:44
uh ends up being a pain and it's easier to to make it type trip and i just wanted to try this other
00:51:48
approach because i i think i've come to the conclusion really the code x. that encode and decode is really not type class
00:51:54
it's more they're more like parser commentators and for matters um that uh i i think this is
00:52:00
kind of a sliding scale the stuff that really wants me type driven stuff that really wants to be
00:52:04
i'm sort of a constructed manually and this that's kind of in the middle and i i think you can go both ways
00:52:11
so it's it's a good question i don't know the answer but i'm a try and see what happens
00:52:17
the wheels is one way in the back i think
00:52:21
so you won't like it
00:52:26
uh_huh
00:52:29
uh_huh
00:52:33
um thank you for your talk and on a nice library ah
00:52:37
recently this year on art to the p. c. came out which
00:52:41
is very active database connectivity for job ah that's not that's cool
00:52:46
like scott scores ah i'm bought some what's our differences is what song
00:52:54
now what implementation both implementations differ amuse us increments channel sockets
00:53:00
and if our exactly i'll start with the p. c. use
00:53:05
uh i don't know very much about it um a three
00:53:11
mm my big might be the issue with really not with the synchrony it was
00:53:16
with the him precision of of talking to
00:53:19
a database through this g. d. c. intermediary
00:53:23
'cause it mate marks around with the type mappings to re write your queries it does all kinds of stuff
00:53:29
uh that make it really hard to know what's going on when you're talking to the database
00:53:33
so you know like if you've ever tried to do with dates and times and stalking to database
00:53:37
is just a complete nightmare 'cause you never know what she sees gonna do what the drivers go
00:53:42
um so that's really what i wanted to get away from so i haven't really been paying that much attention to the new
00:53:47
it's increased really see that's that's a really good question i think maybe do we will be
00:53:52
able to switch so that um uh if if it ends up being better yeah good question

Share this talk: 


Conference Program

Welcome!
June 11, 2019 · 5:03 p.m.
1574 views
A Tour of Scala 3
Martin Odersky, Professor EPFL, Co-founder Lightbend
June 11, 2019 · 5:15 p.m.
8337 views
A story of unification: from Apache Spark to MLflow
Reynold Xin, Databricks
June 12, 2019 · 9:15 a.m.
1267 views
In Types We Trust
Bill Venners, Artima, Inc
June 12, 2019 · 10:15 a.m.
1569 views
Creating Native iOS and Android Apps in Scala without tears
Zahari Dichev, Bullet.io
June 12, 2019 · 10:16 a.m.
2232 views
Techniques for Teaching Scala
Noel Welsh, Inner Product and Underscore
June 12, 2019 · 10:17 a.m.
1296 views
Future-proofing Scala: the TASTY intermediate representation
Guillaume Martres, student at EPFL
June 12, 2019 · 10:18 a.m.
1157 views
Metals: rich code editing for Scala in VS Code, Vim, Emacs and beyond
Ólafur Páll Geirsson, Scala Center
June 12, 2019 · 11:15 a.m.
4695 views
Akka Streams to the Extreme
Heiko Seeberger, independent consultant
June 12, 2019 · 11:16 a.m.
1552 views
Scala First: Lessons from 3 student generations
Bjorn Regnell, Lund Univ., Sweden.
June 12, 2019 · 11:17 a.m.
577 views
Cellular Automata: How to become an artist with a few lines
Maciej Gorywoda, Wire, Berlin
June 12, 2019 · 11:18 a.m.
386 views
Why Netflix ❤'s Scala for Machine Learning
Jeremy Smith & Aish, Netflix
June 12, 2019 · 12:15 p.m.
5026 views
Massively Parallel Distributed Scala Compilation... And You!
Stu Hood, Twitter
June 12, 2019 · 12:16 p.m.
958 views
Polymorphism in Scala
Petra Bierleutgeb
June 12, 2019 · 12:17 p.m.
1113 views
sbt core concepts
Eugene Yokota, Scala Team at Lightbend
June 12, 2019 · 12:18 p.m.
1656 views
Double your performance: Scala's missing optimizing compiler
Li Haoyi, author Ammonite, Mill, FastParse, uPickle, and many more.
June 12, 2019 · 2:30 p.m.
837 views
Making Our Future Better
Viktor Klang, Lightbend
June 12, 2019 · 2:31 p.m.
1682 views
Testing in the postapocalyptic future
Daniel Westheide, INNOQ
June 12, 2019 · 2:32 p.m.
498 views
Context Buddy: the tool that knows your code better than you
Krzysztof Romanowski, sphere.it conference
June 12, 2019 · 2:33 p.m.
394 views
The Shape(less) of Type Class Derivation in Scala 3
Miles Sabin, Underscore Consulting
June 12, 2019 · 3:30 p.m.
2321 views
Refactor all the things!
Daniela Sfregola, organizer of the London Scala User Group meetup
June 12, 2019 · 3:31 p.m.
514 views
Integrating Developer Experiences - Build Server Protocol
Justin Kaeser, IntelliJ Scala
June 12, 2019 · 3:32 p.m.
551 views
Managing an Akka Cluster on Kubernetes
Markus Jura, MOIA
June 12, 2019 · 3:33 p.m.
735 views
Serverless Scala - Functions as SuperDuperMicroServices
Josh Suereth, Donna Malayeri & James Ward, Author of Scala In Depth; Google ; Google
June 12, 2019 · 4:45 p.m.
936 views
How are we going to migrate to Scala 3.0, aka Dotty?
Lukas Rytz, Lightbend
June 12, 2019 · 4:46 p.m.
709 views
Concurrent programming in 2019: Akka, Monix or ZIO?
Adam Warski, co-founders of SoftwareMill
June 12, 2019 · 4:47 p.m.
1974 views
ScalaJS and Typescript: an unlikely romance
Jeremy Hughes, Lightbend
June 12, 2019 · 4:48 p.m.
1377 views
Pure Functional Database Programming‚ without JDBC
Rob Norris
June 12, 2019 · 5:45 p.m.
6374 views
Why you need to be reviewing open source code
Gris Cuevas Zambrano & Holden Karau, Google Cloud;
June 12, 2019 · 5:46 p.m.
484 views
Develop seamless web services with Mu
Oli Makhasoeva, 47 Degrees
June 12, 2019 · 5:47 p.m.
785 views
Implementing the Scala 2.13 collections
Stefan Zeiger, Lightbend
June 12, 2019 · 5:48 p.m.
811 views
Introduction to day 2
June 13, 2019 · 9:10 a.m.
250 views
Sustaining open source digital infrastructure
Bogdan Vasilescu, Assistant Professor at Carnegie Mellon University's School of Computer Science, USA
June 13, 2019 · 9:16 a.m.
375 views
Building a Better Scala Community
Kelley Robinson, Developer Evangelist at Twilio
June 13, 2019 · 10:15 a.m.
245 views
Run Scala Faster with GraalVM on any Platform
Vojin Jovanovic, Oracle
June 13, 2019 · 10:16 a.m.
1342 views
ScalaClean - full program static analysis at scale
Rory Graves
June 13, 2019 · 10:17 a.m.
463 views
Flare & Lantern: Accelerators for Spark and Deep Learning
Tiark Rompf, Assistant Professor at Purdue University
June 13, 2019 · 10:18 a.m.
380 views
Metaprogramming in Dotty
Nicolas Stucki, Ph.D. student at LAMP
June 13, 2019 · 11:15 a.m.
1250 views
Fast, Simple Concurrency with Scala Native
Richard Whaling, data engineer based in Chicago
June 13, 2019 · 11:16 a.m.
624 views
Pick your number type with Spire
Denis Rosset, postdoctoral researcher at Perimeter Institute
June 13, 2019 · 11:17 a.m.
245 views
Scala.js and WebAssembly, a tale of the dangers of the sea
Sébastien Doeraene, Executive director of the Scala Center
June 13, 2019 · 11:18 a.m.
661 views
Performance tuning Twitter services with Graal and ML
Chris Thalinger, Twitter
June 13, 2019 · 12:15 p.m.
2003 views
Supporting the Scala Ecosystem: Stories from the Line
Justin Pihony, Lightbend
June 13, 2019 · 12:16 p.m.
163 views
Compiling to preserve our privacy
Manohar Jonnalagedda and Jakob Odersky, Inpher
June 13, 2019 · 12:17 p.m.
301 views
Building Scala with Bazel
Natan Silnitsky, wix.com
June 13, 2019 · 12:18 p.m.
565 views
245 views
Asynchronous streams in direct style with and without macros
Philipp Haller, KTH Royal Institute of Technology in Stockholm
June 13, 2019 · 3:45 p.m.
304 views
Interactive Computing with Jupyter and Almond
Sören Brunk, USU Software AG
June 13, 2019 · 3:46 p.m.
681 views
Scala best practices I wish someone'd told me about
Nicolas Rinaudo, CTO of Besedo
June 13, 2019 · 3:47 p.m.
2707 views
High performance Privacy By Design using Matryoshka & Spark
Wiem Zine El Abidine and Olivier Girardot, Scala Backend Developer at MOIA / co-founder of Lateral Thoughts
June 13, 2019 · 3:48 p.m.
754 views
Immutable Sequential Maps – Keeping order while hashed
Odd Möller
June 13, 2019 · 4:45 p.m.
277 views
All the fancy things flexible dependency management can do
Alexandre Archambault, engineer at the Scala Center
June 13, 2019 · 4:46 p.m.
389 views
ScalaWebTest - integration testing made easy
Dani Rey, Unic AG
June 13, 2019 · 4:47 p.m.
468 views
Mellite: An Integrated Development Environment for Sound
Hanns Holger Rutz, Institute of Electronic Music and Acoustics (IEM), Graz
June 13, 2019 · 4:48 p.m.
213 views
Closing panel
Panel
June 13, 2019 · 5:54 p.m.
400 views