SBTB 2015: James Earl Douglas, What JavaScript taught me about Programming in Scala
Recording: SBTB 2015: James Earl Douglas, What JavaScript taught me about Programming in Scala
cool so a really quick background I spent the last several years doing primarily solid development and then over the course of the last few months I've switched into primarily JavaScript development and it's been kind of an interesting journey for me hopefully this won't be too trivial for those of you who are familiar with JavaScript i'ts still consider myself very much a newbie but with this talk I want to go through just some of the lessons that I've learned by taking ideas and patterns from JavaScript and thinking about what i know about scala and how they compare and sort of what some of the advantages or disadvantages might be and then how i can fold that back into my scala development to sort of improve myself in both languages and i also want to caveat this or sort of disclaim this with this is what javascript taught me about programming in scala so there's probably certainly going to be a lot of confirmation bias and anomaly hunting that i did looking for things that i wanted to learn so i don't necessarily recommend any of these things to anyone but an audience of myself so take that with a little grain of salt so right off the bat if you jump into the world of javascript you'll find that there are a couple different ways that you can write it the these are all different languages that extend javascript in some way or give you a totally different syntax or language that compiles into javascript and I think I like this because it sort of demonstrates the lack of consensus on how to write JavaScript and there's sort of a similar thing in in the scholar world oh by the way I'm going to go really fast through these slides because I have like 50 of them so at feel free to stop me at any time I'll put on the brakes and go into detail otherwise I'm going to zip through so there's a similar pattern in Scala where depending on your background you'll come into scholar and say hey this just looks like simplified clean up java or whoa i can do type classes and this is just like my haskell that I know and I've heard the same thing from ml developers and I think you can even write basic in scholarly but with the dsl and furthermore the set of language features that scala offers is pretty robust and in sip a teen they added this modular zation so you can choose which language features you want to use and which ones you don't want to use when we're developing Scala and so from all of this the lesson that I sort of took is there's a lot of options and if you're on a team especially a very team with different backgrounds you're unlikely to sort of come to an organic consensus on how to write your code so the lesson is to sort of be intentional about picking the dialect based on who your developers are and what their experiences are and there's sort of what the appropriate style will be and then kind of move on and go forward and in our case this manifested as looking at these JavaScript languages typescript especially and finally saying well you know we're just going to write vanilla JavaScript and move on with our lives similar to this lesson is rather than language styles sort of patterns within the language so for example constructors in Java Script look like this and the idea is that name spacing and scoping in javascript is a little bit confusing for somebody who's coming from Scala and when you're using this in JavaScript you have to be careful to use new so that you're creating a new context under which this function is running and so the way to instantiate a new object in Scala sorry in javascript is either like this or like this and so the there the difference between object literals and constructors are that with object literals you're not using new you're not using this you're just creating a literal new object and returning it and again there's not really a right or wrong way this is just illustrating a difference that I encountered and actually really confused me at first how when I didn't know what was going on with the scope of this Scala has a similar sort of competing patterns especially when it comes to implicit conversions so for example let's say we want to add functions to an existing type in this case we have int and we want to add a squared function to it the very simple way is to write a squared trait and then create an implicit conversion from int to an instance of squared that has a squared function defined but another way we can do this is with full type classes and that's where we are not only defining this implicit conversion but we're defining a library that operates on values that have some conversion to that type and then can then call that squared function on behalf of the original type and again there's there's not necessarily a pro or a con or an obvious choice between these two patterns until you start to look at external pressures and experience and other kind of stylistic preferences so again this is very similar to the first lesson but the idea is that we have sort of a wealth of different options on the table and we can kind of debate them and argue about them but at some point as a development team we need to just sort of pick the set of conventions and patterns that we want to use and and move ahead in this case I extended the audience bullet a little bit to talk about because we are now talking about specifics of how we're writing our code and so that's going to affect our api's and affect our function signatures and so people who are depending on who's going to use the code whether they're just the team members or third party library users or even some kind of other API consumer all this kind of folds into consideration of what patterns you'll choose modularization is another big tricky bit in JavaScript actually it's going back to that first slide with all those languages there's a million different ways to do modular station in JavaScript and that's it's mainly because there's not really a native way to do it there's pretty much one global namespace and it's very difficult to create non-global namespaces so we primarily used nodejs so no it has its own module system and it looks like this so here I'm building a module and exporting one function from that module called square but this wouldn't work if I were to use this code in a web browser instead of a note app so if we were using a web browser it gets a little bit more noisy but the pattern that's sort of generally adopted is something like this so we have to deal with this global namespace and and to do that we're taking some object in this case it's called maths and treating that as sort of a namespace that we can add things to but it's the same idea the point here is that it's different and if you want to write a JavaScript library that works both in the browser and in a note app you have to figure out some way to combine these two which is doable is just noisy and painful and tedious luckily in Scala it's a little bit easier because we don't have all these different competing modular much lusatian standards we pretty much have packages and objects and then maybe traits or classes depending on how you define a module but again there are still sort of these considerations to take thinking about who's going to use the code that we're writing are we writing Scala code for other Scala users and if that's the case we're kind of free to use anything that we want because Scala understands Scala but if for example we're writing style code that needs to be consumed by Java developers through some API we might we might be a little bit more limited because of the way Scala generates anonymous classes and appends dollar signs and things to the names of objects things like this are all sort of important to consider and so this is the third lesson that sort of in the same class which is of all the choices that we have we have to sort of intentionally pick an ecosystem that fits our particular use case so in this case the considerations are do we need this to work in maven or SBT and if so what are the implications will we be using third-party libraries and do those impose sort of modular ization structions and on and on and on continuous integration release processes third-party users and consumers etc okay so now I'm getting more into the code itself I'm primarily a vim user so I tend to have my code open in them and then some kind of build tool open in the terminal that's that's about all I do so for me I scala adds a lot of value in that I can continuously compile it and when I have syntax errors or compilation errors I see them right away but this isn't the case in JavaScript because there is no compiler right generally if you make in this case i forgot to capitalize the w when i called hello world and i would only find out about that at runtime so of course lots of tools exist to help you and and the one that we were using was called JS hint and it's just a typical code linting tool and so like with scala you can run this then continuously and it will look at your code do static analysis and catch in this case it catches two problems it says hey you're calling a function that doesn't exist and also you wrote a function that's not called so both of those point to the problem of me fat fingering my invocation of the function so scala also has code linting tools the biggest one in my opinion being scala see if your code doesn't compile that's a big indicator that something is wrong with your code and then there are other tools Scala style is like the java check style case you're familiar but it lets you define coding conventions that you want to adhere to and then enforce those and then wart remover is a pretty cool one which is similar but sort of comes with its own at least out of the box comes with its own suggestions about what your code should and should not do and I really like wart remover because it it exits a SBT plug-in and it executes at compile time so you can you can come up with whatever standards are appropriate for your team like they'll shalt not use var or whatever and then that gets enforced at compile time and so you can catch problems like this early and so the lesson learned from all of this is do static analysis it it the compiler in Scala is your friend and the earlier you catch a problem the faster you could move and so for me that that means I use a lot of triggered execution so as i said i'll have my editor and my terminal and my terminal i'm usually running SBT with some combination of Tilda and a command so test is a common one but compiler would work as well and this way anytime i'm updating the code i'm running scala see and then any other tools like Scala style or wart remover that I might have integrated and then any sorts of convention enforcement that can be done automatically should be done automatically so I shouldn't have to run jeaious hint from my terminal when I can integrate it into my toolset integrated into my continuous integration and so forth okay so can anyone guess what this function does this is a JavaScript function for which I've hidden the implementation the short answer is no there is absolutely nothing you can infer about this function just from looking at the signature of the method except for maybe that its name is foo and I would love to go into why but I don't have enough time so what about this one this is a scala function it's almost the same but scala makes us put a type annotation on the argument and in this case i've added one to the return type so we know a little bit more about this function just something with integers whereas this function does who knows what with anything so we can start to guess what this does maybe it takes accent doubles it may be just returns 42 and ignores X so let's see if we can make this better so now we throw in a type parameter in there and gotten rid of int so now we say the function foo is parameterized on a and it takes an a and returns an a so surely some of you have seen this but can anyone guess what this function does just from the type right yeah so this says for any possible type A which I don't know what a is I the function I will return you in a and the only possible way to get an a when you don't know what a is is to return the one that comes in and this is ignoring cases like null or you know throw exceptions or whatever so the answer is this function can only possibly return its input so it is the identity function and this is just a little peek into a very interesting field called para metro city and i really recommend you read the paper theorems for free by wobbler if you're interested in learning more about this but basically the lesson is I said use generics but really I should have said be judicious about your type annotations and use generics where you can because it really constrains the scope of what a function can do and because of that it communicates a huge amount of information to other developers who might be future you or current team members and and so forth okay let's talk about testing so JavaScript doesn't have a compiler as I mentioned it does have code linting tools and so forth but testing and javascript is hugely important especially when you're talking about refactoring or just a certain correctness of your code imagine you have a huge JavaScript project and you want to rename a function you know fairly simple operation well you go and rename it and then what how do you know that you've propagated that change everywhere it needs to be at least for me when I was first getting into JavaScript and I didn't know a lot about the tools i would run it and kind of see what happened and it would fail and it was very frustrating so I learned quickly that testing is really really important so that when when I do introduce a problem like refactoring or anything else I can catch it quickly without having to deploy my system and wait for a user to find the problem but so even though Scala has a compiler and it has these other tools testing is still really important you know the type system is great generics are great but Scala is not a theorem prover right it's not it's not a fully path-dependent it's a turing-complete language so there can always be problems in your code so testing is still maybe less critical than in JavaScript but still pretty important and when you start thinking about tests as being so important they actually start to buy you a lot so you can reuse them and what I mean by reuse them is if you write a set of tests and you say this is what my system should do you can take those tests and say hey here's some documentation maybe it goes in the example section of your read me or maybe it literally becomes the documentation of how to use your system and then you can take it one step further and say you know what we only want to officially endorse the functionality that's behind our tests and in that case your tests actually become this the official specification of your software and and then you can start doing things like versioning by specification and and it gets really interesting it has tons of benefits and so I guess the lesson from all this is where you can I emphasize tests but where you can develop from the tests and so then you don't have to argue that one hundred percent code coverage is good it's awesome when you get there but it's totally not needed and it's not always practical but if you are making the tests your official standard then you're guaranteed to have a hundred percent coverage of your specification and that's pretty handy when it comes to preventing bugs in the specification from going out and then it also opens you up to really start changing the way you think about changes that you're making to your system so when people are reporting issues you can you can start to redefine what bug means and what feature requests means to the point where if if your specs if your specification tests are all passing and somebody reports something that they dislike for whatever reason you can call that a feature request and not a bug because as specified your system works great it's a little bit hand wavy but it but it does make for a very effective version management and release management strategy okay mutability I'm sure you can guess where I'm going with this one but when you have mutability unexpected things can happen so here's a really dumb example where I have a date data type of the year month in the day and i have a function that computes a date with for the next year for the following year and because i have this plus plus in there it's actually modifying the incoming data and so this function is not referential e transparent because it it does return the correct thing but it mutates the input value so i can't call it more than once without basically breaking the input data in scala we can get around this with case classes which are immutable by default and so we can write the same kind of thing and be much more confident that we're not going to have accidentally be because maybe when we wrote this we didn't understand the semantics of plus plus that it modified that it doesn't just compute a new value but modifies the original value but in this case we're not going to accidentally fall into that trap and so it's much easier to write a referential a transparent next year function when all of our code is all of our data is immutable and then in a similar vein when we start to talk about multi-threaded systems mutability becomes even more of an issue fortunately a node in many cases your entire application is a single thread so you can get away with a lot of stuff like maybe I want to hit counter on a stage ttp request service function and I can do really nasty things like this this would be totally unsafe in a multi-threaded system because we're changing hit count based on hit count and hit count itself could change while we're trying to change it because of another process so to get around things like that we can do things like make data structures that allow us to treat immutable data as though it were mutable and that's for example this date monad would let us do that and that buys us a lot in terms of being able to reason about code that in a sort of imperative mutable way but how'd it still be safe and immutable and you could do this in JavaScript as well but it would be pretty messy with the types or the lack thereof so I'm just about out of time and I have quite a bit to go which is unfortunate so let me just scroll through these real quick and I'll just talk about each of the lessons so in this case the lesson learned is avoid mutability where feasible so I also talked about and I will publish these slides for anyone who's interested in reading more and all to be around to answer questions and say I'm going kind of so fast through these but in these slides will include the rest of these lessons that I'm rushing through so one of them is configuration via code so using actual Scala to configure your Scala application instead of properties files or ini files or whatever I talk about documentation and why it's important to publish that and I even talked about why you might not want to use scala in certain cases for example startup performance popularity ecosystem adoption there are all kinds of considerations that that you need to think about I wish I wish I had time to talk about deployment deployment Tucson type is really really painful and node with the node package manager I think gets it totally right it's it's wicked easy to deploy NPM modules and I think that actually leads to more adoption cool so sorry I didn't have too much time but are there any questions on what I did cover yeah if I use this with 10 you're going to tell me oh well nice right that's probably the point okay that's fair so well so the question is when we're talking about tests as specification it's pretty important to get the completeness of the tests right because if we if we write a square function and we test that 7 times 7 is 49 and 1 times 1 is 1 right but we don't test 10 times 10 is 100 and then it turns out there's a bug in that particular case users are not going to like that and if we come back with well we didn't specify that it could square 10 right it can only score 1 and 7 yeah that's that's awful and that therein lies the trade-off so getting your tests if you're testing well if you're writing functions that operate on very wide ranges of data like integer your best bet is going to be something like you know property based testing or something like that but yeah you're not going to catch all the cases so i think that's that's a good point and maybe a follow-up to that would be try to use algebraic data types where you can to limit those kinds of problems but yeah it's a valid concern cool thank you