Niek Bartholomeus
Niek Bartholomeus

Reputation: 385

How to create a function that works generically on an Array as well as an Option

I would like to create a generic function that works for both an Array as an Option:

val numbers = Array(1, 2, 3)
val numberO: Option[Int] = Some(4)

def addOnes(numbers: ???[Int]) = numbers.map(_+1)

addOnes(numbers)
addOnes(numberO)

Right now I have a separate function for each structure

def addOnesForArray(numbers: Array[Int]) = numbers.map(_+1)
def addOnesForOption(numberO: Option[Int]) = numberO.map(_+1)

addOnesForArray(numbers)
addOnesForOption(numberO)

So basically I need a superclass of Array and Option that has the functor and monad methods map, flatMap, filter, ...

Upvotes: 2

Views: 94

Answers (2)

simpadjo
simpadjo

Reputation: 4017

In general I agree with @slouc . If you want to make existing classes kinda extend some other trait you need typeclasses. But in your particular case it is not required since Option and Array are both Traversable:

object Example extends App {
  def plus1(data: Traversable[Int]): Traversable[Int] = data.map(x => x + 1)

  println(plus1(Array(1, 2, 3)))
  println(plus1(Some(4)))
}

Upvotes: 2

slouc
slouc

Reputation: 9698

You could use structural typing (aka "duck typing"), with which we could say that you need literally "something with a map", written as { def map(): T }. But that's a) ugly, b) uses reflection, and c) hard (note that map(): T is just an example; in reality you will have to match the exact signature of map, with CanBuildFrom and all that jazz).

Better way would be to reach for scalaz or cats and use category theory concepts. Just pick the least powerful abstraction that does the job. If you need just map, then it's a Functor. If you want to map with (curried) functions of more than one parameter, then it's an Applicative. If you also want flatMap, then it's a monad, etc.

Example for functor:

import scalaz._, Scalaz._

def addOne[F[Int]](f: F[Int])(implicit m: Functor[F]) = f.map(_ + 1)

val numbers = Array(1, 2, 3).toList
val numberO = Option(123)

addOne(numbers) // List(2, 3, 4)
addOne(numberO) // Some(124)

You will notice that I had to convert your array to a List because there are no typeclass instances (that I know of) for functors, applicatives, monads etc. that work on arrays. But arrays are old fashioned and invariant and really not idiomatic Scala anyway. If you get them from elsewhere, just convert them to Lists, Vectors etc. (based on your use case) and work with those from that point onwards.

Upvotes: 2

Related Questions