scala notes: monoids and for-comprehensions working together

It has taken me awhile to understand monoids and their use. Once you get the hang of it, they make a lot of sense as a design pattern for structuring your code. Either, Option, Try are all monoids that can help structure your code and adapt existing code without changing the existing code or they help in structuring new code.

It also took a while to understand for-comprehensions as well. It turns out that switching monoids types in the middle of a for-comprehension is not impossible but difficult. Its best if each statement in the for-comprehension has the same monoid return type.

On gisthub: https://gist.github.com/aappddeevv/6530787 in case it does not display below correctly.
1:    
2:  // Define functions that return an Option  
3:  def f1(x: Int): Option[Int] = Some(x*30)  
4:  def f2(x: Int): Option[Int] = Some(x+3)  
5:  def f3(x: Int): Option[Int] = Some(x*2)  
6:    
7:  // Sequence the computation, it will be successful  
8:  var answer = for {  
9:   x <- f1(30)  
10:   y <- f2(x)  
11:   z <- f3(y)  
12:   } yield (x,y,z)  
13:     
14:  println("answer: " + answer)  
15:     
16:  // Sequence the computation, the filter will be used  
17:  answer = for {  
18:   x <- f1(30)  
19:   y <- f2(x) if(x>1000)  
20:   z <- f3(y)  
21:   } yield (x,y,z)  
22:     
23:  println("answer with filter: " + answer)  
24:     
25:  // Define functions that return Either  
26:  // Note that e2b returns Left as if it is an error.  
27:  def e1(x: Int): Either[String, Int] = Right(x*30)  
28:  def e2(x: Int): Either[String, Int] = Right(x+3)  
29:  def e2b(x: Int): Either[String, Int] = Left("error")  
30:  def e3(x: Int): Either[String, Int] = Right(x*2)  
31:    
32:  // Sequence the computations, knowing it will succeed  
33:  var eanswer = for {  
34:   x <- e1(30).right  
35:   y <- e2(x).right  
36:   z <- e3(y).right  
37:   } yield z  
38:     
39:  println("eanswer: " + eanswer)  
40:    
41:  // Sequence computations, knowing that it will have an error  
42:  eanswer = for {  
43:   x <- e1(30).right  
44:   y <- e2b(x).right  
45:   z <- e3(y).right  
46:   } yield z  
47:     
48:  println("eanswer (with Left()): " + eanswer)  
49:    
50:    
51:  /** Prints:  
52:  answer: Some((900,903,1806))  
53:  answer with filter: None  
54:  eanswer: Right(1806)  
55:  eanswer (with Left()): Left(error)  
56:  **/  
57:    
58:  // The following does not compile  
59:  // eanswer = for { ...  y <- e2b(x).left ... } ...  
60:  // Because e3, the function after e2b is called expects an int,   
61:  // but Left contains a String so when it is unwrapped to call  
62:  // e3 it is the wrong type. Scala compile time checks catch this!  
63:    
64:  // Either, Left and Right do not have a withFilter method  
65:  // so you cannot use if() clauses in for comprehensions directly  
66:  // when using Either. Option has a withFilter method.  
67:  println("Filter on <200: " + e2(33).right.filter[String](_<200))  
68:  println("Filter on >200: " + e2(33).right.filter[String](_ > 200))  
69:    
70:  /** Prints:  
71:  Filter on <200: Some(Right(36))  
72:  Filter on >200: None  
73:  **/  
74:    
75:  // Sequencing using just the Either monad and no filter  
76:  // criteria used on any of the map lines.   
77:  // Generally, when using a filter, check the scala  
78:  // docs for the monoid you are using (e.g. Option or  
79:  // Either) to understand what map, flatMap and withFilter  
80:  // do when applied to the monoid value. If withFilter  
81:  // is not present, then you may have difficulty using  
82:  // an if-condition on one of the "map" lines.  
83:  //  
84:  // Note that it does not make sense to have a withFilter  
85:  // on an Either. When using withFilter on an Option monoid,  
86:  // if the filter selects false (and hence returns a None),  
87:  // then a Some(yourValue) becomes a None. Both of which are  
88:  // well defined. But if you use either and the Right(yourValue)  
89:  // (or left depending on which 1/2 you use) is not selected,  
90:  // you need to return Left(someOtherYourValue) but someOtherYourValue  
91:  // is never defined automatically so there is no "automatic"  
92:  // other value to use. That's why you cannot apply a filter  
93:  // using the for-comprehension syntax.  
94:  //  
95:  // In the for-comprehension below, if a Left appears as a result  
96:  // of the function call, the for terminates immediately and returns   
97:  // the Left as the value.  
98:  var eanswer2 = for {  
99:   x <- e1(30).right  
100:   y <- e2(x).right  
101:   z <- e3(y).right  
102:   } yield z  
103:    
104:  println("eanswer with no filter: " + eanswer2)  
105:    
106:  /** Prints:  
107:  eanswer with no filter: Right(1806)  
108:  **/  
109:    

Comments

Popular posts from this blog

quick note on scala.js, react hooks, monix, auth

zio environment and modules pattern: zio, scala.js, react, query management

user experience, scala.js, cats-effect, IO