Scalaz 用于功能代码简化
Scalaz for function code simplification
我使用 bing 操作在 Scalaz
上写了一个斐波那契函数。这是我的代码:
import scalaz._, Scalaz._
def fib(i: Int): Option[Int] = i match {
case 0 => 0.some
case 1 => 1.some
case x if x > 1 =>
fib(i - 1) >>= {
a => fib(i - 2) >>= {
b => (a + b).some
}
}
case _ => None
}
可以简化吗?我不喜欢这样的东西 a => fib(i - 2) >>= ...
.
这看起来更简单,但没有 Scalaz:
def fib(i: Int): Option[Int] = i match {
case 0 => 0.some //Ok, a little bit of Scalaz's syntax sugar
case 1 => 1.some
case x if x > 1 => for {
a <- fib(i - 1)
b <- fib(i - 2)
} yield a + b
case _ => None
}
基本上是Haskell的do-notation,在这里你可以看到<-
作为>>=
(绑定)的更方便的版本。实际上它被编译器扩展为 flatMap
。
当你需要为更复杂的类型做这件事时,Scalaz 可以提供帮助(假设你想做它 async/effectful,但仍然是有序的)——你可以使用 monadic 转换器。这是来自 cats 文档的解释(与 scalaz 非常相似,但更清晰):http://typelevel.org/cats/tut/optiont.html.
当事情变得更加复杂时(假设您需要管理 Future
或 Task
的有效计算以及错误处理),使用高级转换器可能会有所帮助,例如:https://github.com/djspiewak/emm
P.S。我们需要这一切来实现斐波那契吗?可能不是,但我们仍然可以想象 "minus" 和 "plus" 的外部服务,所以在那种情况下 Scalaz/Cats 会有所帮助。
def minus(a: Int, b: Int): Task[Int] = Task.delay(a - b)
def plus(a: Int, b: Int): Task[Int] = Task.delay(a + b)
def fib(i: Int): Task[Option[Int]] = {...}
哪个 scalaz/cats 可以简化(至少绑定自己 :) )到:
def minus(a: Int, b: Int): OptionT[Task, String] = OptionT(Task.delay(Some(a - b)))
def plus(a: Int, b: Int): OptionT[Task, String] = OptionT(Task.delay(Some(a - b)))
然后到:
def minus(a: Int, b: Int): OptionT[Task, String] = OptionT.liftF(Task.delay(a - b))
def plus(a: Int, b: Int): OptionT[Task, String] = OptionT.liftF(Task.delay(a - b))
def fib(i: Int): OptionT[Task, String] = i match {
case 0 => OptionT.pure(0)//OptionT.fromOption(0.some)
case 1 => OptionT.pure(1)//OptionT.fromOption(1.some)
case x if x > 1 => for {
a <- fib(minus(i, 1))
b <- fib(minus(i, 2))
sum <- plus(a, b)
} yield sum
case _ => None
}
错误处理示例(scalaz/fs2 Task
基本上封装了它们,但假设您想要精确类型而不是 Throwable
)
type ServiceError = Throwable //use Xor.catchNonFatal to catch errors from external service
def minus(a: Int, b: Int): Task[Xor[ServiceError, Int]] = Task.delay((a - b).right)
def plus(a: Int, b: Int): Task[Xor[ServiceError, Int]] = Task.delay((a + b).right)
def fib(i: Int): Task[Option[Xor[ServiceError, Int]]] = {...}
Emm 将其简化为:
type ServiceError = Throwable //use Xor.catchNonFatal to catch errors from external service
//Note: you need kind-projector for that syntax: https://github.com/non/kind-projector
type E = Task |: (ServiceError \/ ?) |: Option |: Base
def minus(a: Int, b: Int): Emm[E, Int] = ...
def plus(a: Int, b: Int): Emm[E, Int] = ...
def fib(i: Int): Emm[E, Int] = {...}
您可以尝试完成这些示例作为练习。
P.S.2
您还可以简化模式匹配:
def fib(i: Int): Option[Int] = i match {
case x if x < 0 => None //explicit validation comes first
case x if x > 1 => for {
a <- fib(i - 1)
b <- fib(i - 2)
} yield a + b
case x => x.some
}
您可以尝试使用 Xor
/\/
进行验证作为练习:http://typelevel.org/cats/tut/xor.html
P.S.3 请注意,您的所有实现都不是堆栈安全的(没有 tailrec 优化),因此您可能需要 Trampoline 来补偿(取决于您的实际任务,因为斐波那契的实现要容易得多以堆栈安全的方式,但负数没有错误,你基本上可以抛出异常或 return 0):
http://blog.richdougherty.com/2009/04/tail-calls-tailrec-and-trampolines.html
斯卡拉兹:http://eed3si9n.com/learning-scalaz/Stackless+Scala+with+Free+Monads.html
猫蹦床只是 Free[Function0, A]
:
https://github.com/typelevel/cats/blob/master/free/src/main/scala/cats/free/Trampoline.scala
谈到 Task
,它实际上是在内部蹦床,所以将它用于此类计算是安全的 - 因此单子变换器 (OptionT[Task, Int]
) 在这里派上用场。
补充dk14的回答:
因为fib(i - 2)
不依赖fib(i - 1)
,我们其实不需要>>=
/bind
/flatMap
,我们可以这样做Applicative
.
您的嵌套 >>=
(或理解的等效项)可以替换为:
Apply[Option].apply2(fib(i - 1), fib(i - 2))(_ + _)
或者更简单地使用应用程序构建器语法:
(fib(i - 1) |@| fib(i - 2))(_ + _)
我使用 bing 操作在 Scalaz
上写了一个斐波那契函数。这是我的代码:
import scalaz._, Scalaz._
def fib(i: Int): Option[Int] = i match {
case 0 => 0.some
case 1 => 1.some
case x if x > 1 =>
fib(i - 1) >>= {
a => fib(i - 2) >>= {
b => (a + b).some
}
}
case _ => None
}
可以简化吗?我不喜欢这样的东西 a => fib(i - 2) >>= ...
.
这看起来更简单,但没有 Scalaz:
def fib(i: Int): Option[Int] = i match {
case 0 => 0.some //Ok, a little bit of Scalaz's syntax sugar
case 1 => 1.some
case x if x > 1 => for {
a <- fib(i - 1)
b <- fib(i - 2)
} yield a + b
case _ => None
}
基本上是Haskell的do-notation,在这里你可以看到<-
作为>>=
(绑定)的更方便的版本。实际上它被编译器扩展为 flatMap
。
当你需要为更复杂的类型做这件事时,Scalaz 可以提供帮助(假设你想做它 async/effectful,但仍然是有序的)——你可以使用 monadic 转换器。这是来自 cats 文档的解释(与 scalaz 非常相似,但更清晰):http://typelevel.org/cats/tut/optiont.html.
当事情变得更加复杂时(假设您需要管理 Future
或 Task
的有效计算以及错误处理),使用高级转换器可能会有所帮助,例如:https://github.com/djspiewak/emm
P.S。我们需要这一切来实现斐波那契吗?可能不是,但我们仍然可以想象 "minus" 和 "plus" 的外部服务,所以在那种情况下 Scalaz/Cats 会有所帮助。
def minus(a: Int, b: Int): Task[Int] = Task.delay(a - b)
def plus(a: Int, b: Int): Task[Int] = Task.delay(a + b)
def fib(i: Int): Task[Option[Int]] = {...}
哪个 scalaz/cats 可以简化(至少绑定自己 :) )到:
def minus(a: Int, b: Int): OptionT[Task, String] = OptionT(Task.delay(Some(a - b)))
def plus(a: Int, b: Int): OptionT[Task, String] = OptionT(Task.delay(Some(a - b)))
然后到:
def minus(a: Int, b: Int): OptionT[Task, String] = OptionT.liftF(Task.delay(a - b))
def plus(a: Int, b: Int): OptionT[Task, String] = OptionT.liftF(Task.delay(a - b))
def fib(i: Int): OptionT[Task, String] = i match {
case 0 => OptionT.pure(0)//OptionT.fromOption(0.some)
case 1 => OptionT.pure(1)//OptionT.fromOption(1.some)
case x if x > 1 => for {
a <- fib(minus(i, 1))
b <- fib(minus(i, 2))
sum <- plus(a, b)
} yield sum
case _ => None
}
错误处理示例(scalaz/fs2 Task
基本上封装了它们,但假设您想要精确类型而不是 Throwable
)
type ServiceError = Throwable //use Xor.catchNonFatal to catch errors from external service
def minus(a: Int, b: Int): Task[Xor[ServiceError, Int]] = Task.delay((a - b).right)
def plus(a: Int, b: Int): Task[Xor[ServiceError, Int]] = Task.delay((a + b).right)
def fib(i: Int): Task[Option[Xor[ServiceError, Int]]] = {...}
Emm 将其简化为:
type ServiceError = Throwable //use Xor.catchNonFatal to catch errors from external service
//Note: you need kind-projector for that syntax: https://github.com/non/kind-projector
type E = Task |: (ServiceError \/ ?) |: Option |: Base
def minus(a: Int, b: Int): Emm[E, Int] = ...
def plus(a: Int, b: Int): Emm[E, Int] = ...
def fib(i: Int): Emm[E, Int] = {...}
您可以尝试完成这些示例作为练习。
P.S.2
您还可以简化模式匹配:
def fib(i: Int): Option[Int] = i match {
case x if x < 0 => None //explicit validation comes first
case x if x > 1 => for {
a <- fib(i - 1)
b <- fib(i - 2)
} yield a + b
case x => x.some
}
您可以尝试使用 Xor
/\/
进行验证作为练习:http://typelevel.org/cats/tut/xor.html
P.S.3 请注意,您的所有实现都不是堆栈安全的(没有 tailrec 优化),因此您可能需要 Trampoline 来补偿(取决于您的实际任务,因为斐波那契的实现要容易得多以堆栈安全的方式,但负数没有错误,你基本上可以抛出异常或 return 0):
http://blog.richdougherty.com/2009/04/tail-calls-tailrec-and-trampolines.html
斯卡拉兹:http://eed3si9n.com/learning-scalaz/Stackless+Scala+with+Free+Monads.html
猫蹦床只是 Free[Function0, A]
:
https://github.com/typelevel/cats/blob/master/free/src/main/scala/cats/free/Trampoline.scala
谈到 Task
,它实际上是在内部蹦床,所以将它用于此类计算是安全的 - 因此单子变换器 (OptionT[Task, Int]
) 在这里派上用场。
补充dk14的回答:
因为fib(i - 2)
不依赖fib(i - 1)
,我们其实不需要>>=
/bind
/flatMap
,我们可以这样做Applicative
.
您的嵌套 >>=
(或理解的等效项)可以替换为:
Apply[Option].apply2(fib(i - 1), fib(i - 2))(_ + _)
或者更简单地使用应用程序构建器语法:
(fib(i - 1) |@| fib(i - 2))(_ + _)