class: center, middle background-color: black .title[Programming Strategically with the Principle of Least Power] .author[Leif Wickland]
--- background-image: url("assets/IMG_20160417_170409.jpg") .medium[ @leifwickland] --- # Credit to Li Haoyi .right[
] - Strongly inspired by his [blog](http://www.lihaoyi.com/). - Wicked smart. - Scala.js. Ammonite. - Good parts are his. - Anything stupid is mine. - Originally he wrote about Scala. --- class: left, middle background-image: url(assets/icbm.jpg) # Strategic
Power --- # Principle of Least Power .quote[ Given a choice of solutions, pick the least powerful solution capable of solving your problem. ] — [Li Haoyi](http://www.lihaoyi.com/post/StrategicScalaStylePrincipleofLeastPower.html) --- # Developers seek power & flexibility .right[
] - _My design is so flexible!_ - _I'm not exactly sure how I'm going to do it, so…_ - _The requirements might change, so…_ --- # With Great Power - If it can do anything it _might do anything_ - Bigger possible space to consider --- # Motivation .quote[ A restricted solution that … can only do a few things is straightforward for someone to inspect, analyze and manipulate later. ] — [Li Haoyi](http://www.lihaoyi.com/post/StrategicScalaStylePrincipleofLeastPower.html) --- # Vocabulary check ## _Power_ -- ## _Restriction_ -- ## _Possibility_ -- ## _Constraint_ --- background-image: url("assets/luggage.jpg") --- background-image: url("assets/porter.jpg") --- class: middle .huge[ Constraints liberate ] --- # Essay .quote[ transitive verb:
to try to do, perform, or deal with ] ## Can we program stategically using the principle of least power in other languages, too? --- # What I'm not talking about - Picking a language - Picking a framework - Picking a library - Most of us don't get to pick those on a daily basis --- class: center, middle
--- ```cs switch (n) { case 1: cost += 25; break; case 2: cost += 25; goto case 1; case 3: cost += 50; goto case 1; default: Console.WriteLine("Invalid selection."); break; } ``` From [MSDN `goto` docs](https://msdn.microsoft.com/en-us/library/13940fs2.aspx) --- .huge[ 1968: _Go To Statement Considered Harmful_ ] I promised you details about our languages becoming more powerful in _recent_ years. --- class: center, middle .huge[ PHP 5.3 added `goto` in 2009 ]
--- class: center, middle .huge[ PHP 5.3 added `goto` in 2009 ]
--- ```java Baz baz = null; while (true) { // 20 lines calculating foo… if (!foo.isValid()) break; // 30 lines calculting bar… if (!bar.isValid()) break; // Another 30 lines calculating wah… if (!wah.isValid()) break; baz = new Baz(foo, bar, wah); break; } return baz; ``` --- ```java Foo foo = calculateFoo(); Bar bar = calculateBar(); Wah wah = calculateWah(); if (foo.isValid() && bar.isValid() && wah.isValid()) { return new Baz(foo, bar, wah); } else { return null; } ``` --- class: middle, center .huge[ Superior because complexity is constrained ] --- class: middle, center .huge[ Immutability changes everything ] --- class: middle, center .huge[ Easier to reason about because fewer possibilities ] --- ```javascript var label; if (havingGoodDay(bob)) label = "ok"; else label = "bitter"; ``` -- ```javascript // ----- or ----- const label = (havingGoodDay(bob)) ? "good" : "bitter"; ``` --- background-image: url("assets/const.png") padding: 0px 0px 0px 0px background-size: contain; background-repeat: no-repeat; background-position: center; .footnote[From [caniuse.com](http://caniuse.com/#feat=const)] --- background-image: url("assets/let.png") padding: 0px 0px 0px 0px background-size: contain; background-repeat: no-repeat; background-position: center; .footnote[From [caniuse.com](http://caniuse.com/#feat=let)] --- ```javascript function tellMeAboutYourFeels(bob) { var feels = "meh"; if (goodDay(bob)) feels = "hawt"; else if (badDay(bob)) feels = "bitter"; return feels; } ``` -- ```javascript // ----- or ----- function tellMeAboutYourFeels(bob) { if (goodDay(bob)) return "hawt"; if (badDay(bob)) return "bitter"; return "meh"; } ``` --- # Prefer Immutabilty - As far as possible - Except if you really need the perf improvement - Spoiler: Very little of your code does - Limit the scope of mutabilty --- background-image: url("assets/doublej.jpg") padding: 0px 0px 0px 0px background-size: contain; background-repeat: no-repeat; background-position: center; --- ```java List<String> names = myBffs(); // ----- or ----- const List<String> names = myBffs(); // ----- or ----- ImmutableList<String> names = myBffs(); // ----- or ----- const ImmutableList<String> names = myBffs(); ```
---
```java List<String> names = myBffs(); ```
```java const List<String> names = myBffs(); // ----- or ----- ImmutableList<String> names = myBffs(); // ----- or ----- const ImmutableList<String> names = myBffs(); ```
.medium[ No Double Mutability] --- class: center, middle .medium[ **Thought experiment:** What's the _simplest_ interface to your module? ] --- class: center, middle .huge[Design:] .medium[ Interface to module to find cycles between nodes in graph ] --- ```java class Node { String name() } ``` -- ```java class Graph { void addEdge(Node from, Node to) boolean hasCycle() } ``` --- # Looks Simple, but… - Have to instantiate a `Graph` and `Node`s - Can only be used with these data types - `Node` can't store anything but a string - Limited interface for defining graph's connections --- # How can we make it better? - Abstract `Node` type - User can define `Node` to hold whatever - Abstract `Graph` type - User chooses how to define connections --- class: middle ```java abstract class Node { String name() } abstract class GraphBase
{ Set
getNodes() Set
connectsTo(Node node) boolean hasCycle() } ``` --- # Typical Approach - Most of us have been taught OOP-y design - Feels natural to have a class for each "thing" - I don't think it's the best --- .huge[ How would we reduce the setup? ]
-- .medium[ Avoid instantiation and subclassing? ] --- ```java class GraphUtil { static boolean hasCycle
( Set
nodes, Function
> connectsTo(Node n) ) } ``` --- # Haoyi's Ranking of Interfaces 1. Static method with **standard** types 2. Static method with **custom** types 3. Class which must be **instantiated** to be called 4. Abstract class which must be **subclassed** ??? Think in terms of usabilty for the person calling this code. Hopefully, you are probably the first user of your code when you write tests. More likely to write tests --- background-image: url("assets/lambda.png") ??? Now that Java has added closures nearly every language has closures Difference between closure and lambda --- # Closure vs Class -- - Both wrap (potentially mutable) state - Both encapsulate data - Both have method(s) which can be invoked -- - Closure "constructors" are plausible - Closure only has one method - You can fake more with stringy dispatch -- - Please don't --- # Passing Classes to Functions - Generally if a function needs a thing which can do stuff, we'll pass that object to it - Often gives the function too much power - Can access unrelated data - Can access unneeded methods - Tight coupling produces brittleness - Can potentially mutate the object --- # Pass Closures, not Classes - What if instead we pass more restrictive, less powerful thing? - Provides a nice abstraction layer without explicitly defining interface - Receiver can only take one action with closure - Shared interface without inheritance --- # Architectural Hotspot Patterns - Unstable interface - Implicit cross-module dependency - Unhealthy interface inheritance hierarchy - Cross-module cycle - Cross-package cycle .footnote[[Adrian Colyer's blog](https://blog.acolyer.org/2016/06/10/hotspot-patterns-the-formal-definition-and-automatic-detection-of-architecture-smells/)] --- ```java class Node { String name() Set
connectsTo(Node n) } class Graph { static boolean hasCycle(Set
nodes) } // ----- vs ----- class GraphUtil { static boolean hasCycle(Set
nodes, Function
> connectsTo) } hasCycle(nodes, node::connectsTo) ``` --- class: koan background-image: url(assets/two-monks-blur-16-9.jpg) .huge[ Koan] --- class: koan background-image: url(assets/two-monks-blur-16-9.jpg) .footnote[By Anton van Straaten From [MIT CSAIL list](http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html) via [C2](http://c2.com/cgi/wiki?ClosuresAndObjectsAreEquivalent)] .huge[ The Closure Koan] ??? The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" -- .medium[ Objects are good] ??? Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures." -- .medium[ Poor man's closures] ??? Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress. -- .medium[ Intent on Studying] ??? On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." -- .medium[ Closures are truly good] ??? Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." -- .medium[ Poor man's objects] ??? At that moment, Anton became enlightened. --- # The Better Option: Simpler Error Handling - `Option`, `Optional`, or `Maybe` in most langauges - If not in standard then a third party library -- - Allows you to indicate either a value **or** failure - No confusion which is which - _Is that_ `-1` _a value or a failure code?_ -- - Short circuit evaluation bails out on error - Pairs exquisitely with lambdas --- class: middle .huge[ `Option` is exactly either `Some(value)` or `None` ] --- ```scala for ( x <- Some(5) y <- Some(7) ) yield x * y // Results in Some(35) ``` --- ```scala for ( x <- Some(5) y <- None ) yield x * y // Results in None ``` --- ```java Baz baz = null; while (true) { // 20 lines calculating foo… if (!foo.isValid()) break; // 30 lines calculting bar… if (!var.isValid()) break; // Another 30 lines calculating wah… if (!wah.isValid()) break; baz = new Baz(foo, bar, wah); break; } return baz; ``` --- # How can we make it better? - Separate functions to make `Foo`, `Bar`, and `Wah` -- - Only create valid objects - Use `Option` to indicate possibility of invalid --- ```scala def makeFoo(): Option[Foo] def makeBar(): Option[Bar] def makeWah(): Option[Wah] for ( foo <- makeFoo() bar <- makeBar() wah <- makeWah() ) yield new Baz(foo, bar, wah) ``` --- ```java Optional
makeFoo() { …} Optional
makeBar() { … } Optional
makeWah() { … } return makeFoo().flatMap(foo -> makeBar().flatMap(bar -> makeWah().map(wah -> new Baz(foo, bar, wah); ))); ``` --- # The Better Option: Simpler Error Handling - Avoids common pitfalls: - Not supposed to use `throw` for flow control - `null` is the _Billion Dollar Mistake_ [according to inventor](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare) - Don't have to remember to check error code --- # Limited `Option` - We can't say what went wrong - There are other types, like `Either`, to express more detailed failure --- background-image: url("assets/bowtie.gif") --- # Summary - Deliberate constraints simplify code use and maintenance -- - Better predictability makes refactoring and testing easier -- - More powerful solution is not necessarily better -- - Mo' power, mo' problems! --- # How? - Immutability - Constrained Interfaces - Lambdas to Restrict Coupling - `Option` to Express Possible Failure --- background-image: url("assets/LWW_8015.jpg") .medium[ @leifwickland]