Большинство из вас, вероятно, слышали о великой книге 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> import scalaz._import scalaz._ scala> import Scalaz._import Scalaz._ |
В этой первой статье мы рассмотрим следующие классы типов из библиотеки 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
|
final def ===(other: F): Boolean = F.equal(self, other)final def /==(other: F): Boolean = !F.equal(self, other)final def =/=(other: F): Boolean = /==(other)final def ≟(other: F): Boolean = F.equal(self, other)final def ≠(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> 1 lte 4d<console>:14: error: type mismatch; found : Double(4.0) required: Int 1 lte 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
|
final def <(other: F): Boolean = F.lessThan(self, other)final def <=(other: F): Boolean = F.lessThanOrEqual(self, other)final def >(other: F): Boolean = F.greaterThan(self, other)final def >=(other: F): Boolean = F.greaterThanOrEqual(self, other)final def max(other: F): F = F.max(self, other)final def min(other: F): F = F.min(self, other)final def cmp(other: F): Ordering = F.order(self, other)final def ?|?(other: F): Ordering = F.order(self, other)final def lte(other: F): Boolean = F.lessThanOrEqual(self, other)final def gte(other: F): Boolean = F.greaterThanOrEqual(self, other)final def lt(other: F): Boolean = F.lessThan(self, other)final def 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
|
final def succ: F = F succ selffinal def -+-(n: Int): F = F.succn(n, self)final def succx: Option[F] = F.succx.apply(self)final def pred: F = F pred selffinal def ---(n: Int): F = F.predn(n, self)final def predx: Option[F] = F.predx.apply(self)final def from: EphemeralStream[F] = F.from(self)final def fromStep(step: Int): EphemeralStream[F] = F.fromStep(step, self)final def |=>(to: F): EphemeralStream[F] = F.fromTo(self, to)final def |->(to: F): List[F] = F.fromToL(self, to)final def |==>(step: Int, to: F): EphemeralStream[F] = F.fromStepTo(step, self, to)final def |-->(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> import scalaz.Ordering._import scalaz.Ordering._ scala> :paste// Entering paste mode (ctrl-D to finish) case class Coloring(val toInt: Int, val name: String) object Coloring extends ColoringInstances { val RED = Coloring(1, "RED") val BLUE = Coloring(1, "BLUE") val GREEN = Coloring(1, "GREEN") } sealed abstract class ColoringInstances { import Coloring._ implicit val coloringInstance: Enum[Coloring] with Show[Coloring] = new Enum[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 { case Coloring.RED => c2 case o => o } override def shows(c: Coloring) = c.name def zero: Coloring = Coloring.RED def succ(c: Coloring) = c match { case Coloring.RED => Coloring.BLUE case Coloring.BLUE => Coloring.GREEN case Coloring.GREEN => Coloring.RED } def pred(c: Coloring) = c match { case Coloring.GREEN => Coloring.BLUE case Coloring.BLUE => Coloring.RED case Coloring.RED => Coloring.GREEN } override def max = Some(GREEN) override def min = Some(RED) } } // Exiting paste mode, now interpreting. defined class Coloringdefined object Coloringdefined class ColoringInstances |
Теперь мы можем использовать все функции, определенные в перечислении Scalaz:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
scala> import Coloring._import Coloring._ 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 for detailsres3: 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 if this is `true`, otherwise, the zero element for the 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)final def ∧(q: => Boolean) = b.conjunction(self, q)// Conjunction. (AND)final def /\(q: => Boolean) = ∧(q)// Disjunction. (OR)final def ∨(q: => Boolean): Boolean = b.disjunction(self, q)// Disjunction. (OR)final def \/(q: => Boolean): Boolean = ∨(q)// Negation of Disjunction. (NOR)final def !||(q: => Boolean) = b.nor(self, q)// Negation of Conjunction. (NAND)final def !&&(q: => Boolean) = b.nand(self, q)// Conditional.final def -->(q: => Boolean) = b.conditional(self, q)// Inverse Conditional.final def <--(q: => Boolean) = b.inverseConditional(self, q)// Bi-Conditional.final def <-->(q: => Boolean) = b.conditional(self, q) && b.inverseConditional(self, q)// Inverse Conditional.final def ⇐(q: => Boolean) = b.inverseConditional(self, q)// Negation of Conditional.final def ⇏(q: => Boolean) = b.negConditional(self, q)// Negation of Conditional.final def -/>(q: => Boolean) = b.negConditional(self, q)// Negation of Inverse Conditional.final def ⇍(q: => Boolean) = b.negInverseConditional(self, q)// Negation of Inverse Conditional.final def <\-(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, double and 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 . |