Last active
November 6, 2018 13:21
-
-
Save thinkharderdev/3bf2b2d5162bec12b97837ab0a8184dc to your computer and use it in GitHub Desktop.
Sharpen The Saw #scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed trait UserRole | |
case object BasicUser extends UserRole | |
case object Admin extends UserRole | |
// I can very easily define basic data types using case classes | |
case class User(id: String, name: String, email: String, role: UserRole) | |
//Defining User as a case class has a couple of implications | |
// You don't need to use the "new" key word when instantiating an instance | |
val user = User("id","Dan","foo@bar.com", Admin) | |
// What this is actually doing under the hood is calling the apply method on the User companion object. | |
// So the above expression is desugared to | |
val user = User.appl("id","foo@bar.com",Admin) | |
// In this case, the scala compiler is auto-generating the User companion object and apply method | |
// The scala compiler will auto-generate a bunch of useful stuff for case classes. Such as... | |
// A copy constructor for creating shallow copies of my object | |
def makeAdmin(user: User): User = user.copy(role = Admin) // here we can just copy the original object with a new role | |
// The "scaffolding required for pattern matching | |
def isAdmin(user: User): Boolean = user match { | |
case User(_,_,Admin) => true | |
case _ => false | |
} | |
// An object is just a singleton, but one that is enforced by the runtime. | |
// It can have internal state but it is only initialized once | |
// A "companion" object is just an object that is a "companion" (that is, has the same name) as a class | |
class MyClass(state: String) | |
// Here is MyClass's "companion object" | |
object MyClass { | |
// In this case we just have a static constant. In the java world this would be a static variable on MyClass itself | |
// In Scala, by covention anything that would be declared static in Java just goes in the companion object | |
val SOME_STATIC_CONSTANT: String = "foo" | |
} | |
// But, there is also some syntactic sugar associated with objects | |
// For example, the apply method can be called using just parens. So | |
object MyObj { | |
def apply(s: String): String = ??? | |
} | |
// We can call MyObj.apply like so | |
MyObj("foo") | |
// Which gets desugared to | |
MyObj.apply("foo") | |
// As noted before, this is what is happening with case classes. The compiler is auto-generating the companion object and apply method, | |
// which is why we don't need to use the new keyword |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Methods in scala can be defined with multiple separate argument lists. For example | |
// from the Traversable trait we have an example | |
// This method starts with a base value z and then traverses the collection applying the pairwise operation op | |
// to the output of the previous iteration and the next element | |
def foldLeft[B](z: B)(op: (B, A) => B): B | |
// So, I if I wanted to sum a list on Ints I could do it like so | |
val ints = List(1,2,3,4) | |
ints.foldLeft(0)((sum,next) => sum + next) // result is 10 | |
// This is for a more concise syntax (IMHO). But also allows for easy currying. | |
// For example we could do something like this | |
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) | |
val numberFunc = numbers.foldLeft(Lis.empty[Int]) | |
val squares = numberFunc((xs, x) => xs:+ x*x) | |
print(squares.toString()) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) | |
val cubes = numberFunc((xs, x) => xs:+ x*x*x) | |
print(cubes.toString()) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// An expression can a normal assignment statement like we see in other languages | |
val e1 = "foo" | |
// It can also be a "block" which is a series of simple expressions which takes the type of the last simple expression to execute | |
val e2: String = { | |
val inner = "foo" | |
if (inner.startsWith("f")) "goo" else "foo" | |
} | |
def myMethod(arg: String): String = { | |
s"$arg-someextrastuff" | |
} | |
// We can call our method the "normal" way | |
myMethod(e2) | |
// But we can also pass a complex expression as an argument to a method | |
myMethod { | |
val inner = "foo" | |
if (inner.startsWith("f")) "goo" else "foo" | |
} | |
// This really makes immutability less onerous because we can just initialize immutable vals to the return value of a block | |
// So instead of writing | |
val user: User = ... // | |
var username = null; | |
if (user.isBusinessUser) { | |
username = user.businessName | |
} else { | |
username = user.username | |
} | |
//We can write | |
val username = if (user.isBusinessUser) user.businessName else user.username |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Pattern matching is kina of like case/switch on steroids. | |
// Using plain "vanilla" pattern matching we can do a number of things very nice and concisely | |
// Example copied shamelessly from https://docs.scala-lang.org/tour/pattern-matching.html | |
abstract class Notification | |
case class Email(sender: String, title: String, body: String) extends Notification | |
case class SMS(caller: String, message: String) extends Notification | |
case class VoiceRecording(contactName: String, link: String) extends Notification | |
// We can do basic pattern matching on case classes | |
def showNotification(notification: Notification): String = { | |
notification match { | |
case Email(email, title, _) => | |
s"You got an email from $email with title: $title" | |
case SMS(number, message) => | |
s"You got an SMS from $number! Message: $message" | |
case VoiceRecording(name, link) => | |
s"you received a Voice Recording from $name! Click the link to hear it: $link" | |
} | |
} | |
val someSms = SMS("12345", "Are you there?") | |
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") | |
println(showNotification(someSms)) // prints You got an SMS from 12345! Message: Are you there? | |
println(showNotification(someVoiceRecording)) // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 | |
// We can also use "guards" to condition the match | |
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = { | |
notification match { | |
case Email(email, _, _) if importantPeopleInfo.contains(email) => | |
"You got an email from special someone!" | |
case SMS(number, _) if importantPeopleInfo.contains(number) => | |
"You got an SMS from special someone!" | |
case other => | |
showNotification(other) // nothing special, delegate to our original showNotification function | |
} | |
} | |
val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com") | |
val someSms = SMS("867-5309", "Are you there?") | |
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") | |
val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!") | |
val importantSms = SMS("867-5309", "I'm here! Where are you?") | |
println(showImportantNotification(someSms, importantPeopleInfo)) | |
println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) | |
println(showImportantNotification(importantEmail, importantPeopleInfo)) | |
println(showImportantNotification(importantSms, importantPeopleInfo)) | |
// Or we can match on type only. This is like doing instanceof in Java | |
abstract class Device | |
case class Phone(model: String) extends Device{ | |
def screenOff = "Turning screen off" | |
} | |
case class Computer(model: String) extends Device { | |
def screenSaverOn = "Turning screen saver on..." | |
} | |
def goIdle(device: Device) = device match { | |
case p: Phone => p.screenOff | |
case c: Computer => c.screenSaverOn | |
} | |
// In a way, pattern matching is just syntactic sugar for Extractor objects. I won't cover that here for time | |
// but checkout https://docs.scala-lang.org/tour/extractor-objects.html for more info obout how to "customize" | |
// pattern matches. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scalaz._ | |
import Scalaz._ | |
def sum(ns: List[Int]): Int = ns.foldRight(0)((sum,next) => sum + next) | |
def all(bs: List[Boolean]): Boolean = bs.foldRight(true)((sum,next) => sum && next) | |
def concat[A](ss: List[List[A]]): List[A] = ss.foldRight(List.empty[A])((sum,next) => sum ::: next) | |
trait Combiner[A] { | |
def combine(l: A, r: A): A | |
def zero: A | |
} | |
def genericSum[A](as: List[A])(implicit c: Combiner[A]): A = { | |
as.foldRight(c.zero)((sum,next) => c.combine(sum,next)) | |
} | |
implicit val intCombiner = new Combiner[Int] { | |
override def combine(l: Int, r: Int) = l + r | |
override def zero = 0 | |
} | |
implicit val boolCombiner = new Combiner[Boolean] { | |
override def combine(l: Boolean, r: Boolean) = l && r | |
override def zero = true | |
} | |
genericSum(List(1,2,3),intCombiner) | |
def genericerSum[M[_],A](as: M[A])(implicit c: Combiner[A], folder: Foldable[M]): A = { | |
folder.foldRight(as, c.zero)((sum,next) => c.combine(sum,next)) | |
} | |
implicit def pairCombiner[A,B](implicit aCombiner: Combiner[A], bCombiner: Combiner[B]): Combiner[(A,B)] = new Combiner[(A,B)] { | |
override def combine(l: (A, B), r: (A, B)) = (aCombiner.combine(l._1, r._1),bCombiner.combine(l._2,r._2)) | |
override def zero = (aCombiner.zero,bCombiner.zero) | |
} | |
genericerSum(List(1,2,3)) | |
genericerSum(Set(1,2,3)) | |
genericerSum(List((1,true),(2,true),(3,false))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Scalac will infer that foo is of type String | |
val foo = "foo" | |
//But you can also make the type explicit | |
val foo: String = "foo" | |
//Scalac will also infer type arguments | |
def myFunc[S](arg: S) = ??? //Here myFunc has a type parameter S which defines the type of it's argument arg | |
//When invoking myFunc, scalac will infer the type argument by the value passed to it | |
myFunc("foo") //Scalac infers that the type argument is String since arg is of type String | |
val l = List(1,3,3) | |
case class MyContainer[T](maybeValue: Option[T]) | |
case class MyOtherContainer[T](value: T) | |
//Here's a contrived example where we let the compiler infer all type arguments | |
l.map(n => MyContainer(Some(n))) | |
.collect { | |
case MyContainer(Some(v)) if v < 3 => MyOtherContainer(v) | |
} | |
//Now the same example where we explicitly provide all type arguments | |
l.map[MyContainer[Int],List[MyContainer[Int]]](n => MyContainer(Some(n))) | |
.collect[MyOtherContainer[Int],List[MyOtherContainer[Int]]] { | |
case MyContainer(Some(v)) if v < 3 => MyOtherContainer(v) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment