Scala 函数式编程体操
我试图用尽可能少的代码和尽可能多的功能来执行以下操作:
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double
显然以下工作有效:
= (floor -> cap) match {
case (None, None) => amt
case (Some(f), None) => f max amt
case (None, Some(c)) => c min amt
case (Some(f), Some(c)) => (f max amt) min c
}
我真的希望有一些更优雅的东西,并且会接受使用 Scalaz 库! 您可以假设以下内容为真:
floor.forall( f => cap.forall( _ > f))
如果有人感兴趣,这里有一些测试代码:
object Comparisons {
sealed trait Cf {
def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double
}
def main(args: Array[String]) {
val cf : Cf = //TODO - your impl here!
def runtest(floor: Option[Double], cap: Option[Double], amt: Double, exp : Double) : Unit = {
val ans = cf.restrict(floor, cap, amt)
println("floor=%s, cap=%s, amt=%s === %s (%s) : %s".format(floor, cap, amt, ans, exp, if (ans == exp) "PASSED" else "FAILED"))
}
runtest(Some(3), Some(5), 2, 3)
runtest(Some(3), Some(5), 3, 3)
runtest(Some(3), Some(5), 4, 4)
runtest(Some(3), Some(5), 5, 5)
runtest(Some(3), Some(5), 6, 5)
runtest(Some(3), None, 2, 3)
runtest(Some(3), None, 3, 3)
runtest(Some(3), None, 4, 4)
runtest(Some(3), None, 5, 5)
runtest(Some(3), None, 6, 6)
runtest(None, Some(5), 2, 2)
runtest(None, Some(5), 3, 3)
runtest(None, Some(5), 4, 4)
runtest(None, Some(5), 5, 5)
runtest(None, Some(5), 6, 5)
runtest(None, None, 2, 2)
runtest(None, None, 3, 3)
runtest(None, None, 4, 4)
runtest(None, None, 5, 5)
runtest(None, None, 6, 6)
}
}
I am trying to do the following in as little code as possible and as functionally as possible:
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double
Obviously the following works:
= (floor -> cap) match {
case (None, None) => amt
case (Some(f), None) => f max amt
case (None, Some(c)) => c min amt
case (Some(f), Some(c)) => (f max amt) min c
}
I was really hoping for something more elegant and will accept use of the Scalaz library! You can assume that the following is true:
floor.forall( f => cap.forall( _ > f))
If anyone is interested, here is some test code:
object Comparisons {
sealed trait Cf {
def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double
}
def main(args: Array[String]) {
val cf : Cf = //TODO - your impl here!
def runtest(floor: Option[Double], cap: Option[Double], amt: Double, exp : Double) : Unit = {
val ans = cf.restrict(floor, cap, amt)
println("floor=%s, cap=%s, amt=%s === %s (%s) : %s".format(floor, cap, amt, ans, exp, if (ans == exp) "PASSED" else "FAILED"))
}
runtest(Some(3), Some(5), 2, 3)
runtest(Some(3), Some(5), 3, 3)
runtest(Some(3), Some(5), 4, 4)
runtest(Some(3), Some(5), 5, 5)
runtest(Some(3), Some(5), 6, 5)
runtest(Some(3), None, 2, 3)
runtest(Some(3), None, 3, 3)
runtest(Some(3), None, 4, 4)
runtest(Some(3), None, 5, 5)
runtest(Some(3), None, 6, 6)
runtest(None, Some(5), 2, 2)
runtest(None, Some(5), 3, 3)
runtest(None, Some(5), 4, 4)
runtest(None, Some(5), 5, 5)
runtest(None, Some(5), 6, 5)
runtest(None, None, 2, 2)
runtest(None, None, 3, 3)
runtest(None, None, 4, 4)
runtest(None, None, 5, 5)
runtest(None, None, 6, 6)
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
编辑2:
在思考
cataX
方法时,我发现cataX
只不过是一个简单的折叠。使用它,我们可以获得一个纯 scala 解决方案,无需任何额外的库。所以,这就是:
与 相同
(并不是说这一定更容易理解)。
我想你不能有比这更短的时间了。
无论好坏,我们也可以使用 scalaz 来解决它:
甚至:
作为一名“普通”Scala 程序员,人们可能不知道所使用的特殊 Scalaz 运算符和方法(
|>
和>Option.cata
)。它们的工作原理如下:value |> function
转换为function(value)
,因此amt |> (m => v fun m)
等于v fun amt
。opt.cata(fun, v)
转换为或
opt.map(fun).getOrElse(v)
。请参阅
cata
和
| >
。一个更对称的解决方案是:
编辑: 抱歉,现在变得很奇怪,但我也想有一个无积分版本。新的
cataX
已柯里化。第一个参数采用二元函数;第二个是一个值。如果
o
匹配Some
,我们将返回具有o
值的函数并应用第二个参数(如果o
匹配)None
我们只返回第二个参数。我们开始吧:
也许他们已经在 Scalaz 中拥有了这个……?
Edit 2:
While thinking about the
cataX
method, I figured out thatcataX
is nothing else than a plain and simple fold. Using that, we can get a pure scala solution without any additional libraries.So, here it is:
which is the same as
(not that this is necessarily easier to understand).
I think you can’t have it any shorter than that.
For better or worse, we can also solve it using scalaz:
or even:
As a ‘normal’ Scala programmer, one might not know about the special Scalaz operators and methods used (
|>
andOption.cata
). They work as follows:value |> function
translates tofunction(value)
and thusamt |> (m => v fun m)
is equal tov fun amt
.opt.cata(fun, v)
translates toor
opt.map(fun).getOrElse(v)
.See the Scalaz definitions for
cata
and|>
.A more symmetric solution would be:
Edit: Sorry, it’s getting weird now, but I wanted to have a point-free version as well. The new
cataX
is curried. The first parameter takes a binary function; the second is a value.If
o
matchesSome
we return the function with the value ofo
and the second parameter applied, ifo
matchesNone
we only return the second parameter.And here we go:
Maybe they already have this in Scalaz…?
不像 scalaz 版本那么简洁,但另一方面,没有依赖项,
Not quite as terse as the scalaz version, but on the other hand, no dependencies,
我将从这个开始:
但我感觉我在这里错过了一些机会,所以我可能还没有完成。
I'll start with this:
But I have the feeling I'm missing some opportunity here, so I may not be finished.
这表明,如果将
cap
和floor
转换为函数,组合会变得多么容易,而不是纯粹的简洁。在 scalaz 中,我使用函子映射 MA#∘ 来作为使用 Option.map 和 Function1.andThen 的一种方式;和 OptionW#|,它是 Option.getOrElse 的别名。
更新
这就是我一直在寻找的:
scalaz.Endo[A]
是A => 的包装器。 A
,双向都有隐式转换。有一个为Endo[A]
定义的Monoid
实例,Monoid#plus
链接函数,Monoid#zero
code> 返回恒等函数。如果我们有一个Endo[A]
的List
,我们可以对列表求和并得出单个值,该值可以用作A =>一个。
MA#foldMap
将给定函数映射到Foldable
数据类型,并使用Monoid
简化为单个值。foldMapEndo
在此之上提供了便利。这种抽象允许您轻松地将证明Option
中的上限和下限更改为任何可折叠类型,例如List
。另一次重构可能会导致:
Rather than going for pure brevity, this shows how much easier composition becomes if you turn
cap
andfloor
into functions.From scalaz, I use
MA#∘
, the functor map, both as a way of usingOption.map
andFunction1.andThen
; andOptionW#|
which is an alias forOption.getOrElse
.UPDATE
This is what I was looking for:
scalaz.Endo[A]
is a wrapper aroundA => A
, there are implicit conversions in both directions. There is an instance ofMonoid
defined forEndo[A]
,Monoid#plus
chains the functions, andMonoid#zero
returns the identity function. If we have aList
ofEndo[A]
, we can sum the list and result in a single value, which can be used asA => A
.MA#foldMap
maps the given function over aFoldable
data type, and reduces to a single value with aMonoid
.foldMapEndo
is a convenience on top of this. This abstraction allows you to easily change from proving the cap and floor inOption
s to any foldable type, such as aList
.Another refactoring might lead to:
这个怎么样?[编辑]
第二次尝试:
对于我的口味来说,看起来有点太“lispy”,但通过了测试:-)
[第二次编辑]
第一个版本也可以“修复” :
How about this?[Edit]
Second try:
Looks a little bit too "lispy" for my taste, but passes the tests :-)
[2nd Edit]
The first version can be "repaired", too:
没有更漂亮,没有更短,当然也没有更快!
但它更可组合,更通用,更“功能”:
编辑:使代码完全通用:)
更新2:还有这个版本,更好地利用
数字
我希望你喜欢类型签名:)
更新3:这里有一个与边界做同样的事情
如果没有别的......这很清楚地表明了原因导入参数将是一件好事。想象一下,如果以下是有效的代码:
更新 4:将解决方案颠倒过来。这为未来的扩展提供了一些更有趣的可能性。
如果幸运的话,我将激励其他人利用我的解决方案构建更好的解决方案。这些事情通常都是这样进行的...
Not prettier, not much shorter, and certainly not faster!
But it is more composable more generic and more "functional":
EDIT: made the code fully generic :)
UPDATE 2: There's also this version, taking better advantage of
Numeric
I hope you like type signatures :)
UPDATE 3: Here's one that does the same with bounds
If nothing else... this shows quite clearly why import parameters would be a Good Thing(tm). Imagine if the following was valid code:
UPDATE 4: Turning the solution upside down here. This one offers some more interesting possibilities for future extension.
With any luck, I'll inspire someone else to build a better solution out of mine. That's how these things usually work out...
这在 Scalaz 中并不比在常规 Scala 中容易多少:(
在
max
和min
之后添加_
如果这让你感觉更好看的话 不过,一旦您了解了运算符的作用,Scalaz 就更容易阅读了。
This isn't really much easier in Scalaz than in regular Scala:
(Add
_
aftermax
andmin
if it makes you feel better to see where the parameter goes.)Scalaz is a little easier to read, though, once you understand what the operators do.
我发现当问题要求使用
Option
来指示可选参数时,通常有一种更自然的方式来表示缺少的参数。因此,我将在这里稍微更改一下接口,并使用默认参数来定义函数并使用命名参数来调用该函数。然后你可以调用:(
相同原理的另一个示例。)
I find that when a question asks to use an
Option
to indicate an optional parameter, there's usually a more natural way to represent the missing parameter. So I'm going to change the interface a little here, and use default arguments to define the function and named parameters to call the function.Then you can call:
(Another example of the same principle.)
这是基于 Ken Bloom 的答案:
最终会编写这样的代码:
我认为这非常棒,并且不会遇到 Ken 解决方案的问题(在我看来),即它是一个黑客!
This is based on Ken Bloom's answer:
It ends up with writing code like this:
I think that's pretty awesome and doesn't suffer from the problem with Ken's solution (in my opinion), which is that it is a hack!
这是修复 Landei 的第一个答案的另一种方法
This is another way to fix Landei's first answer
我添加了另一个答案,该答案的灵感来自于 retronym 和 Debilski - 基本上相当于将上限和下限转换为函数 (
Double => Double< /code>,如果它们存在),然后通过它们组合折叠恒等函数:
I'm adding another answer which was inspired by both retronym and Debilski - basically it amounts to converting the cap and floor to functions (
Double => Double
, if they are present) and then folding the identity function through them with composition:使用普通 Scala 和匿名 lambda 的简单解决方案,没有任何映射、折叠、Double.{Min/Max}Value 等:
Straightforward solution with plain Scala and anonymous lambda, without any mappings, folds, Double.{Min/Max}Value, and so on:
我最喜欢匹配大小写的初始解决方案 - 除此之外,我不明白
amt
意味着amount
(在德国,'amt' 意味着 'office '),我只知道cap
是我戴在头上的东西......现在这是一个非常平庸的解决方案,使用内部方法:
I like the initial solution with the match-case most - beside the fact, that I didn't understand that
amt
meansamount
(in Germany, 'amt' means 'office') and I only knewcap
as something I wear on my head ...Now here is a really uninspired solution, using an inner method: