Большинство из вас, вероятно, слышали о великой книге Javascript: Javascript хорошие части. В этом же свете я хотел бы показать некоторые вещи из Scalaz, которые действительно здорово использовать в повседневных проектах, без необходимости погружаться в (по крайней мере, для меня) страшную внутреннюю работу Scalaz. В этой первой части мы углубимся в ряд полезных классов типов. В следующих частях мы рассмотрим такие вещи, как монадные трансформаторы, бесплатные монады, валидация и т. Д.
Для этих примеров мы будем использовать scala REPL. Поэтому, если вы хотите поиграть, запустите scala и загрузите библиотеку scala:
| 1 2 3 4 5 6 7 8 | scala> :require /Users/jos/.ivy2/cache/org.scalaz/scalaz-core_2.11/bundles/scalaz-core_2.11-7.2.1.jarAdded '/Users/jos/.ivy2/cache/org.scalaz/scalaz-core_2.11/bundles/scalaz-core_2.11-7.2.1.jar'to classpath. scala> importscalaz._importscalaz._ scala> importScalaz._importScalaz._ | 
В этой первой статье мы рассмотрим следующие классы типов из библиотеки Scalaz:
- Класс типов equals: для безопасных операторов equals.
- Класс типов заказа: для более безопасного заказа
- Класс типов Enum : для создания многофункциональных перечислений
Кроме того, мы также рассмотрим несколько простых расширений, которые Scalaz добавляет к некоторым типам, предоставляемым стандартной библиотекой Scala. Мы не будем смотреть на все, что добавляет Scalaz, а только на несколько расширений Option и Boolean .
Полезные классы типов
С помощью классов типов вы можете легко добавить функциональность к существующим классам (см. Шаблон Pimp my library ). Scalaz поставляется с парой полезных классов типов, которые вы можете сразу использовать.
Оператор Typesafe Equals
Scalaz предоставляет типизированный оператор equals, который выдаст ошибку компиляции при сравнении недопустимых типов. Так что, хотя == и ! = В Scala позволят вам сравнивать String и Int, используя операторы Scalaz === и = / =, это приведет к ошибке времени компиляции:
| 01 02 03 04 05 06 07 08 09 10 11 | scala> 1== 1res6: Boolean = truescala> 1=== 1res7: Boolean = truescala> 1== "1"res8: Boolean = falsescala> 1=== "1"<console>:14: error: type mismatch; found   : String("1") required: Int              1=== "1" | 
Scalaz предоставляет следующий набор операторов, поведение которых можно легко увидеть из реализации функции.
| 1 2 3 4 5 | finaldef ===(other: F): Boolean = F.equal(self, other)finaldef /==(other: F): Boolean = !F.equal(self, other)finaldef =/=(other: F): Boolean = /==(other)finaldef ≟(other: F): Boolean = F.equal(self, other)finaldef ≠(other: F): Boolean = !F.equal(self, other) | 
Тип заказа
Это очень простой класс типов, который обеспечивает более безопасный тип заказа. Как и в случае с операторами Equals, теперь мы можем отследить сравнение двух разных типов во время компиляции:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | scala> 1< 4dres25: Boolean = true scala> 1lte 4d<console>:14: error: type mismatch; found   : Double(4.0) required: Int              1lte 4d scala> 1?|? 1res31: scalaz.Ordering = EQ scala> 1?|? 2res32: scalaz.Ordering = LT scala> 1?|? 2d<console>:14: error: type mismatch; found   : Double(2.0) required: Int              1?|? 2d | 
Для этого Scalaz предоставляет следующий набор операторов:
| 01 02 03 04 05 06 07 08 09 10 11 12 | finaldef <(other: F): Boolean = F.lessThan(self, other)finaldef <=(other: F): Boolean = F.lessThanOrEqual(self, other)finaldef >(other: F): Boolean = F.greaterThan(self, other)finaldef >=(other: F): Boolean = F.greaterThanOrEqual(self, other)finaldef max(other: F): F = F.max(self, other)finaldef min(other: F): F = F.min(self, other)finaldef cmp(other: F): Ordering = F.order(self, other)finaldef ?|?(other: F): Ordering = F.order(self, other)finaldef lte(other: F): Boolean = F.lessThanOrEqual(self, other)finaldef gte(other: F): Boolean = F.greaterThanOrEqual(self, other)finaldef lt(other: F): Boolean = F.lessThan(self, other)finaldef gt(other: F): Boolean = F.greaterThan(self, other) | 
Тип перечисления
С типом Scalaz Enum очень легко создавать перечислители, которые обладают большей функциональностью, чем в стандартных библиотеках Scala или Java. Он предоставляет ряд функций для прохождения через Enum и помогает в создании новых, подмножеств из них. Scalaz предоставляет следующий набор функций:
| 01 02 03 04 05 06 07 08 09 10 11 12 | finaldef succ: F = F succ selffinaldef -+-(n: Int): F = F.succn(n, self)finaldef succx: Option[F] = F.succx.apply(self)finaldef pred: F = F pred selffinaldef ---(n: Int): F = F.predn(n, self)finaldef predx: Option[F] = F.predx.apply(self)finaldef from: EphemeralStream[F] = F.from(self)finaldef fromStep(step: Int): EphemeralStream[F] = F.fromStep(step, self)finaldef |=>(to: F): EphemeralStream[F] = F.fromTo(self, to)finaldef |->(to: F): List[F] = F.fromToL(self, to)finaldef |==>(step: Int, to: F): EphemeralStream[F] = F.fromStepTo(step, self, to)finaldef |-->(step: Int, to: F): List[F] = F.fromStepToL(step, self, to) | 
Очень хороший пример можно найти здесь в stackoverflow: http://stackoverflow.com/questions/28589022/enumeration-concept-in-scala-which-option-to-take , который, однако, требует несколько небольших изменений, чтобы получить все вкусности Скалаза. Следующий код показывает, как использовать это перечисление:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | scala> importscalaz.Ordering._importscalaz.Ordering._ scala> :paste// Entering paste mode (ctrl-D to finish)   caseclassColoring(val toInt: Int, val name: String)   object Coloring extendsColoringInstances {     val RED = Coloring(1, "RED")    val BLUE = Coloring(1, "BLUE")    val GREEN = Coloring(1, "GREEN")  }   sealed abstractclassColoringInstances {     importColoring._     implicit val coloringInstance: Enum[Coloring] with Show[Coloring] = newEnum[Coloring] with Show[Coloring] {       def order(a1: Coloring, a2: Coloring): Ordering = (a1, a2) match {        case(RED, RED) => EQ        case(RED, BLUE | GREEN) => LT        case(BLUE, BLUE) => EQ        case(BLUE, GREEN) => LT        case(BLUE, RED) => GT        case(GREEN, RED) => GT        case(GREEN, BLUE) => GT        case(GREEN, GREEN) => EQ      }       def append(c1: Coloring, c2: => Coloring): Coloring = c1 match {        caseColoring.RED => c2        caseo => o      }       override def shows(c: Coloring) = c.name       def zero: Coloring = Coloring.RED       def succ(c: Coloring) = c match {        caseColoring.RED => Coloring.BLUE        caseColoring.BLUE => Coloring.GREEN        caseColoring.GREEN => Coloring.RED      }       def pred(c: Coloring) = c match {        caseColoring.GREEN => Coloring.BLUE        caseColoring.BLUE => Coloring.RED        caseColoring.RED => Coloring.GREEN      }       override def max = Some(GREEN)       override def min = Some(RED)     }  } // Exiting paste mode, now interpreting. defined classColoringdefined object Coloringdefined classColoringInstances | 
Теперь мы можем использовать все функции, определенные в перечислении Scalaz:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 | scala> importColoring._importColoring._ scala> REDres0: Coloring = Coloring(1,RED) scala> GREENres1: Coloring = Coloring(1,GREEN) scala> RED |-> GREENres2: List[Coloring] = List(Coloring(1,RED), Coloring(1,BLUE), Coloring(1,GREEN)) scala> RED succwarning: there was one feature warning; re-run with -feature fordetailsres3: Coloring = Coloring(1,BLUE) scala> RED -+- 1res4: Coloring = Coloring(1,BLUE) scala> RED -+- 2res5: Coloring = Coloring(1,GREEN) | 
Хорошо, верно? Это действительно отличный способ создания гибких и многофункциональных перечислений.
Стандартные расширения классов
Как мы уже говорили в начале этой статьи, мы рассмотрим, как Scalaz делает стандартную библиотеку Scala более функциональной, добавляя функциональность к некоторым ее стандартным классам.
Больше веселья с опциями
С опциональным классом типов Scalaz упрощает работу с опциями Scala. Например, он предоставляет функцию, облегчающую построение:
| 01 02 03 04 05 06 07 08 09 10 11 | scala> Some(10)res11: Some[Int] = Some(10) scala> Noneres12: None.type = None scala> some(10)res13: Option[Int] = Some(10) scala> none[Int]res14: Option[Int] = None | 
Что вы увидите, так это то, что результирующий тип этих функций будет Option [T] вместо Some или None. Вы можете спросить, почему это полезно, но посмотрите на следующее: скажем, у нас есть список опций, который мы хотим свернуть:
| 1 2 3 4 5 6 7 8 | scala> val l = List(Some(10), Some(20), None, Some(30))l: List[Option[Int]] = List(Some(10), Some(20), None, Some(30)) scala> l.foldLeft(None) { (el, z) => el.orElse(z)  }<console>:22: error: type mismatch; found   : Option[Int] required: None.type              l.foldLeft(None) { (el, z) => el.orElse(z)  } | 
Это не удастся, потому что наш фолд ожидает, что результатом будет None.type, а не Option. Когда мы используем версии Scalaz, он работает как положено:
| 1 2 | scala> l.foldLeft(none[Int]) { (el, z) => el.orElse(z)  }res19: Option[Int] = Some(10) | 
И Скалаз не был бы Скалазом без введения новых операторов.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Alternative for getOrElsescala> Some(10) | 20res29: Int = 10 scala> none | 10res30: Int = 10 // Ternary operatorscala> Some(10) ? 5| 4res31: Int = 5 // ~ operator: Returns the item contained in the Option if it is defined, otherwise, the zero element for the type Ascala> some(List())res32: Option[List[Nothing]] = Some(List()) scala> ~res32res33: List[Nothing] = List() scala> some(List(10))res34: Option[List[Int]] = Some(List(10)) scala> ~res34res35: List[Int] = List(10) | 
Ничего сложного, только некоторые вспомогательные функции. Есть много других вещей, связанных с Options в библиотеке Scalaz, но это немного выходит за рамки данной статьи.
Больше логических функций
Scalaz также добавляет некоторые функциональные возможности к логическому типу.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 | # Ternary operations are back!scala> true? "This is true"| "This is false"res45: String = This is true scala> false? "This is true"| "This is false"res46: String = This is false # Returns the given argument ifthisis `true`, otherwise, the zero element forthe type of the given argument.scala> false?? List(120,20321)res55: List[Int] = List() scala> true?? List(120,20321)res56: List[Int] = List(120, 20321) | 
И целый список дополнительных операторов для двоичной арифметики:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // Conjunction. (AND)finaldef ∧(q: => Boolean) = b.conjunction(self, q)// Conjunction. (AND)finaldef /\(q: => Boolean) = ∧(q)// Disjunction. (OR)finaldef ∨(q: => Boolean): Boolean = b.disjunction(self, q)// Disjunction. (OR)finaldef \/(q: => Boolean): Boolean = ∨(q)// Negation of Disjunction. (NOR)finaldef !||(q: => Boolean) = b.nor(self, q)// Negation of Conjunction. (NAND)finaldef !&&(q: => Boolean) = b.nand(self, q)// Conditional.finaldef -->(q: => Boolean) = b.conditional(self, q)// Inverse Conditional.finaldef <--(q: => Boolean) = b.inverseConditional(self, q)// Bi-Conditional.finaldef <-->(q: => Boolean) = b.conditional(self, q) && b.inverseConditional(self, q)// Inverse Conditional.finaldef ⇐(q: => Boolean) = b.inverseConditional(self, q)// Negation of Conditional.finaldef ⇏(q: => Boolean) = b.negConditional(self, q)// Negation of Conditional.finaldef -/>(q: => Boolean) = b.negConditional(self, q)// Negation of Inverse Conditional.finaldef ⇍(q: => Boolean) = b.negInverseConditional(self, q)// Negation of Inverse Conditional.finaldef <\-(q: => Boolean) = b.negInverseConditional(self, q) | 
Например:
| 1 2 3 4 5 6 7 8 | scala> true/\ trueres57: Boolean = true scala> true/\ falseres58: Boolean = false scala> true!&& falseres59: Boolean = true | 
Больше информации о дополнительных функциях
В этой короткой статье мы показали вам только несколько дополнительных функций, предоставляемых Scalaz. Если вам нужна дополнительная информация, проще всего просто найти источники, которые в этом случае предоставляют довольно полезную информацию, например:
- scalaz.syntax.std.BooleanOps
- scalaz.syntax.std.ListOps
- scalaz.syntax.std.MapOps
- scalaz.syntax.std.OptionOps
- scalaz.syntax.std.StringOps
Несколько примеров:
Список веселья
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # get the tail as an optionscala> List(10,20,30)res60: List[Int] = List(10, 20, 30) scala> res60.tailOptionres61: Option[List[Int]] = Some(List(20, 30)) scala> List()res64: List[Nothing] = List() scala> res64.tailOptionres65: Option[List[Nothing]] = None # intersperse the list with additional elementsscala> List(10,20,30)res66: List[Int] = List(10, 20, 30) scala> res66.intersperse(1)res68: List[Int] = List(10, 1, 20, 1, 30) # from list to List[List] of all possibilitiesscala> List('a','b','c','d').powersetres71: List[List[Char]] = List(List(a, b, c, d), List(a, b, c), List(a, b, d), List(a, b), List(a, c, d), List(a, c), List(a, d), List(a), List(b, c, d), List(b, c), List(b, d), List(b), List(c, d), List(c), List(d), List()) | 
Карта веселья
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | # alter one entry in a safe mannerres77: scala.collection.immutable.Map[Char,Int] = Map(a -> 10, b -> 20)scala> res77.alter('a')(f => f |+| some(5))res78: Map[Char,Int] = Map(a -> 15, b -> 20) # intersect two maps, and determine which value to keep of the keys that intersectscala> val m1 =  Map('a'-> 100, 'b'-> 200, 'c'-> 300)m1: scala.collection.immutable.Map[Char,Int] = Map(a -> 100, b -> 200, c -> 300) scala> val m2 = Map('b'-> 2000, 'c'-> 3000, 'd'-> 4000)m2: scala.collection.immutable.Map[Char,Int] = Map(b -> 2000, c -> 3000, d -> 4000) scala> m1.intersectWith(m2)((m1v,m2v) => m2v)res23: Map[Char,Int] = Map(b -> 2000, c -> 3000) scala> m1.intersectWith(m2)((m1v,m2v) => m1v)res24: Map[Char,Int] = Map(b -> 200, c -> 300) | 
Струнное веселье
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | # Make a string plural (in a somewhat naive way)scala> "Typeclass".plural(1)res26: String = Typeclass scala> "Typeclass".plural(2)res27: String = Typeclasss scala> "Day".plural(2)res28: String = Days scala> "Weekly".plural(2)res29: String = Weeklies # safely parse booleans, bytes, shorts, longs, floats, doubleand intsscala> "10".parseDoubleres30: scalaz.Validation[NumberFormatException,Double] = Success(10.0) scala> "ten".parseDoubleres31: scalaz.Validation[NumberFormatException,Double] = Failure(java.lang.NumberFormatException: For input string: "ten") | 
Вывод
Я надеялся, что вам понравилось это первое краткое введение в Скалаз. И, как вы видели, эти простые функции уже обеспечивают большую добавленную стоимость, без необходимости углубляться в очень сложные внутренние принципы работы Scalaz. Используемый здесь шаблон — это простой шаблон TypeClass, используемый для добавления функциональности в некоторые стандартные функции Scala.
В следующей статье мы рассмотрим некоторые более сложные функции, когда рассмотрим Monad Transformers.
| Ссылка: | Функции Scalaz для повседневного использования. Часть 1. Классы типов и расширения Scala от нашего партнера по JCG Йоса Дирксена из блога Smart Java . |