我是FP和Scala的新手,正在阅读Scala中的Functional Programming一书。第4章中的一个练习要求我们编写一个名为sequence
的函数,它将List[Option[A]]
转换为Option[List[A]]
。这里Option
是Scala库提供的Option
的重新实现。这是必需的代码。
trait Option[+A] {
/* Function to convert Option[A] to Option[B] using the function passed as an argument */
def map[B](f: A => B): Option[B] = this match {
case None => None
case Some(v) => Some(f(v))
}
/* Function to get the value in `this` option object or return the default value provided. Here,
* `B >: A` denotes that the data type `B` is either a super-type of `A` or is `A`
*/
def getOrElse[B >: A](default: => B): B = this match {
case None => default
case Some(v) => v
}
/* Used to perform (nested) operations on `this` and aborts as soon as the first failure is
* encountered by returning `None`
*/
def flatMap[B](f: A => Option[B]): Option[B] = {
map(f).getOrElse(None)
}
}
case class Some[+A](get: A) extends Option[A] // used when the return value is defined
case object None extends Option[Nothing] // used when the return value is undefined
现在我尝试了很多,但我必须查找编写sequence
的解决方案,即
def sequence[A](l: List[Option[A]]): Option[List[A]] = l match {
case Nil => Some(Nil) // Or `None`. A design decision in my opinion
case h :: t => h.flatMap(hh => sequence(t).map(hh :: _))
}
我只是想确保我正确理解解决方案。所以这是我的问题。
case Nil
的返回值的直觉是否正确?它真的是一个设计决定还是比另一个更好?case h :: t
,这就是我所理解的。我们首先将值h
传递给flatMap
中的匿名函数(作为hh
),它以递归方式调用sequence
。 sequence
的这种递归调用会返回Option
封装Option
中的t
。我们对此返回值调用map
并将h
传递给匿名函数(作为hh
),然后创建一个新的List[A]
,其中递归调用返回的列表为尾部和h
作为头部。然后通过调用Option
将此值封装在Some
中并返回。我对第二部分的理解是否正确?如果是,是否有更好的解释方法?
答案 0 :(得分:3)
如果列表中的任何元素为sequence
,None
似乎都会返回None
,否则会返回列表中Some
的值。因此,您对Nil
案例的直觉不正确 - Nil
是一个不包含None
的空列表,因此结果不应为None
。
让我们从内到外一步一步。
假设我们有optionList
类型的变量Option[List[A]]
和a
类型的变量A
。我们打电话时会得到什么:
optionList.map(a :: _)
如果optionList
为None
,那么这将是None
。如果optionList
包含一个列表,请说list
,这将是Some(a :: list)
。
现在,如果对于option
类型的某个变量Option[A]
,我们在调用时会得到什么:
option.flatMap(a => optionList.map(a :: _))
如果option
为None
,那么这将是None
。如果option
包含一个值,例如a
,那么这将是optionList.map(a :: _)
,我们在上面计算出来(根据flatMap
的定义)。
现在如果我们把它绑在一起,我们会看到如果任何元素是None
,那么就会避免递归调用,整个结果将是None
。如果没有元素是None
,那么递归调用将继续追加元素的值,结果将是列表元素内部值的Some
。
如果你重写内部部分可能会更清楚:
def sequence[A](l: List[Option[A]]): Option[List[A]] = l match {
case Nil => Some(Nil)
case h :: t => h match {
case None => None
case Some(head) => sequence(t) match {
case None => None
case Some(list) => Some(head :: list)
}
}
}
甚至更少惯用,但也许澄清:
def sequence[A](l: List[Option[A]]): Option[List[A]] = l match {
case Nil => Some(Nil)
case h :: t =>
val restOfList = sequence(t)
if (h == None || restOfList == None) None else Some(h.get :: restOfList.get)
}
你也可以在没有递归的情况下自然地重写这个fold
,以防你感到困惑:
def sequence[A](l: List[Option[A]]) = (Option(List.empty[A]) /: l) {
case(Some(sofar), Some(value)) => Some(value :: sofar);
case(_, _) => None
}
答案 1 :(得分:0)
我想我试图从同一本书中解决同样的问题并提出这个问题。它适用于我,看起来很清楚和consice
{{1}}
答案 2 :(得分:0)
尾递归解决方案
def seqToOption[T](s: Seq[Option[T]]): Option[Seq[T]] = {
@tailrec
def seqToOptionHelper(s: Seq[Option[T]], accum: Seq[T] = Seq[T]()): Option[Seq[T]] = {
s match {
case Some(head) :: Nil => Option(head +: accum)
case Some(head) :: tail => seqToOptionHelper(tail, head +: accum)
case _ => None
}
}
seqToOptionHelper(s)
}
答案 3 :(得分:0)
使用List[Option[A]]
将Option[List[A]]
转换为foldLeft
:
def optionSequence[A](loa: List[Option[A]]): Option[List[A]] =
loa
.foldLeft(Option(List.empty[A]))((ol, v) =>
(ol, v) match {
case (None, _) | (_, None) => None
case (Some(l), Some(av)) => Some(av :: l)
})
.map(_.reverse)
由于List
是一个链表,所以前置比附加要快。这就是为什么我将值放在最终列表的前面,然后反转列表(使用map
)以保留顺序的原因。
答案 4 :(得分:0)
假设l
是List[Option[A]]
,这是使用partition
和isEmpty
的另一种方法。
l.partition(_.isEmpty) match {
case (Nil, x) => Some(x.map(_.get))
case _ => None
}
请注意,当给定一个空列表时,它将返回Some(List())
。如果需要None
,则添加另一种情况很简单:
l.partition(_.isEmpty) match {
case (Nil, Nil) => None
case (Nil, x) => Some(x.map(_.get))
case _ => None
}