Flatten types after composing two defs

久未见 提交于 2019-12-08 19:57:15

问题


following is a toy example to demonstrate real life legacy methods' shape weirdness and point of the question.

As you can see anotherFunc, after mapping over personList expands type to \/[Throwable,List[\/[Throwable,String]]] which isn't intended return type but effect of maping personList. again what's illustrated below inside anotherFunc is for demo purpose(in reality there is more meaningful things happening instead of Option("fakeString") or any of that.

Requirement is to map personList and if it's right then do something with each element of the List[Person] returned from right (side that was returned from the disjunction).

how to simplify/flatten return type of anotherFunc (to return \/[Throwable,List[String]]). May be using other combinators?

case class Person(name :String)

def personList : \/[Throwable,List[Person]] ={
  \/.fromTryCatch{
    List(Person("John"))
  }
} 

def anotherFunc  : \/[Throwable,List[\/[Throwable,String]]]= {
  personList.map{ pl =>
    pl.map{p =>
      for{
        s <- Option("fakeString").\/>(new Throwable("not found"))
      } yield s

    }

  }
}

回答1:


Noah's answer is basically right, but you should always use traverse instead of a map followed by a sequence—the two are equivalent but the former is clearer and a little more efficient:

def anotherFunc: Throwable \/ List[String] = personList.flatMap { pl =>
  pl.traverseU { p =>
    for {
      // I assume you're doing something more interesting here...
      s <- Option("fakeString").\/>(new Throwable("not found"))
    } yield s
  }
}

I'm making this an answer instead of a comment, though, because there's another way to solve this kind of problem that can be more elegant in some situations. If you're doing a lot of work with lists of disjunctions, you can use the ListT monad transformer to make it look like you're dealing with a single level:

type OrThrowable[A] = Throwable \/ A    

def personList = ListT[OrThrowable, Person](
  \/.fromTryCatch { List(Person("John")) }
)

def anotherFunc: ListT[OrThrowable, String] = personList.flatMap { p =>
  Option("fakeString").\/>(new Throwable("not found")).liftM[ListT]
}

Now just use anotherFunc.run at the end to get a Throwable \/ List[Person] and this is exactly equivalent to your current code (but a lot more concise).




回答2:


If you flatMap the personList and then sequenceU the inner list you can basically flatten your return type:

  def anotherFunc: \/[Throwable, List[String]] = {
    personList.flatMap(
      pl =>
        pl.map(
          p =>
            for {
              s <- Option("fakeString").\/>(new Throwable("not found"))
            } yield s
        ).sequenceU
    )
  }

Intellij complains about this with some red lines but it compiles and prints out correctly for me \/-(List(fakeString)).

This version looks a little prettier in my opinion:

  def anotherFunc2: \/[Throwable, List[String]] = {
    for {
      pl <- personList
      res <- pl.map(p => Option("fakeString").\/>(new Throwable("not found"))).sequenceU
    } yield res
  }


来源:https://stackoverflow.com/questions/24340931/flatten-types-after-composing-two-defs

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!