Scala:如何避免特殊情况下映射中的var和return

Scala:如何避免特殊情况下映射中的var和return,scala,Scala,假设我想编写一个函数foo,它在一个列表list[a]上迭代,并在每个元素上调用一些函数f。此函数返回选项[B]。如果遇到None,我希望整个函数foo返回None。如果没有,我希望它返回整个结果列表list[B] 我可以这样写: def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = { var res: List[B] = Nil for (element <- list) {

假设我想编写一个函数
foo
,它在一个列表
list[a]
上迭代,并在每个元素上调用一些函数
f
。此函数返回
选项[B]
。如果遇到
None
,我希望整个函数
foo
返回
None
。如果没有,我希望它返回整个结果列表
list[B]

我可以这样写:

def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
    var res: List[B] = Nil
    for (element <- list) {
        f(element) match {
            case Some(fRes) => res = fRes :: res
            case _ => return None
        }
    }
    Some(res.reverse)
}
def foo[A, B](list: List[A], f: (A) => Option[B]): Option[List[B]] = {
  val lst = list.view.map(f)
  if(lst.exists(_.isEmpty)) None 
  else Option { lst.flatten.to[List] }
}
def foo(list:list[A],f:(A)=>Option[B]):Option[list[B]={
var res:List[B]=Nil
对于(元素res=fRes::res)
case=>返回None
}
}
一些(反向)
}
但是这段代码看起来很难看,因为我必须使用
var
return
。有没有办法让它看起来更好

编辑


在这个问题中,我假设
f
是一个非常耗时的函数,如果我们已经知道结果应该是
None
的话,我想避免调用它。可能有几种不同的方法可以做到这一点;下面是我能想到的最简单的方法:

def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
  val x = list.map(f)
  if (x.contains(None)) {
    None
  } else {
    Some(x.flatten)
  }
}

如果遇到
None
,您确实会失去短路的好处。您可以通过将
流转换为
并返回,即
val x=list.toStream.map(f)
Some(x.flatte.toList)来解决这一问题

可能有几种不同的方法可以做到这一点;以下是我能想到的最简单的方法:

def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
  val x = list.map(f)
  if (x.contains(None)) {
    None
  } else {
    Some(x.flatten)
  }
}
如果遇到
,您确实会失去短路的好处。您可以通过将
流转换为
,然后再转换回来,即
val x=list.toStream.map(f)
Some(x.flatte.toList)
类似以下内容:

def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
    var res: List[B] = Nil
    for (element <- list) {
        f(element) match {
            case Some(fRes) => res = fRes :: res
            case _ => return None
        }
    }
    Some(res.reverse)
}
def foo[A, B](list: List[A], f: (A) => Option[B]): Option[List[B]] = {
  val lst = list.view.map(f)
  if(lst.exists(_.isEmpty)) None 
  else Option { lst.flatten.to[List] }
}
大概是这样的:

def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
    var res: List[B] = Nil
    for (element <- list) {
        f(element) match {
            case Some(fRes) => res = fRes :: res
            case _ => return None
        }
    }
    Some(res.reverse)
}
def foo[A, B](list: List[A], f: (A) => Option[B]): Option[List[B]] = {
  val lst = list.view.map(f)
  if(lst.exists(_.isEmpty)) None 
  else Option { lst.flatten.to[List] }
}

我会这样写:

def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
    val newList = list.map(x => f(x))
    if(newList.contains(None)) None else Some(newList.flatten)
 }
如果B是简单类型,如Int、Double等,但不是集合类型的列表,那么下面的代码将非常简单-

def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
   val newList = list.flatMap(f)
   if(newList.size < list.size) None else Some(newList)
}
def foo(list:list[A],f:(A)=>Option[B]):Option[list[B]={
val newList=list.flatMap(f)
如果(newList.size
我将写如下-

def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
    val newList = list.map(x => f(x))
    if(newList.contains(None)) None else Some(newList.flatten)
 }
如果B是简单类型,如Int、Double等,但不是集合类型的列表,那么下面的代码将非常简单-

def foo(list: List[A], f: (A) => Option[B]): Option[List[B]] = {
   val newList = list.flatMap(f)
   if(newList.size < list.size) None else Some(newList)
}
def foo(list:list[A],f:(A)=>Option[B]):Option[list[B]={
val newList=list.flatMap(f)
如果(newList.size
使用
流的另一种替代方法(我认为这是一个很酷的想法)是使用一个显式尾部递归,它允许:

def foo[A, B](list: List[A])(f: (A) => Option[B]): Option[List[B]] = {
  @tailrec
  def impl(list: List[A], acc: List[B]): Option[List[B]] = list match {
    case a :: rest => f(a) match {
      case Some(b) => impl(rest, b :: acc)
      case None => None
    }
    case _ => Some(acc.reverse)
  }

  impl(list, List.empty)
}

这显然比基于流的方法的代码要多,但我预计在某些数据上,它可能更快,但可能没有那么快(在所有的情况下,有些是好的)由于
List

强制执行
acc.reverse
,因此使用简单
map
的代码使用
(我认为这是一个很酷的想法)的另一种替代方法是使用显式尾部递归,允许:

def foo[A, B](list: List[A])(f: (A) => Option[B]): Option[List[B]] = {
  @tailrec
  def impl(list: List[A], acc: List[B]): Option[List[B]] = list match {
    case a :: rest => f(a) match {
      case Some(b) => impl(rest, b :: acc)
      case None => None
    }
    case _ => Some(acc.reverse)
  }

  impl(list, List.empty)
}

这显然比基于流的方法的代码要多,但我预计在某些数据上,它可能更快,但可能没有那么快(在所有的情况下,有些是好的)由于
List

强制执行
acc.reverse
,因此使用简单
map
的代码通常递归执行此类操作:

@tailrec
def foo(
  list: List[A], 
  result: List[B] = Nil
)(f: A => Option[B]): Option[List[B]] = list match {
 case Nil => Some(result.reverse)
 case head :: tail => f(head) match {
    case Some(b) => foo(tail, b::result)(f)
    case None => None
  }
}    
您还可以将最后一个
案例写为

case head::tail => f(head).flatMap { foo(tail, _ :: result)(f) }

但我认为,这会扼杀尾部递归…

这类事情通常是递归完成的:

@tailrec
def foo(
  list: List[A], 
  result: List[B] = Nil
)(f: A => Option[B]): Option[List[B]] = list match {
 case Nil => Some(result.reverse)
 case head :: tail => f(head) match {
    case Some(b) => foo(tail, b::result)(f)
    case None => None
  }
}    
您还可以将最后一个
案例写为

case head::tail => f(head).flatMap { foo(tail, _ :: result)(f) }

但是我认为,这会杀死尾部递归…

是的,对我来说,
map
的问题是有时
f
是一个非常耗时的函数。不过,我没有想到流,谢谢,对我来说,
map
的问题是有时候
f
是一个非常耗时的函数。我没有想到thanksCool,我不知道Scala中的视图,但看起来更像Joe K的回答。酷,我不知道Scala中的视图,但看起来更像Joe K的回答