Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Equality in Scala (ScalaMatsuri 2020)

740 views

Published on

A talk gave at ScalaMatsuri 2020 (October 17th, 2020) about the details of equality handling in Scala.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Equality in Scala (ScalaMatsuri 2020)

  1. 1. 自由平 Eugene Yokota (@eed3si9n) 等Equality in Scala
  2. 2. 1998年
  3. 3. OOPSLA: 1998 Growing a Language Guy Steele 「言語を成長させる」
  4. 4. small vs large language 1. Small language: short time to learn 2. Large language: more vocabulary to use 小さい言語は習うのが容易 大きい言語はより多くの語彙を使うことができる
  5. 5. primitive • "A primitive is a word for which we can take it for granted that we all know what it means." プリミティブとは、それが何を意味するのか自明とできる言葉
  6. 6. language that can grow 1. APL: users cannot define symbols 2. LISP: user-defined words look like the primitives APL は優れた言語だが、ユーザはシンボル演算子を定義不可 LISP はユーザ定義でプリミティブ的なものを作れる
  7. 7. language that can grow 1. Cathedral (one design) vs Bazaar (no one plan) 2. plan can change to meet the user 寺院設計は 1つの大計画、商店街は複数の計画を持つ ユーザのニーズに応えて計画を変更することができる
  8. 8. making Java growable 1. Generic types 2. Operator overload Java を成長させる ジェネリック型と演算子のオーバーロード
  9. 9. 複素数
  10. 10. OOPSLA 1998 「過去のために将来を安全なものにする」 Newspeak 作者、小田好先生、Haskell 作者
  11. 11. Java virtual machine https://www.artima.com/insidejvm/ed2/jvmP.html int c = a + b; iload_0 // push local var0 iload_1 // push local var0 iadd istore_2
  12. 12. https://www.artima.com/insidejvm/ed2/jvmP.html int a = 1; java.util.Vector xs = new java.util.Vector(); xs.add(java.lang.Integer.valueOf(1)); primitive types boxed primitives スタックに直に積めるのがプリミティブデータ型 ヒープ上にオブジェクトとして包むことをボックス化と言う 1
  13. 13. 2001年
  14. 14. Lightweight Language 1. Easy to learn and use 2. Widely-used languages are coming from industry 3. Perl, Python, Ruby, (Scheme) http://ll1.ai.mit.edu/cfp.html LL言語とは何だったのか。実用言語が産業側から来てるとい うアカデミアにおける危機感。ライトなのは学びやすさ。
  15. 15. 2008年
  16. 16. a scalable language スケーラブルな言語
  17. 17. "The name Scala stands for scalable language. The language is so named because it was designed to grow with the demands of its users. You can apply Scala to a wide range of programming tasks, from writing small scripts to building large systems." 'Programming in Scala', Odersky, Spoon, Venners Scala という名前は「スケーラブルな言語」を意味する ユーザーの求めに応じて成長していけるように設計されている Scala: a scalable language
  18. 18. "Guy Steele noted in a talk on "growing a language" that the same distinction can be applied to language design. Scala is much more like a bazaar than a cathedral, in the sense that it is designed to be extended and adapted by the people programming in it." 'Programming in Scala', Odersky, Spoon, Venners Guy Steele は「言語を成長させる」という講演の中で 言語設計も「伽藍とバザール」が区別できると言っている Scala: a scalable language
  19. 19. Scala 1. Abstract types 2. Operator overload 抽象型 演算子オーバーロード
  20. 20. purely object-oriented 1. Everything-is-a- object, message passing 1 + 2 1.+(2) 純粋オブジェクト指向言語 全てがオブジェクト、全てがメッセージ・パッシング
  21. 21. purely object-oriented https://www.scala-lang.org/files/archive/spec/2.13/12-the-scala-standard-library.html 1. Everything-is-a- object, message passing
  22. 22. complex number in Scala (Spire) scala> import spire.implicits._, spire.math._ import spire.implicits._ import spire.math._ scala> Complex(1.0, 2.0) + Complex(3.0, 5.0) val res0: spire.math.Complex[Double] = (4.0 + 7.0i) scala> Complex(1.0, 2.0) * Complex(3.0, 5.0) val res1: spire.math.Complex[Double] = (-7.0 + 11.0i) Scala における複素数
  23. 23. Scala 1. Intuitive and OO number types, like Python/Ruby 2. Extensibility 直観的かつオブジェクト指向な数値型 拡張性
  24. 24. ==
  25. 25. what is equality? •substitution property •for any a, b, and f(x): a == b ⇒ f(a) == f(b) •equivalence relation 等価性とは代入原理を満たす同値関係の1つ
  26. 26. equivalence • reflexive • for any a (except NaN): a == a • symmetric • for any a, b: a == b ⇒ b == a • transitive • for any a, b, and c: a == b && b == c ⇒ a == c 同値関係は反射律、対称律、推移律
  27. 27. equivalent, but not equal 合同な図形は同値関係にあるが等しくは無い these shapes are congruent in some context they could be considered "same"
  28. 28. https://www.artima.com/insidejvm/ed2/jvmP.html • boolean • char • byte, short, int, long float, double primitive types in Java プリミティブ型
  29. 29. 1.if either operand is double, widen both to double 2.else if either operand is float, widen both to float 3.else if either operand is long, widen both to long 4.else widen both operands to int numeric promotion in == operation == 演算における数値昇格 double や long など広い方の型に拡張変換する 1 == 1.0
  30. 30. https://www.artima.com/insidejvm/ed2/jvmP.html primitive types equality in Java プリミティブ型は == を用いて比較。数値昇格が行われる。 128 == 128 // true // true via numeric promotion JLS 15.21.1 1 == 1L
  31. 31. https://www.artima.com/insidejvm/ed2/jvmP.html // false Integer.valueOf(128) == Integer.valueOf(128) // Error: incomparable types: java.lang.Integer and java.lang.Long Integer.valueOf(128) == Long.valueOf(128L) // true Integer.valueOf(128).equals(Integer.valueOf(128)) // false Integer.valueOf(1).equals(Long.valueOf(1L)) primitive types boxed primitives equality in Java 128 128 ボックス型は equals を用いて比較。数値昇格は無し。 128 == 128 // true // true via numeric promotion JLS 15.21.1 1 == 1L
  32. 32. // true Integer.valueOf(128).equals(Integer.valueOf(128)) // 128 Integer.valueOf(128).hashCode() // 128 Integer.valueOf(128).hashCode() reference types equals and hashCode 128 128 2つのオブジェクトが equal な場合は 同じ hashCode() を返す約束 •if two objects are equals, they must return the same hash codes
  33. 33. == in Scala 1. For value types, it uses numerical equality 2. For reference types, java.lang.Object#equals that can be overridden https://www.scala-lang.org/docu/files/ScalaOverview.pdf 値型は数値的等価性を用いる 参照型はオーバーライド可能な Object#equals を使う
  34. 34. eq in Scala 1. For reference types, use eq for reference equality. 参照的等価性を使いたい場合は eq メソッドを使う
  35. 35. cooperative equality 協調的等価性 == の実装レベルでボックス型でも数値昇格を エミュレート。さらっとボックス化するための仕組み。 scala> java.lang.Integer.valueOf(128) == java.lang.Long.valueOf(128L) val res1: Boolean = true scala> (128: Any).getClass val res2: Class[_] = class java.lang.Integer scala> (128: Any) == (128L: Any) val res3: Boolean = true scala> Set(128, 128L, "foo") val res4: scala.collection.immutable.Set[Any] = Set(128, foo) == emulates numeric promotion of boxed primitives
  36. 36. cooperative equality cont. == は equal(...) と異なることに注意 hashCode() の代わりに ## というものが用意されている scala> (128: Any) == (128L: Any) val res3: Boolean = true scala> (128: Any).equals(128L: Any) val res5: Boolean = false == is different from equals(...) ## is different from hashCode() scala> (1.0: Any).hashCode() val res6: Int = 1072693248 scala> (1.0: Any).## val res7: Int = 1
  37. 37. scala/bug#11551 Scala 2.13.0-RC3 の段階で吉田さんが Set のバグを発見 https://twitter.com/xuwei_k/status/1134972015226449920 https://twitter.com/not_xuwei_k/status/1135372481156526081
  38. 38. scala/bug#11551 scala> Set[AnyVal](1L, 2L, 3L, 4L, 5L) + 1 res1: scala.collection.immutable.Set[AnyVal] = HashSet(5, 2, 3, 4, 1, 1) 1 is duplicated! 集合なのに1が重複しまっている
  39. 39. scala/bug#11551 scala> Set[AnyVal](1L, 2L, 3L, 4L, 5L) + 1 res1: scala.collection.immutable.Set[AnyVal] = HashSet(5, 2, 3, 4, 1, 1) 1 is duplicated! 修正は equals(...) を == で置き換えること
  40. 40. scala/bug#11551 警告機能の提言
  41. 41. Display warning on equals comparing non- references #8120 Scala 2.13.4 から警告を出すようにしました scala> def check[A](a1: A, a2: A): Boolean = a1.equals(a2) ^ warning: comparing values of types A and A using `equals` is unsafe due to cooperative equality; use `==` instead check: [A](a1: A, a2: A)Boolean I implemented the warning
  42. 42. BCodeBodyBuilder プリミティブ型の比較は Java 同様数値昇格が行われる val tk = tpeTK(l).maxType(tpeTK(r)) genLoad(l, tk) genLoad(r, tk) genCJUMP(success, failure, op, tk, targetIfNoJump) primitive type == is handled by genComparisonOp(...) same numeric promotion as Java
  43. 43. BCodeBodyBuilder cont. 参照型の比較は、異なるボックス型の場合 BoxesRunTime その他の場合は null 比較もしくは equals を用いる reference type == is handled by genEqEqPrimitive(...) 1.if both sides are different boxed primitive types are boxed floating point, use scala.runtime.BoxesRunTime. 2.if LHS is null literal, RHS eq null 3.if RHS is null literal, LHS eq null 4.if statically non-null (like "foo"), use Object#equals 5.otherwise, if (null eq this) null eq that else this.equals(that)
  44. 44. universal equality 1. scala.Any (compiler fiction) defines == and equals 2. since all values are a member of scala.Any, they can be compared using == 普遍的等価性 全ての値同士を == を用いて比較できる
  45. 45. unsound equality 絶対に真を返すことのできない式を書ける これを不健全 (unsound) であると言う scala> 1 == "1" ^ warning: comparing values of types Int and String using `==` will always yield false val res0: Boolean = false scala> Option(1) == Option("1") val res1: Boolean = false == admits expressions that can never be true
  46. 46. unsound equality Java の == は強い型付けを持っている 数値の取り扱いをナイスにする代わりに型安全性が犠牲に scala> Option(1) == Option("1") val res1: Boolean = false Java has static and strongly-typed == java.util.Optional.of(1) == java.util.Optional.of("1"); | Error: | incomparable types: java.util.Optional<java.lang.Integer> and java.util.Optional<java.lang.String> | java.util.Optional.of(1) == java.util.Optional.of("1"); | ^----------------------------------------------------^ scala> (128: Any) == (128L: Any) val res3: Boolean = true all for numeric niceness?
  47. 47. 2010年
  48. 48. Eq typeclass Scalaz は型クラスを用いた独自の === 等号を導入 コンパイル時に検査をするようになったが、Someを扱えない trait Equal[A] { self => def equal(a1: A, a2: A): Boolean } scala> 1 === 1 val res0: Boolean = true scala> 1 === "foo" <console>:37: error: type mismatch; found : String("foo") required: Int 1 === "foo" ^ • Scalaz implemented typeclass-based equality • === is checked at compile-time • invariant version does not handle Some(...) vs None
  49. 49. 2011年
  50. 50. "Rethinking equality" 等価性の再考 現行の実装は長く見れば見るほど醜く見える •"Now that 2.9 is almost out the door, we have the luxury to think of what could come next. One thing I would like to address is equality. The current version did not age well; the longer one looks at it, the uglier it gets. In particular it is a great impediment for DSL design. Witness the recent popularity of === as an alternative equality operator." https://groups.google.com/g/scala-internals/c/MhIR30mYt-M/m/MHD0VHhMqoQJ
  51. 51. 2016年
  52. 52. multiversal equality 多元的等価性
  53. 53. multiversal equality sealed trait Eql[-L, -R] scala> Option(1) == Option(1) 1 |Option(1) == Option(1) |^^^^^^^^^^^^^^^^^^^^^^ |Values of types Option[Int] and Option[Int] cannot be compared with == or != scala> given eqlOption[A1, A2](using eq: Eql[A1, A2]) as Eql[Option[A1], Option[A2]] = Eql.derived scala> Option(1) == Option(1) val res0: Boolean = true scala> Option(1) == Some(1) val res1: Boolean = true scala> Option(1) == Option("foo") 1 |Option(1) == Option("foo") |^^^^^^^^^^^^^^^^^^^^^^^^^^ |Values of types Option[Int] and Option[String] cannot be compared with == or !=. |I found: | | eqlOption[Int, String](/* missing */summon[Eql[Int, String]]) • opt-in with -language:strictEquality flag or
 import scala.language.strictEquality コンパイラオプションによって使用可能となる コンパイル時に型検査。自分で Option の型クラス導出する? https://dotty.epfl.ch/docs/reference/contextual/multiversal-equality.html
  54. 54. multiversal equality still supports numeric promotion sealed trait Eql[-L, -R] object Eql { object derived extends Eql[Any, Any] // Instances of `Eql` for common Java types given eqlNumber as Eql[Number, Number] = derived given eqlString as Eql[String, String] = derived } scala> 128 == 128L val res3: Boolean = true 多元的等価性は数値昇格を行ってしまっている これは外すべき機能 https://github.com/lampepfl/dotty/blob/0.27.0-RC1/library/src/scala/Eql.scala we should remove eqlNumber instance
  55. 55. 2020年
  56. 56. cooperative equality is closed-world scala> import spire.math._ scala> Complex(1.0, 0.0) == 1.0 1 | Complex(1.0, 0.0) == 1.0 |^^^^^^^^^^^^^^^^^^^^^^^^^ |Values of types spire.math.Complex[Double] and Double cannot be compared with == or != 協調的等価性は ## の実装で協調するため閉じた世界 そのためライブラリは参加できない •requires ## implementations to be aware of each other •libraries like Spire cannot participate in this!
  57. 57. equality in Valhalla Valhalla の値の等価性は置換意味論を用いる 参照値の等価性は == であるインラインオブジェクトの参照 •Valhalla is an experimental OpenJDK project to develop value types •"Two object references are equal if they are both null, or are both references to the same identity object, or are both references to inline objects that are == to each other. This extends the substitutability semantics of == to all values – two values are == only if it is not possible to distinguish between them in any way (excepting the legacy behavior of NaN.)" https://cr.openjdk.java.net/~briangoetz/valhalla/sov/02-object-model.html
  58. 58. equality in Valhalla Java 的には数値昇格は == の前に起こっていることのなので Valhalla 後も数値昇格はそのままっぽい •numeric promotion is likely to stay in Java https://twitter.com/eed3si9n/status/1317150368380428292
  59. 59. making Scala 3 growable 多元的等価性を導入するタイミングで協調的等価性を外すこと ができれば Scala 3 をより成長させることができる •let's remove cooperative equality under strict equality mode •let's restore == and equals(...) final def == (that: Any): Boolean = if (null eq this) null eq that else this.equals(that)
  60. 60. thanks and stay healthy ご清聴ありがとうございます
  61. 61. metropolitan house supply (2020.09 mixtape)

×