Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Implementing map and withFilter in a simple custom type

I'm learning Scala and already had to find out that the concept of monads is a bit too sophisticated for my current level of knowledge. However, my goal is at least to make a very simple class that can be used with the for expression and additionally a filter.

From my point of understanding, the following rules apply:

  • In order for a custom type to be usable in the generator of a for expression (where the generator only produces simple variables), it needs to implement map.
  • If filters should be used in addition, then the type also has to implement withFilter.

My minimal classes look like this:

class Grid(private val fields: IndexedSeq[Field])

class Field(val name: String, val isVisible: Boolean)

What I want to achieve is be able to do the following:

for(f <- grid) yield f.name // needs map
for(f <- grid; if f.isVisisble) yield f.name // needs map + withFilter

However, I have a hard time finding examples with that kind of simplicity. It's ok if the solution is "tailored" to the two classes shown above instead of being a general solution that can be applied to any classes. Unterstanding the implementation for this simple example would definitely help me. Any help is appreciated, thank you.

Edit:

As Lee pointed out, my intention only seem to work for generic types. I assume it would make more sense if I forget about the class Field and redefine Grid as follows:

class Grid[E](private val fields: IndexedSeq[E])
like image 363
ceran Avatar asked Jan 14 '15 08:01

ceran


2 Answers

In this case you can just pass the map call on to the wrapped collection fields.
For withFilter you can call the filter method on fields, but I think that's not entirely in line with the semantics that withFilter is supposed to have.

case class Grid[E](private val fields: IndexedSeq[E]) {
  def map[R](f: E => R): Grid[R] = new Grid(fields map f)
  def withFilter(p: E => Boolean): Grid[E] = new Grid(fields filter p)
}

A more correct, but convoluted implementation of what you are asking would be:

case class Grid[E](private val fields: IndexedSeq[E]) {
  def map[R](f: E => R): Grid[R] = new Grid(fields map f)
  def withFilter(p: E => Boolean): WithFilter = new WithFilter(p)

  class WithFilter(p: E => Boolean) {
    def map[R](f: E => R): Grid[R] = new Grid(fields.withFilter(p).map(f))
    def withFilter(q: E => Boolean): WithFilter = new WithFilter(x => p(x) && q(x))
  }
}

That way, withFilter will work lazily as expected.

like image 96
Jasper-M Avatar answered Oct 02 '22 16:10

Jasper-M


It will work with even not generic definition of Grid, yet map isn't what you expect:

case class Field(name: String, isVisible: Boolean)

case class Grid(val fields: IndexedSeq[Field]) {
  def map[B](f: Field => B): IndexedSeq[B] =
    fields.map(f)

   def filter(f: Field => Boolean): Grid =
     new Grid(fields.filter(f))
}

val grid = new Grid(Vector(Field("foo", true), Field("bar", false)))

// works
for { f <- grid } yield f.name
// res7: IndexedSeq[String] = Vector(foo, bar)

for { f <- grid; if f.isVisible } yield f.name
// res13: IndexedSeq[String] = Vector(foo)

For-comprehension de-sugaring is syntax-based. It rewrites the expression using .map .flatMap .filter etc and then type-checks (AFAIK).

like image 38
phadej Avatar answered Oct 02 '22 16:10

phadej