forked from politrons/reactiveScala
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathScalaHaskell.scala
194 lines (166 loc) · 6.96 KB
/
ScalaHaskell.scala
1
2
3
4
5
6
7
8
9
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package app.impl.haskell
import java.util.Calendar._
import app.impl.haskell.ScalaHaskell.{Name, Time, Word}
import org.junit.Test
import scalaz.ioeffect.{IO, RTS}
/**
* Just like using the Monad IO of Haskell, here we use the IO Monad for ScalaZ.
* What really make this code pure like in Haskell is, that is not eager evaluation but lazy,
* so just like in Haskell, if we use composition, those functions in the IO wont
* be executed until we finish our program and we use [unsafePerformIO] which means
* we can compose those pure functions in our program knowing that those will provide
* same result once are executed.
*/
class ScalaHaskell extends RTS {
/**
* One of the best ways to emulate Haskell from Scala is using for comprehension which is
* just like [do block] a sugar syntax to make sequential composition of functions.
*
* Haskell Example do block:
*
* fooFunc :: Socket -> Chan Message -> MsgId -> IO ()
* fooFunc sock channel msgId = do
* let broadcast msg = writeChan channel $ Message msgId msg
* handle <- createHandle sock ReadWriteMode
* name <- logInClient handle broadcast
* newChannel <- duplicateChannel channel
* readerThreadId <- readMessage handle newChannel msgId
* writeMessage broadcast handle name readerThreadId
*/
@Test
def doBlockWithIO(): Unit = {
val io = for {
result <- concatTwoWords(Word("hello"))(Word("world"))
result <- appendValue(result)
result <- transformToUpperCase(result)
} yield result
println(unsafePerformIO(io))
}
/**
* Here we can see an example of pure referential transparency where our
* function receive Word => Word and we always return IO[Throwable, Word]
* of that value once is evaluated.
*/
def concatTwoWords: Word => Word => IO[Throwable, Word] =
word => word1 => IO.point(Word(s"${word.value} - ${word1.value}"))
def appendValue: Word => IO[Throwable, Word] =
word => IO.point(Word(word.value.concat("!!!!")))
def transformToUpperCase: Word => IO[Throwable, Word] =
word => IO.point(Word(word.value.toUpperCase))
/**
* In this example we show how using IO monad like in Haskell we can have pure Functional programing
* using Referential transparency, we are doing composition of this three functions, and it does not
* matter how many times we compose those functions, since are lazy evaluated we know that function
* will give same result in time for example in here.
* We create the IO and even waiting 2 seconds before evaluate the io and go through pure to impure
* we see the time it never was set in place when we were composing the functions.
*/
@Test
def lazyEvaluation(): Unit = {
val io = for {
time <- pureTimeFunction(Time(""))
time <- pureTimeFunction(time)
time <- pureTimeFunction(time)
} yield time
val now = getInstance()
println(s"Time before we start the evaluation: ${now.get(MINUTE)} - ${now.get(SECOND)}")
Thread.sleep(2000)
println(unsafePerformIO(io))
}
def pureTimeFunction: Time => IO[Throwable, Time] =
time => IO.point {
val now = getInstance()
Time(s"${time.value} : ${now.get(MINUTE)} - ${now.get(SECOND)}")
}
/**
* Scala provide in all their Monads very good operators to transform(map), compose(flatMap, zip) and filter
* So for this particular example we could use [filter] of List to find the name in the list, but what
* we want to show here is how just like Haskell Scala use [tail recursion] a very efficient way
* to do recursion without create a stack per recursion which it can end up in a StackOverFlow
* The patter matching of Scala is an implementation of how Haskell function can define internally this
* match so in here we can reduce the list giving us one value and the rest of the list like in Haskell we do with
*
* sumAllPrices :: Double -> [Item] -> Double
* sumAllPrices totalPrice (item:items) = sumAllPrices (totalPrice + (price item)) items
* sumAllPrices totalPrice [] = totalPrice -- Last condition. When the list is empty we break the recursion
*/
@Test
def recursion(): Unit = {
val io = for {
name <- searchName(Name("Politrons"))(List(Name("Paul"), Name("Lee"), Name("Politrons"), Name("Esther")))
name <- nameToUpperCase(name)
} yield name
println(unsafePerformIO(io))
}
def searchName: Name => List[Name] => IO[Throwable, Name] =
name => {
case Nil => IO.point(Name("Name not found"))
case head :: _ if head.value == name.value => IO.point(name)
case _ :: tail => searchName(name)(tail)
}
def nameToUpperCase: Name => IO[Throwable, Name] =
name => IO.point(name.copy(value = name.value.toUpperCase))
/**
* Thanks to [implicit] we can make the compiler once he infer a type go to
* one implicit implementation for that class that implement a trait or another,
* just like Haskell does. Here unfortunately it's quite more verbose than in
* Haskell but still is doable use this amazing feature.
*/
@Test
def typeClasses(): Unit = {
val io = for {
number <- processValue(1981)
sentence <- processValue("hello world")
result <- IO.point(s"$number - $sentence")
} yield result
println(unsafePerformIO(io))
}
def processValue[T](value: T)(implicit myClass: MyClass[T]): IO[Throwable, T] = {
IO.point(myClass.process(value))
}
/**
* Like in Haskell we define the class, here [trait] with generic type and the
* function process which receive a T and return a T
* Then we define the instances, here implicit anonymous implementations of MyClass[T]
* and in that implementation just like in Haskell we specify the type for that class.
*
* Haskell type class example:
* -- | Here we just define the class with the structure as a contract.
* class ArithmeticTypeClass _type where
* customSum :: _type -> _type -> _type
*
* -- |Here we define the implementation for type Integer.
* instance ArithmeticTypeClass Integer where
* customSum i1 i2 = i1 + i2
*/
trait MyClass[T] {
def process: T => T
}
/**
* Implementation for String type of type class [MyClass]
*/
implicit val stringValue: MyClass[String] = new MyClass[String] {
override def process: String => String = value => value.toUpperCase + "!!!"
}
/**
* Implementation for Int type of type class [MyClass]
*/
implicit val intValue: MyClass[Int] = new MyClass[Int] {
override def process: Int => Int = value => value * 1000
}
}
/**
* Using case class is the most close to data types that you can find in Haskell, bot are final immutable types
* and both provide get access to the elements of the class.
*
* Haskell example of Data types:
*
* data Products = Products { results :: [Product]}
*
* data Product = Product { artistName:: [Char], trackName:: [Char], collectionName:: [Char] }
*/
object ScalaHaskell {
case class Word(value: String) extends AnyVal
case class Time(value: String) extends AnyVal
case class Name(value: String) extends AnyVal
}