sc_ray
sc_ray

Reputation: 8043

Mapping with a function that returns Try[T] to return a Try[T] instead a List[Try[T]]

The title of this question is a tad weird but I want to accomplish is as follows.

I have a List of Tasks. For the sake of convenience the task is defined as follows:

case class Task(name: String)

I have a TaskStorage trait with a store method that will persist the task and return a Try[Task].

My storage doesn't have a batch storage API so I need to mimic batch storage in the application end. The way I was initially doing was as follows:

val tasks = List(task1, task2)
tasks.map(taskStorage) -> This returns a List[Try[Task]]

My API design might be a little suspect here but what I want is some thing as follows:

def batchStoreTasks(tasks: List[Task]):Try[Task] = {
   //
}

The Try[Task] indicates the status of the last task that was asked to be persisted by the storage. Since I was unable to find an idiomatic way to accomplish the above, I resorted to pattern match and did the following:

def batchStoreTasks(tasks: List[Task]): Try[Task] = tasks match {
   case Nil => Failure(EmptyTaskListException)
   case x :: Nil => taskStorage(x)
   case x :: t => val storedTask = taskStorage(x); if(storedTask.isSuccess) batchStoreTasks(t) else storedTask

}

I am able to get the job done but I am missing the idiomatic way to accomplish the above. It will be great if I could be pointed in the right direction to restructure the batchStoreTasks to give it a more idiomatic shape.

Thanks

Upvotes: 1

Views: 89

Answers (3)

proximator
proximator

Reputation: 688

Belw a possible solution, I am using view on the list to make sure the map is lazy and taskStorage is not evaluated after unessary, in the example only task "A" and "B" will be printed:

  case class Task(name: String)
  class TaskStorage {

    def apply(task: Task): Try[Task] = {
      if (task.name == "B"){
        println("Failed")
        Failure(new RuntimeException)
      }
      else {
        println("Success")
        Success(task)
      }
    }
  }
  val taskStorage: TaskStorage = new TaskStorage()
  val tasks = List(Task("A"), Task("B"), Task("C"))

  tasks.view.map(taskStorage(_)).collectFirst {
    case r if r.isFailure => r
  }.getOrElse(taskStorage(tasks.head))

In RxJava/RxScala, this could be easily implemented:

case class Task(name: String)

trait TaskStorage {
  def apply(task: Task): Try[Task]
}

val taskStorage: TaskStorage = _ //Instance of concrete task storage

def batchStoreTasks(tasks: List[Task]): Observable[Task] = {
  for {
      taskName <- Observable.from(tasks)
      taskResult <- Observable.from(taskStorage(taskName))
   } 
   yield taskResult
}

Upvotes: 1

Shanti Swarup Tunga
Shanti Swarup Tunga

Reputation: 641

You can use takeWhile

def batchStoreTasks(tasks: List[Task]): Try[Task] =
if(tasks.isEmpty) Failure(EmptyTaskListException) else {
  val tryStore = tasks.diff(tasks.takeWhile(x => taskStorage(x).isSuccess))
  tryStore.map(_ => Failure(new RuntimeException(tryStore.head.toString))).headOption.getOrElse(Success(tryStore.reverse.head))
}

Upvotes: 0

Andrey Tyukin
Andrey Tyukin

Reputation: 44918

Assuming that you want to return the failure of the last task if the list was non-empty but all tasks failed:

val tryTasks = tasks.map(taskStorage).reverse
tryTasks
  .find(_.isSuccess)                          // try to find a success
  .orElse(tryTasks.headOption)                // return last failure
  .getOrElse(Failure(EmptyTaskListException)) // special failure for empty list

Note that it will usually throw away the failure information captured in the failed tasks, but that's what I understood from your description.

Upvotes: 2

Related Questions