Scala函数式编程体操

oxb*_*kes 10 functional-programming scala scalaz

我试图在尽可能少的代码和尽可能功能的情况下执行以下操作:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double
Run Code Online (Sandbox Code Playgroud)

显然以下工作:

= (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
  }
Run Code Online (Sandbox Code Playgroud)

我真的希望有更优雅的东西,并且会接受使用Scalaz库!您可以假设以下情况属实:

floor.forall( f => cap.forall( _ > f))
Run Code Online (Sandbox Code Playgroud)

如果有人有兴趣,这里有一些测试代码:

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)
  }
}
Run Code Online (Sandbox Code Playgroud)

Deb*_*ski 16

编辑2:

在考虑这种cataX方法时,我发现这cataX只不过是一个简单而简单的折叠.使用它,我们可以获得纯scala解决方案,而无需任何其他库.

所以,这里是:

( (amt /: floor)(_ max _) /: cap)(_ min _)
Run Code Online (Sandbox Code Playgroud)

这是一样的

cap.foldLeft( floor.foldLeft(amt)(_ max _) )(_ min _)
Run Code Online (Sandbox Code Playgroud)

(并不是说这一点更容易理解).

我认为你不能比这更短.


无论好坏,我们也可以使用scalaz解决它:

floor.map(amt max).getOrElse(amt) |> (m => cap.map(m min).getOrElse(m))
Run Code Online (Sandbox Code Playgroud)

甚至:

floor.cata(amt max, amt) |> (m => cap.cata(m min, m))
Run Code Online (Sandbox Code Playgroud)

作为一个"普通"的Scala程序员,人们可能不知道使用的特殊Scalaz运算符和方法(|>Option.cata).它们的工作原理如下:

value |> function转化为function(value)并因此amt |> (m => v fun m)等于v fun amt.

opt.cata(fun, v) 翻译成

opt match {
  case Some(value) => fun(value) 
  case None => v
}
Run Code Online (Sandbox Code Playgroud)

opt.map(fun).getOrElse(v).

请参阅Scalaz定义cata|>.

更对称的解决方案是:

amt |> (m => floor.cata(m max, m)) |> (m => cap.cata(m min, m))
Run Code Online (Sandbox Code Playgroud)

编辑:对不起,现在变得很奇怪了,但我想要一个无点版本.新的cataX是咖喱.第一个参数采用二进制函数; 第二个是价值.

class CataOption[T](o: Option[T]) {
  def cataX(fun: ((T, T) => T))(v: T) = o.cata(m => fun(m, v), v)
}
implicit def option2CataOption[T](o: Option[T]) = new CataOption[T](o)
Run Code Online (Sandbox Code Playgroud)

如果o匹配,Some我们返回值为o和应用第二个参数的函数,如果o匹配,None我们只返回第二个参数.

现在我们开始:

amt |> floor.cataX(_ max _) |> cap.cataX(_ min _)
Run Code Online (Sandbox Code Playgroud)

也许他们已经在Scalaz中有这个...?

  • 你应该得到所有的观点 - 中间答案充满了令人敬畏的.可能是一个想法来阐明正在发生的事情 (2认同)
  • @Debilski FWIW,`|>`是最重要的F#运算符之一. (2认同)

Mil*_*bin 15

不像scalaz版本那么简洁,但另一方面,没有依赖,

List(floor.getOrElse(Double.NegativeInfinity), cap.getOrElse(Double.PositiveInfinity), amt).sorted.apply(1)
Run Code Online (Sandbox Code Playgroud)


Dan*_*ral 5

我将从这开始:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
  val flooring = floor.map(f => (_: Double) max f).getOrElse(identity[Double] _)       
  val capping  = cap.map(f => (_: Double) min f).getOrElse(identity[Double] _)         
  (flooring andThen capping)(amt)                                                      
}                                                                                    
Run Code Online (Sandbox Code Playgroud)

但我觉得我在这里错过了一些机会,所以我可能没有完成.


ret*_*nym 5

这不仅仅是为了简洁,而是表明如果你转向capfloor进入功能,组合会变得多么容易.

scala> val min = (scala.math.min _).curried                                        
min: (Int) => (Int) => Int = <function1>

scala> val max = (scala.math.max _).curried                                        
max: (Int) => (Int) => Int = <function1>

scala> def orIdentity[A](a: Option[A])(f: A => A => A): (A => A) = a ? f | identity
orIdentity: [A](a: Option[A])(f: (A) => (A) => A)(A) => A

scala> val cap = 5.some; val floor = 1.some                                        
cap: Option[Int] = Some(5)
floor: Option[Int] = Some(1)

scala> val ffloor = orIdentity(floor)(max)                                         
ffloor: (Int) => Int = <function1>

scala> val fcap = orIdentity(cap)(min)                                             
fcap: (Int) => Int = <function1>

scala> val capAndFloor = fcap ? ffloor                                             
capAndFloor: (Int) => Int = <function1>    

scala> (0 to 8).toSeq ? (capAndFloor)    
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5)
Run Code Online (Sandbox Code Playgroud)

从scalaz,我使用MA#?的仿函数图,无论是作为使用的方式Option.mapFunction1.andThen; 并且OptionW#|这是一个别名Option.getOrElse.

UPDATE

这就是我要找的东西:

scala> import scalaz._; import Scalaz._
import scalaz._
import Scalaz._

scala> val min = (scala.math.min _).curried                                        
min: (Int) => (Int) => Int = <function1>

scala> val max = (scala.math.max _).curried                                        
max: (Int) => (Int) => Int = <function1>

scala> def foldMapEndo[F[_]: Foldable, A](fa: F[A], f: A => A => A): Endo[A] = 
     |    fa.foldMap[Endo[A]](a => f(a))                                       
foldMapEndo: [F[_],A](fa: F[A],f: (A) => (A) => A)(implicit evidence$1: scalaz.Foldable[F])scalaz.Endo[A]

scala> val cap = 5.some; val floor = 1.some                                    
cap: Option[Int] = Some(5)
floor: Option[Int] = Some(1)    

scala> val capAndFloor = List(foldMapEndo(floor, max), foldMapEndo(cap, min)) ?
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$$anon$1@4352d1fc

scala>(0 to 10).toSeq.map(capAndFloor)                                               
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5)
Run Code Online (Sandbox Code Playgroud)

scalaz.Endo[A]是一个包装器A => A,两个方向都有隐式转换.有一个Monoid定义的实例Endo[A],Monoid#plus链接函数,并Monoid#zero返回标识函数.如果我们有一个ListEndo[A],我们可以归纳列表并导致单个值,可以用来作为A => A.

MA#foldMap将给定函数映射到Foldable数据类型,并使用a减少为单个值Monoid.foldMapEndo这是一个方便的.这种抽象允许您轻松地从证明Options中的cap和floor 到任何可折叠类型,例如a List.

val capAndFloor = Seq(foldMapEndo(List(1, 2, max), foldMapEndo(cap, min)).collapsee
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$$anon$1@76f40c39
Run Code Online (Sandbox Code Playgroud)

另一种重构可能会导致:

val capAndFloor = Seq((cap, min), (floor, max)).foldMap { case (a, f) => foldMapEndo(a, f) }
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$$anon$1@25b85c8e
Run Code Online (Sandbox Code Playgroud)