Share
Embed
What Can Scala Puzzlers Teach Us?
A look at some of the most common puzzlers faced by Scala beginners.
Daniel Capo Sobral
April 20, 2014
Transcript
48 slides
-
Disclaimer #1: "Puzzlers" • Java Puzzlers (the book/presentations): ◦ Weird
behavior in Java that is difficult to predict or explain • This presentation: ◦ Weird behavior in Scala that confounds beginners ▪ (and sometimes experienced programmers) ◦ FAQs, not Fun -
I can't speak for any of the people who contributed
to create and turn Scala into what it is. Furthermore, if you learn anything from this presentation, learn that a perfect understanding of Scala is an unlikely achievement... Disclaimer #2: don't trust me. -
Symbols, operators and punctuation • What does it mean? •
Where can I find information about it? • WTF? -
The Origin of the Symbols • Punctuation elements of the
language ◦ @ # ( ) [ ] { } , . ; : ` ' " • Keywords, reserved symbols and XML ◦ <- => <: >: <% // /* */ _* <? <! • Normal definitions from libraries ◦ <:< =:= ☆ ★ η :/ • Underscore ◦ All of the above! -
Symbols may be part of the language or come from
external libraries; it can be difficult to tell where they begin and end; they are difficult to search for on the Internet, non-mnemonic and unpronounceable. -
Then why? def f(xs: Array[Int], a: Int, b: Int) =
for (i <- xs.indices) xs(i) = a * i + b -
So that libraries can extend the language seamlessly def f(xs:
M[T], a: T, b: T) = for (i <- xs.indices) xs(i) = a * i + b For M and T library-types that support these operations -
Advice regarding Symbols • Learn the language: ◦ What symbols
are "built-in" ◦ The syntax rules • If exploring code using unknown libraries, ◦ Use an IDE to explore it harsh, indeed... -
implicit def KleisliCategory[M[_] : Monad]: Category[({type λ[α, β] = Kleisli[M,
α, β]})#λ] = { new Category[({type λ[α, β] = Kleisli[M, α, β]})#λ] { def id[A] = ☆(_ η) def compose[X, Y, Z](f: Kleisli[M, Y, Z], g: Kleisli[M, X, Y]) = f <=< g } } Let's try to figure this one out -
implicit def KleisliCategory[M[_] : Monad]: Category[({type λ[α, β] = Kleisli[M,
α, β]})#λ] = { new Category[({type λ[α, β] = Kleisli[M, α, β]})#λ] { def id[A] = ☆(_ η) def compose[X, Y, Z](f: Kleisli[M, Y, Z], g: Kleisli[M, X, Y]) = f <=< g } } Split Scala Syntax from Identifiers -
implicit def KleisliCategory[M[_] : Monad]: Category[({type λ[α, β] = Kleisli[M,
α, β]})#λ] = { new Category[({type λ[α, β] = Kleisli[M, α, β]})#λ] { def id[A] = ☆(_ η) def compose[X, Y, Z](f: Kleisli[M, Y, Z], g: Kleisli[M, X, Y]) = f <=< g } } Identify what we are defining -
implicit def KleisliCategory[M[_] : Monad]: Category[({type λ[α, β] = Kleisli[M,
α, β]})#λ] = { new Category[({type λ[α, β] = Kleisli[M, α, β]})#λ] { def id[A] = ☆(_ η) def compose[X, Y, Z](f: Kleisli[M, Y, Z], g: Kleisli[M, X, Y]) = f <=< g } } Where we are using our definitions -
implicit def KleisliCategory[M[_] : Monad]: Category[({type λ[α, β] = Kleisli[M,
α, β]})#λ] = { new Category[({type λ[α, β] = Kleisli[M, α, β]})#λ] { def id[A] = ☆(_ η) def compose[X, Y, Z](f: Kleisli[M, Y, Z], g: Kleisli[M, X, Y]) = f <=< g } } What's left are outside identifiers -
The Underscore • What does it mean? • Why does
it work here but not here? • WTF? • It's everywhere! It's the borg operator! -
The underscore is Scala's wildcard; it means "something" or "anything"
and even "nothing". However, it's semantics can be widely different, even in similar contexts. -
Why? • ??? • Picking a different symbol for each
case would stretch ASCII • Picking keywords is not in the spirit of Scala • They are somewhat intuitive ◦ except when they bite you -
Advice regarding underscore • Intuition does go a long way
• But, again, learn the language • And, in particular, know the difference between these: ◦ f _ ◦ f(_, y) ◦ f(x + _, y) -
Eta, partial function application and placeholders • f _ ◦
an eta expansion: turn method f into an anonymous function. • f(_, y) ◦ a partial function application: creates an anonymous function from method f, whose parameters are the ones wholly replaced by underscores. • f(x + _, y) ◦ a placeholder: creates an anonymous function from the expression "x + _", whose parameters are the underscores (and that function gets passed to f). -
Erasure • Where are my types? • How do I
get them back? • WTF? -
Some things that don't work trait X { def overload(f:
Int => Int) def overload(f: String => String) } -
Some things that don't work def f[T](list: List[T]) = list
match { case _: List[Int] => "Ints" case _: List[String] => "Strings" case _ => "???" } -
Some things that don't work class C[T] { def f(obj:
Any) = obj match { case _: T => "Ours" case _ => "Not Ours" } } -
Why? • JVM does not support "type parameters" • If
Scala added metadata to "eliminate" erasure, the interaction gap between it and Java would be much greater ◦ Though things like implicits and default parameters already do it, to some extent ◦ At least, implicits and defaults are easy to avoid -
Some things that don't work trait X { def overload(f:
Function1[Int, Int]) def overload(f: Function1[String, String]) } -
Erasure at work trait X { def overload(f: Function1) def
overload(f: Function1) } -
Erasure at work def f(list: List) = list match {
case _: List => "Ints" case _: List => "Strings" case _ => "???" } -
Erasure at work class C { def f(obj: Any) =
obj match { case _: Any => "Ours" case _ => "Not Ours" } } -
A partial solution class C[T : ClassTag] { def f(obj:
Any) = obj match { case _: T => "Ours" case _ => "Not Ours" } } though it will still ignore T's type parameters -
Initialization Order trait A { val a: Int val b
= a * 2 } class B extends A { val a = 5 println(b) } new B -
Initialization Order trait A { val foo: Int val bar
= 10 println("In A: foo: " + foo + ", bar: " + bar) } class B extends A { val foo: Int = 25 println("In B: foo: " + foo + ", bar: " + bar) } class C extends B { override val bar = 99 println("In C: foo: " + foo + ", bar: " + bar) } new C Source: Paul Phillips scala-faq by way of scala puzzlers -
Initialization Order In A: foo: 0, bar: 0 In B:
foo: 25, bar: 0 In C: foo: 25, bar: 99 -
Initialization Order trait A { val foo: Int val bar
= 10 println("In A: foo: " + foo + ", bar: " + bar) } class B extends A { val foo: Int = 25 println("In B: foo: " + foo + ", bar: " + bar) } class C extends B { override val bar = 99 println("In C: foo: " + foo + ", bar: " + bar) } new C -
Initialization Order trait A { val x = 5 }
trait B { val x: Int println(x * 2) } trait C extends A with B trait D extends B class E extends D with C { println(x * 2) } class F extends C with D { println(x * 2) } new E new F -
Initialization Order trait A { val x = 5 }
trait B { val x: Int println(x * 2) } trait C extends A with B trait D extends B class E extends B with D with A with B with C { println(x * 2) } class F extends A with B with C with B with D { println(x * 2) } new E new F -
Other things to look out for • Pattern matching on
val's and for's • All sorts of things implicit • Syntactic sugars, auto-tupling and the like • The return keyword -
So, what did I learn? It seems to me that
there's a wide range of Scala features that depend on the user having deep knowledge of the language when something goes wrong. "What's going on?" seems to be much more common than "How do I do this?" -
As for real Puzzlers... • Site: ◦ http://scalapuzzlers.com/ • Book:
◦ http://www.artima.com/shop/scala_puzzlers