├── LICENSE ├── Modules.scala └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Yawar Amin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Modules.scala: -------------------------------------------------------------------------------- 1 | object Modules { 2 | implicit class Piper[A](val x: A) extends AnyVal { 3 | def |>[B](f: A => B) = f(x) 4 | } 5 | 6 | trait Graph[A] { 7 | type E 8 | type T 9 | 10 | val empty: T 11 | def nodes(t: T): Set[A] 12 | def edges(t: T): Set[E] 13 | def create(vs: Set[A], es: Set[E]): T 14 | def insert(v1: A, v2: A)(t: T): T 15 | } 16 | 17 | trait MapGraph[A] extends Graph[A] { 18 | type E = (A, A) 19 | type T = Map[A, Set[A]] 20 | 21 | override val empty: T = Map.empty 22 | 23 | override def nodes(t: T) = t.keySet 24 | 25 | override def edges(t: T) = { 26 | val pairs = for (v1 <- t.keys; v2 <- t(v1)) yield (v1, v2) 27 | pairs.toSet 28 | } 29 | 30 | override def create(vs: Set[A], es: Set[E]) = { 31 | val vertexesEdges = 32 | for (v <- vs; (v1, v2) <- es if v == v1) yield (v1, v2) 33 | vertexesEdges.groupBy(_._1).mapValues(_.map(_._2).toSet) 34 | } 35 | 36 | override def insert(v1: A, v2: A)(t: T) = { 37 | val otherVertexes = t.get(v1) 38 | 39 | otherVertexes match { 40 | case Some(vs) => t.updated(v1, vs + v2) 41 | case None => t + Tuple2(v1, Set(v2)) 42 | } 43 | } 44 | } 45 | 46 | // IMG = IntMapGraph 47 | val IMG: Graph[Int] = new MapGraph[Int] {} 48 | 49 | trait IntMap[A] { 50 | type NotFound 51 | type T 52 | 53 | val empty: T 54 | def get(i: Int)(x: T): A 55 | def insert(k: Int, v: A)(x: T): T 56 | def remove(k: Int)(x: T): T 57 | } 58 | 59 | trait IntFunMap[A] extends IntMap[A] { 60 | case class NotFound() extends Exception() 61 | type T = Int => A 62 | 63 | override val empty = (i: Int) => throw NotFound() 64 | 65 | override def get(i: Int)(x: T) = x(i) 66 | 67 | override def insert(k: Int, v: A)(x: T) = 68 | (i: Int) => if (i == k) v else get(i)(x) 69 | 70 | override def remove(k: Int)(x: T) = 71 | (i: Int) => if (i == k) empty(i) else get(i)(x) 72 | } 73 | 74 | /* 75 | Can't create instance of IntFunMap because it's abstract--unless we 76 | give it an empty body. Then it becomes an anonymous inner class that 77 | doesn't need to implement anything in its body because all its methods 78 | are already implemented in the trait. 79 | 80 | ISFM = IntStringFunMap 81 | */ 82 | val ISFM: IntMap[String] = new IntFunMap[String] {} 83 | 84 | trait Group[A] { 85 | type T = A 86 | 87 | val empty: T 88 | def op(t1: T, t2: T): T 89 | def inverse(t: T): T 90 | } 91 | 92 | trait Summary[A] { 93 | type T = A 94 | 95 | def sum(xs: Seq[T]): T 96 | def sumNonEmpty(xs: Seq[T]): Option[T] 97 | def sumDifference(xs: Seq[T], ys: Seq[T]): T 98 | } 99 | 100 | // Group of integers under addition. 101 | val Z: Group[Int] = new Group[Int] { 102 | override val empty = 0 103 | override def op(t1: T, t2: T) = t1 + t2 104 | override def inverse(t: T) = -t 105 | } 106 | 107 | // Group of reals under addition. 108 | val R: Group[Double] = new Group[Double] { 109 | override val empty = 0.0 110 | override def op(t1: T, t2: T) = t1 + t2 111 | override def inverse(t: T) = -t 112 | } 113 | 114 | // Group of rationals under addition. 115 | val Rational: Group[(Int, Int)] = 116 | new Group[(Int, Int)] { 117 | /* 118 | gcd and reduce will never be seen by users of this module because 119 | it's been upcast immediately on declaration (to Group) and its 120 | real type is never captured. 121 | */ 122 | def gcd(x: Int, y: Int): Int = 123 | if (x == y) x 124 | else if (x < y) gcd(x, y - x) 125 | else gcd(y, x) 126 | 127 | def reduce(t: T) = { 128 | val u = if (t._2 < 0) (t._1 * -1, t._2 * -1) else t 129 | 130 | if (u._1 == 0) t 131 | else { 132 | val d = gcd(u._1 |> Math.abs, u._2 |> Math.abs) 133 | if (d == u._2) (u._1 / d, 1) else (u._1 / d, u._2 / d) 134 | } 135 | } 136 | 137 | override val empty = (0, 1) 138 | 139 | override def op(t1: T, t2: T) = 140 | (t1._1 * t2._2 + t2._1 * t1._2, t1._2 * t2._2) |> reduce 141 | 142 | override def inverse(t: T) = (-t._1, t._2) |> reduce 143 | } 144 | 145 | /* 146 | Functor from input groups to a group of pairs of elements of the input 147 | groups, under element-wise operation on elements of the input groups. 148 | */ 149 | def PairG[A1, A2](G1: Group[A1], G2: Group[A2]): Group[(A1, A2)] = 150 | new Group[(A1, A2)] { 151 | override val empty = (G1.empty, G2.empty) 152 | 153 | override def op(t1: T, t2: T) = 154 | (G1.op(t1._1, t2._1), G2.op(t1._2, t2._2)) 155 | 156 | override def inverse(t: T) = (G1.inverse(t._1), G2.inverse(t._2)) 157 | } 158 | 159 | /* 160 | Group of pairs of integers under element-wise addition. 161 | 162 | IPG = IntPairG 163 | */ 164 | val IPG = PairG(Z, Z) 165 | 166 | /* 167 | Functor from input group to some summary functions for the same type 168 | as the group's type. 169 | 170 | Exercise in translating typeclass style Scala into ML-style modular 171 | Scala. Example functions originally from 172 | http://aakashns.github.io/better-type-class.html. 173 | */ 174 | def GroupSummary[A](G: Group[A]): Summary[A] = 175 | new Summary[A] { 176 | override def sum(xs: Seq[T]) = xs.foldLeft(G.empty)(G.op) 177 | 178 | override def sumNonEmpty(xs: Seq[T]) = 179 | if (xs.isEmpty) None else xs |> sum |> Some.apply 180 | 181 | override def sumDifference(xs: Seq[T], ys: Seq[T]) = 182 | G.op(xs |> sum, ys |> sum |> G.inverse) 183 | } 184 | 185 | // Summary functions for group of integers. 186 | val ZGS = GroupSummary(Z) 187 | 188 | // Summary functions for group of reals. 189 | val RGS = GroupSummary(R) 190 | 191 | // Summary functions for pair of ints under element-wise addition. 192 | val IPGS = GroupSummary(IPG) 193 | 194 | // Summary functions for rationals under addition. 195 | val RaGS = GroupSummary(Rational) 196 | 197 | trait Ordered[A] { 198 | type T = A 199 | 200 | def compare(t1: T, t2: T): Int 201 | } 202 | 203 | val IntOrdered: Ordered[Int] = new Ordered[Int] { 204 | override def compare(t1: T, t2: T) = t1 - t2 205 | } 206 | 207 | trait MySet[A] { 208 | type E = A 209 | type T 210 | 211 | val empty: T 212 | def insert(e: E)(t: T): T 213 | def member(e: E)(t: T): Boolean 214 | } 215 | 216 | def UnbalancedSet[A](O: Ordered[A]): MySet[A] = 217 | new MySet[A] { 218 | sealed trait T 219 | case object Leaf extends T 220 | case class Branch(left: T, e: E, right: T) extends T 221 | 222 | override val empty = Leaf 223 | 224 | override def insert(e: E)(t: T) = 225 | t match { 226 | case Leaf => Branch(Leaf, e, Leaf) 227 | case Branch(l, x, r) => 228 | val comp = O.compare(e, x) 229 | 230 | if (comp < 0) Branch(insert(e)(l), x, r) 231 | else if (comp > 0) Branch(l, x, insert(e)(r)) 232 | else t 233 | } 234 | 235 | override def member(e: E)(t: T) = 236 | t match { 237 | case Leaf => false 238 | case Branch(l, x, r) => 239 | val comp = O.compare(e, x) 240 | 241 | if (comp < 0) member(e)(l) 242 | else if (comp > 0) member(e)(r) 243 | else true 244 | } 245 | } 246 | 247 | // UIS = UnbalancedIntSet 248 | val UIS = UnbalancedSet(IntOrdered) 249 | } 250 | 251 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exploring ML-Style Modular Programming in Scala 2 | 3 | I recently watched a talk by Martin Odersky [(2014)](#ode2014) in which 4 | he boils Scala down to what he considers to be the essential parts of 5 | the language. In it he remarks that Scala is designed to be a modular 6 | programming language and its modular abstractions are greatly inspired 7 | by modular programming in ML (SML). I found this intriguing, because I 8 | regard SML-style modular programming as a great way to organise software 9 | when doing programming-in-the-large. If Prof. Odersky's assertion about 10 | modular programming in Scala is correct, SML-style modules might be a 11 | great fit with Scala. 12 | 13 | As is usually the case, I went searching for what others have had to say 14 | on the matter. I found a great post [(James, 2014)](#jam2014) showing 15 | several attempts and approaches at encoding ML-style modules in Scala, 16 | and trying out several syntactic 'styles' of Scala to see which one 17 | might 'feel' better in use. James starts with Odersky's central premise, 18 | which I interpret as the following key points: 19 | 20 | - Scala object = ML module 21 | 22 | - Scala trait = ML signature 23 | 24 | - Scala class = ML functor 25 | 26 | He then goes on to explore actually implementing modules using the 27 | driving example of a `set` data structure from Okasaki 28 | [(1996)](#oka1996). 29 | 30 | I also found a very thoughtful answer and discussion [('Encoding 31 | Standard ML modules in OO', 2014)](#enc2014) of the same question, posed 32 | on Stack Overflow. The answer and discussion here are just a gold mine 33 | of information about Scala's theoretical issues with handling ML-style 34 | modules. The key points I took away from them: 35 | 36 | - ML functor = Scala function. This is slightly more elegant than the 37 | cumbersome declaration of a new class; but it does require a little 38 | finesse to avoid typechecker traps. 39 | 40 | - Scala's type relationships are _nominal_ as opposed to ML's 41 | _structural_ relationships. In practice this means we will need to 42 | declare and define all our types and traits before trying to set up 43 | relationships between them using functors. This is admittedly a 44 | limitation, but perhaps one worth living with given the code 45 | organisation and maintainability benefits we gain in exchange. 46 | 47 | Finally, I found a fantastic talk [(Martens, 2015)](#mar2015) on ML 48 | modules, their use, and the type theory behind them. I'm still mulling 49 | all the new ideas over, but so far I've managed to take away the 50 | following main points: 51 | 52 | - SML functors are _generative_ functors, so called because they 53 | _generate_ new modules as return values for each time they're 54 | called. This is true even if they're called with the exact same 55 | arguments each time. Similar to Haskell's `newtype A = Int` and 56 | `newtype B = Int` creating two completely distinct types `A` and `B` 57 | despite being literally the same type. 58 | 59 | - SML functors are actually modelled using formal logic techniques 60 | which I won't pretend to understand--just appreciate. 61 | 62 | - It's possible and sometimes even desirable to get different results 63 | from two modules which have been created by the same functor. This 64 | despite the general idea behind functors being to abstract 65 | implementation away from interface, implying that all 66 | implementations should return the same results (otherwise the 67 | interface 'breaks its promise'). 68 | 69 | ## A Functional Convenience 70 | 71 | Before we start creating modules, let's define a helper 72 | function to make it easy to apply functions in a reverse application 73 | order, like F# or OCaml, e.g.: `x |> f |> g |> h` (= `h(g(f(x)))`). 74 | 75 | ```scala 76 | implicit class Piper[A](val x: A) extends AnyVal { 77 | def |>[B](f: A => B) = f(x) 78 | } 79 | ``` 80 | 81 | For an explanation of how this works and why I recommend it for Scala, 82 | see 'In scala, what's the idiomatic way to apply a series of composed 83 | functions to a value?' [(2013)](#ins2013). 84 | 85 | ## A Basic Module 86 | 87 | As a sort of review, let's look at a simple SML module and beside it a 88 | Scala port. This example is adapted from a fantastic ground-up tutorial 89 | on ML modules [(Tofte, 1996)](#tof1996). It implements a _finite map_ 90 | from integers to values of some arbitrary type. In other words, a 91 | vector. On a side note, the interesting thing about this data structure 92 | is that it's implemented purely using function composition. 93 | 94 | ```scala 95 | /* structure IntFn = */ object IntFn { 96 | /* struct */ case class NotFound() extends Exception() 97 | /* exception NotFound */ type T[A] = Int => A 98 | /* type 'a t = int -> 'a */ 99 | /* */ def empty[A]: T[A] = 100 | /* fun empty i = */ (i: Int) => throw NotFound() 101 | /* raise NotFound */ 102 | /* */ def get[A](i: Int)(x: T[A]) = x(i) 103 | /* fun get i x = x i */ 104 | /* */ def insert[A](k: Int, v: A)(x: T[A]) = 105 | /* fun insert (k, v) x i = */ (i: Int) => if (i == k) v else get(i)(x) 106 | /* if i = k then v else x i */ } 107 | /* end; */ 108 | ``` 109 | 110 | With this implementation, we can do things like: 111 | 112 | scala> IntFn.empty |> IntFn.insert(1, "a") |> IntFn.get(1) 113 | res7: String = a 114 | 115 | A few points to take away from this: 116 | 117 | - Scala's generic methods will require type parameters, which is 118 | really clunky, unless you manage to define the type parameter 119 | elsewhere, as we will see later. 120 | 121 | - Scala is somewhat denser than SML for the equivalent functionality. 122 | To me, this is mostly a result of Scala's weaker type inference that 123 | forces us to specify a lot more. 124 | 125 | ## The Module Signature 126 | 127 | The next step in the evolution of a module is usually to extract its 128 | signature: 129 | 130 | ```scala 131 | /* signature INTMAP = */ trait IntMap[A] { 132 | /* sig */ type NotFound 133 | /* exception NotFound */ type T 134 | /* type 'a t */ 135 | /* */ val empty: T 136 | /* val empty: 'a t */ def get(i: Int)(x: T): A 137 | /* val get: int -> 'a t -> 'a */ def insert(k: Int, v: A)(x: T): T 138 | /* val insert: int * 'a -> 'a t -> 'a t */ } 139 | /* end; */ 140 | ``` 141 | 142 | Now we start making some trade-offs in Scala. Some points to take away: 143 | 144 | - We're able to clean out the type parameters from all the methods, 145 | but we're now passing in the type parameter into the trait. This has 146 | upsides and downsides: it makes the signature's declared types 147 | simpler (we don't have to parameterise any of the inner types or 148 | methods), but it also means we can't instantiate a single module in 149 | Scala to handle any input type, as we'll see later. 150 | 151 | - We express `empty` as a `val` instead of as a `def` because we want 152 | it to return the exact same thing each time; so no need for a 153 | function call to do that. We couldn't do this with the object 154 | version before because `val`s can't accept type parameters (this is 155 | a hard fact of Scala syntax). This is another reason to move out the 156 | type parameter to the trait, so that it's in scope by the time we 157 | start declaring `empty`. 158 | 159 | After that, the next step is to express the module's implementation in 160 | terms of the signature: 161 | 162 | ```scala 163 | /* structure IntFn :> INTMAP = */ trait IntFn[A] extends IntMap[A] { 164 | /* struct */ case class NotFound() extends Exception() 165 | /* exception NotFound */ type T = Int => A 166 | /* type 'a t = int -> 'a */ 167 | /* */ override val empty = 168 | /* fun empty i = */ (i: Int) => throw NotFound() 169 | /* raise NotFound */ 170 | /* */ override def get(i: Int)(x: T) = x(i) 171 | /* fun get i x = x i */ 172 | /* */ override def insert(k: Int, v: A)(x: T) = 173 | /* fun insert (k, v) x i = */ (i: Int) => 174 | /* if i = k */ if (i == k) v else get(i)(x) 175 | /* then v */ } 176 | /* else get i x */ 177 | /* end; */ 178 | ``` 179 | 180 | As I mentioned, we express the SML module directly as a concrete 181 | structure, while we express the Scala module as an abstract trait that 182 | takes a type parameter. We decided in the signature to pass in a type 183 | parameter for a few different reasons, which we'll elaborate on later. 184 | 185 | Well, as a consequence of these decisions, we also aren't able to 186 | directly create a module (i.e., a Scala object) that can work on any 187 | given type. Scala objects can't take type parameters; they can only be 188 | instantiated with concrete types (well, unless you use existential 189 | types, which is advanced type hackery that I want to avoid). 190 | 191 | So, we have to define something which _can_ take a type parameter; and 192 | the choices are a trait or a class (if we're defining something at the 193 | toplevel, that is). I went with a trait partly to minimise the number of 194 | different concepts I use, and partly to emphasise the abstract nature of 195 | this 'module template', if you will. 196 | 197 | Now, we can instantiate _concrete_ Scala modules (objects) with a type 198 | parameter of our choosing, and within some scope (not at the toplevel): 199 | 200 | ```scala 201 | object MyCode { 202 | val IntStrFn: IntMap[String] = new IntFn[String] {} 203 | 204 | IntStrFn.empty |> IntStrFn.insert(1, "a") |> IntStrFn.get(1) |> print 205 | } 206 | ``` 207 | 208 | Notice how: 209 | 210 | - We upcast the `IntStrFn` module to only expose the `IntMap` 211 | interface, just as we constrained the SML `IntFn` module to only 212 | expose the `INTMAP` signature using the constraint operator `:>`. As 213 | a quick reminder, ML calls this 'opaque signature ascription' and we 214 | use it to get the benefit of hiding our implementation details. 215 | 216 | In Scala, we implement opaque signature ascription simply with 217 | upcasting. 218 | 219 | There is another type of ascription, 'transparent' ascription, which 220 | means 'the module exposes _at least_ this signature, but possibly 221 | also more'. We get that in Scala by simply leaving out the type 222 | annotation from the module declaration and letting Scala infer a 223 | subtype of the signature trait for our module. 224 | 225 | These types of ascription are described in Tofte [(1996, p. 226 | 4)](#tof1996). 227 | 228 | - We define the module (`IntStrFn`) inside an object `MyCode` because 229 | in Scala, `val`s and `def`s can't be in the toplevel--they need to 230 | be contained within some scope. In practice we can easily work 231 | around that restriction by defining everything inside some object 232 | and then importing all names from that object into the toplevel. 233 | 234 | - The Scala implementation ends up using two traits for two levels of 235 | abstraction (the module interface and the implementation using a 236 | representation of composed functions), which is somewhat sensible. 237 | 238 | - We use `override` extensively in the `IntFn` trait to explicitly 239 | show which methods and values are from the signature. Of course, we 240 | can have more methods and values in the `IntFn` trait and object 241 | instances (e.g. `IntStrFn`) and these will automatically be 242 | private--they'll never be seen outside the module because the module 243 | will be upcast immediately on creation to its signature's type, and 244 | its runtime type will never be known by any user. 245 | 246 | - We implement the `IntStrFn` module as actually an anonymous class 247 | that extends the `IntFn` trait and passes in the concrete type as 248 | the type parameter. The class has an empty body because it extends a 249 | trait which defines all its methods and values already. 250 | 251 | ## A Detour into Module Opaque Types 252 | 253 | If you evaluate all the Scala traits and modules shown upto this point 254 | in the REPL, and then evaluate the following code: 255 | 256 | ```scala 257 | import MyCode._ 258 | IntStrFn.empty 259 | ``` 260 | 261 | You'll get back something like: 262 | 263 | res: MyCode.IntStrFn.T = 264 | 265 | This is one of the elegant things about ML-style modules. Each module 266 | contains all definitions and _types_ it needs to operate, in a single 267 | bundle. Traditionally, the module's primary type is just called `T`, so 268 | `IntStrFn.T` has the sense that it's the `IntStrFn` module's primary 269 | type. 270 | 271 | This type alias that you get from the module, known as an opaque type, 272 | doesn't give you any information or operations on itself. It limits you 273 | to using values of the type in only _exactly_ the operations that the 274 | module itself provides. And that's a _great_ thing for modularity and 275 | information hiding. 276 | 277 | You might point out that the REPL actually tells you that the type is 278 | really a `Function1`, so you immediately know you can call it and do 279 | certain other operations on it. But that's a detail of how the REPL 280 | prints out the values of arbitrary objects after evaluating them. It's 281 | not something you'll have access to when you're actually building 282 | programs to run standalone. 283 | 284 | To see a concrete example of how ML-style opaque types are great at 285 | helping you make compiler-enforced guarantees in your programs, see 286 | Prof. Dan Grossman's excellent course using SML and especially his 287 | explanations of data type abstraction [(Grossman, 2013, pp. 288 | 3--6)](#gros2013). 289 | 290 | ## Functors 291 | 292 | Now that we've set up all the building blocks of modules, we can tackle 293 | one of ML's most flexible methods for modular code organisation: 294 | _functors,_ functions which build modules. To illustrate functors, I'll 295 | re-implement a functorised module from James [(2014)](#jam2014), a 296 | functional `set` data structure. Here I show and explain my version. 297 | 298 | The steps we will take here are: 299 | 300 | - Define an `Ordered[A]` trait 301 | 302 | - Define a `MySet[A]` trait 303 | 304 | - Define a function which, given an object of type `Ordered[A]`, 305 | returns an object of type `MySet[A]`. 306 | 307 | That's it--that's all a functor is. 308 | 309 | ```scala 310 | trait Ordered[A] { 311 | type T = A 312 | 313 | def compare(t1: T, t2: T): Int 314 | } 315 | ``` 316 | 317 | This is almost exactly the same as James' `Ordering` trait; it's 318 | just that I've tried to stick closer to the SML names wherever possible. 319 | In essence, it sets up a signature for a module that can define a 320 | comparator function for any given datatype. To actually define the 321 | comparator, you just need to create a concrete module, an example of 322 | which we will see later. 323 | 324 | ```scala 325 | trait MySet[A] { 326 | type E = A 327 | type T 328 | 329 | val empty: T 330 | def insert(e: E)(t: T): T 331 | def member(e: E)(t: T): Boolean 332 | } 333 | ``` 334 | 335 | This is a signature for a module which can implement a set. You'll 336 | notice that I'm using _both_ type parameters and abstract types in my 337 | signatures so far. The reason for using a type parameter is as follows. 338 | The set functions `insert` and `member` declare parameters of type `E` 339 | (= Element), which is aliased to type parameter `A`. This allows 340 | concrete modules to pass in values of the concrete type they've been 341 | instantiated with to the functions, and these are internally 'seen' as 342 | values of type `E` without any need for type refinement or other type 343 | trickery. 344 | 345 | The reason for using the type alias `E` when we already have the type 346 | parameter `A` is as follows. If and when we do implement a concrete 347 | module with the `MySet` signature, e.g.: `val IntSet: MySet[Int] = new 348 | MySet[Int] { ... }`, we'll be able to define both `insert` and `member` 349 | using the _exact_ same types that we have in the trait. We won't have to 350 | say e.g. `def insert(e: Int)(t: T) = ...`; we'll just say `def insert(e: 351 | E)(t: T) = ...`. This reduces the possibility for simple copy-paste 352 | errors and such. 353 | 354 | In fact, you can see this in action in our next module: 355 | 356 | ```scala 357 | object Modules { 358 | val IntOrdered: Ordered[Int] = new Ordered[Int] { 359 | override def compare(t1: T, t2: T) = t1 - t2 360 | } 361 | ``` 362 | 363 | Here we're forced to start putting our concrete modules inside a 364 | containing scope because as mentioned earlier Scala `val`s can't reside 365 | in the toplevel. 366 | 367 | We could have declared `IntOrdered` in the toplevel using `object 368 | IntOrdered extends Ordered[Int] { ... }` but that wouldn't have achieved 369 | opaque signature ascription; the module wouldn't have been as tightly 370 | controlled as it is with just the type `Ordered[Int]`. So we'll define 371 | it inside a container module (`Modules`) and later import everything 372 | from `Modules` into the toplevel. 373 | 374 | ```scala 375 | def UnbalancedSet[A](O: Ordered[A]): MySet[A] = // 1 376 | new MySet[A] { // 2 377 | sealed trait T 378 | case object Leaf extends T 379 | case class Branch(left: T, e: E, right: T) extends T 380 | 381 | override val empty = Leaf 382 | 383 | override def insert(e: E)(t: T) = 384 | t match { 385 | case Leaf => Branch(Leaf, e, Leaf) 386 | case Branch(l, x, r) => 387 | val comp = O.compare(e, x) // 3 388 | 389 | if (comp < 0) Branch(insert(e)(l), x, r) 390 | else if (comp > 0) Branch(l, x, insert(e)(r)) 391 | else t 392 | } 393 | 394 | override def member(e: E)(t: T) = 395 | t match { 396 | case Leaf => false 397 | case Branch(l, x, r) => 398 | val comp = O.compare(e, x) // 4 399 | 400 | if (comp < 0) member(e)(l) 401 | else if (comp > 0) member(e)(r) 402 | else true 403 | } 404 | } 405 | ``` 406 | 407 | The rest of the implementation is almost exactly the same as in James 408 | [(2014)](#jam2014). I'll just point out the interesting bits from our 409 | perspective, which I've marked above with the numbers: 410 | 411 | 1. This is the start of the functor definition. Notice how it's just a 412 | normal Scala function which happens to take what we think of as a 413 | concrete module as a parameter; and 414 | 415 | 2. It happens to return what we think of as a new concrete module. Of 416 | course, at the level of the language syntax, they're both just 417 | simple objects that implement some interface. 418 | 419 | Notice also that in **1** we constrain the functor's return type to 420 | a more general `MySet[A]` instead of letting Scala infer the return 421 | type. This is in line with our general philosophy of doing ML-style 422 | opaque signature ascription, and also it's a convenience for 423 | whoever uses the functor as now they won't need to annotate their 424 | concrete module which they get from the functor call. 425 | 426 | 3. And also **4.** Here we actually use the comparator function 427 | defined in the `Ordered` signature to figure out if the value we 428 | were given is less than, greater than, or equal to, values we 429 | already have in the set. These two usages are exactly why the 430 | `UnbalancedSet` functor has a dependency on a module with signature 431 | `Ordered`. And the great thing is it can be any module that does 432 | any thing, as long as it ascribes to the `Ordered` signature (and, 433 | of course, also as long as it typechecks). 434 | 435 | If you're curious about the mechanics of how functors work: 436 | 437 | - The functor _doesn't_ define the output module as an _inheritor_ of 438 | the input module. The modules don't necessarily have any nominal 439 | relationship. 440 | 441 | - Technically, the output module does hold a _reference_ to the input 442 | module--but only because the former's methods _close over_ (in the 443 | sense of being closures over) the latter. So this technique 444 | resembles composition, except you don't compose objects together 445 | yourself--you provide functors which know how to do it and let the 446 | user choose exactly which ones to compose later. 447 | 448 | - The output module knows only the signature (the upcast type) of the 449 | input module. I.e., it doesn't have any knowledge of the latter's 450 | internals. It relies only on what the signature allows it to know. 451 | 452 | ```scala 453 | // UIS = UnbalancedIntSet 454 | val UIS = UnbalancedSet(IntOrdered) 455 | ``` 456 | 457 | This is where we actually define a concrete module which behaves as a 458 | set of integers implemented as an unbalanced tree. All the expected 459 | operations work: 460 | 461 | scala> import Modules._ 462 | import Modules._ 463 | 464 | scala> UIS.empty |> UIS.insert(1) |> UIS.insert(1) |> UIS.insert(2) |> UIS.member(1) 465 | res0: Boolean = true 466 | 467 | ## Review 468 | 469 | Looking back at the various techniques in this article, we take away the 470 | following main points: 471 | 472 | - Scala object = ML module 473 | 474 | - Scala trait = ML signature 475 | 476 | - Scala upcasting type annotation = ML opaque signature ascription 477 | 478 | - Scala trait type parameter ~ ML opaque type but with the 479 | Scala-specific benefits that we can pass in values of this type to 480 | methods in the Scala trait without any special hackery; and also we 481 | simplify the types of the module's contents 482 | 483 | - Also Scala abstract type = ML opaque type 484 | 485 | - Alias a trait-internal type name to the trait's type parameter for 486 | easy reference in any derived trait or module 487 | 488 | - Scala function = ML functor 489 | 490 | - Annotate all Scala module and functor types to better hide 491 | implementation details 492 | 493 | - Need to put Scala `val`s (modules) and `def`s (functors) inside some 494 | other scope because we can't declare them in the toplevel. But we 495 | can import them into the toplevel once declared 496 | 497 | The examples I've shown throughout this article are also all replicated 498 | in the the accompanying `Modules.scala` file, as are a few more advanced 499 | examples. 500 | 501 | Given the above, it seems very plausible that Scala can reliably encode 502 | ML-style modular programming--and beyond. 503 | 504 | ## References 505 | 506 | Encoding Standard ML modules in OO. (2014, April 11). 507 | Retrieved March 30, 2015, from http://stackoverflow.com/q/23006951/20371 508 | 509 | Grossman, D. (2013). CSE431: Programming Languages 510 | Spring 2013 Unit 4 Summary. University of Washington. Retrieved from 511 | http://courses.cs.washington.edu/courses/cse341/13sp/unit4notes.pdf 512 | 513 | In scala, what's the idiomatic way to apply a series 514 | of composed functions to a value? (2013, December 13). Retrieved March 515 | 30, 2015, from http://stackoverflow.com/a/29380677/20371 516 | 517 | James, D. (2014, August 14). Scala's Modular Roots. 518 | Retrieved from http://io.pellucid.com/blog/scalas-modular-roots 519 | 520 | Martens, C. (2015, January). Modularity and 521 | Abstraction in Functional Programming. Presented at the Compose 522 | Conference, New York. Retrieved from 523 | https://www.youtube.com/watch?v=oJOYVDwSE3Q&feature=youtube_gdata_player 524 | 525 | Odersky, M. (2014, August). Scala: The Simple Parts. 526 | Presented at the GOTO Conferences. Retrieved from 527 | https://www.youtube.com/watch?v=P8jrvyxHodU&feature=youtube_gdata_player 528 | 529 | Okasaki, C. (1996). Purely functional data 530 | structures. Carnegie Mellon University, Pittsburgh, PA 15213. Retrieved 531 | from 532 | http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.62.505&rep=rep1&type=pdf 533 | 534 | Tofte, M. (1996). Essentials of Standard ML Modules. 535 | In J. Launchbury, E. Meijer, & T. Sheard (Eds.), Advanced Functional 536 | Programming (pp. 208--229). Springer Berlin Heidelberg. Retrieved from 537 | http://www.itu.dk/courses/FDP/E2004/Tofte-1996-Essentials_of_SML_Modules.pdf 538 | 539 | --------------------------------------------------------------------------------