代码之家  ›  专栏  ›  技术社区  ›  oxbow_lakes

scala功能性程序设计体操

  •  10
  • oxbow_lakes  · 技术社区  · 15 年前

    我正在尝试尽可能少的代码和尽可能多的功能性地执行以下操作:

    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)
      }
    }
    
    13 回复  |  直到 14 年前
        1
  •  16
  •   Debilski    15 年前

    编辑2 :

    同时思考 cataX 方法,我发现 卡塔克斯 只不过是一个简单朴素的褶皱。使用它,我们可以得到一个纯的scala解决方案,而不需要任何额外的库。

    所以,这里是:

    ( (amt /: floor)(_ max _) /: cap)(_ min _)
    

    这和

    cap.foldLeft( floor.foldLeft(amt)(_ max _) )(_ min _)
    

    (但这并不一定更容易理解)。

    我想你不能再短了。


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

    floor.map(amt max).getOrElse(amt) |> (m => cap.map(m min).getOrElse(m))
    

    甚至:

    floor.cata(amt max, amt) |> (m => cap.cata(m min, m))
    

    作为一个普通的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
    }
    

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

    参见scalaz定义 cata |> .

    更对称的解决方案是:

    amt |> (m => floor.cata(m max, m)) |> (m => cap.cata(m min, m))
    

    编辑: 抱歉,现在很奇怪,但我也想要一个无点版本。新的 卡塔克斯 课程设置。第一个参数接受一个二进制函数;第二个参数是一个值。

    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)
    

    如果 o 比赛 Some 我们返回函数的值为 o 以及应用的第二个参数,如果 o 比赛 None 我们只返回第二个参数。

    然后我们开始:

    amt |> floor.cataX(_ max _) |> cap.cataX(_ min _)
    

    也许他们已经把这个放在斯卡利兹了?

        2
  •  15
  •   Miles Sabin    15 年前

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

    List(floor.getOrElse(Double.NegativeInfinity), cap.getOrElse(Double.PositiveInfinity), amt).sorted.apply(1)
    
        3
  •  5
  •   Daniel C. Sobral    15 年前

    我先从这个开始:

    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)                                                      
    }                                                                                    
    

    但我觉得我在这里错过了一些机会,所以我可能还没有结束。

        4
  •  5
  •   retronym    15 年前

    而不是纯粹的简洁,这显示了如果你转向 cap floor 到函数中。

    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)
    

    从斯卡拉斯,我用 MA#∘ ,函数映射,作为使用 Option.map Function1.andThen OptionW#| 它是的别名 Option.getOrElse .

    更新

    这就是我要找的:

    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)
    

    scalaz.Endo[A] 是包装纸吗 A => A ,两个方向都有隐式转换。有一个实例 Monoid 为定义 Endo[A] , Monoid#plus 连锁功能,以及 Monoid#zero 返回Identity函数。如果我们有 List 属于 恩多 ,我们可以对列表和结果求和,得到一个值,可以用作 A=>A .

    MA#foldMap 将给定函数映射到 Foldable 数据类型,并使用 幺半群 . foldMapEndo 在这上面是一个方便。这个抽象允许您很容易地从验证盖帽和地板 Option S到任何可折叠类型,例如 .

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

    另一个重构可能导致:

    val capAndFloor = Seq((cap, min), (floor, max)).foldMap { case (a, f) => foldMapEndo(a, f) }
    capAndFloor: scalaz.Endo[Int] = scalaz.Endos$$anon$1@25b85c8e
    
        5
  •  4
  •   Landei    15 年前

    这个怎么样?

    //WRONG
    def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
       (floor.getOrElse(amt) max amt) min cap.getOrElse(amt)
    

    [编辑]

    第二次尝试:

    def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
       floor.map(f => f max _).getOrElse(identity[Double] _)(
         cap.map(c => c min _).getOrElse(identity[Double] _)(amt))
    

    看起来有点“口齿不清”,但通过了测试:—)

    [第二次编辑]

    第一个版本也可以“修复”,也可以:

    def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double =
      (floor.getOrElse(-Double.MaxValue) max amt) min cap.getOrElse(Double.MaxValue)
    
        6
  •  4
  •   Kevin Wright    15 年前

    不漂亮,不短,当然也不快! 但它更容易组合,更通用,更“实用”:

    编辑 :使代码完全通用:)

    def optWith[T](a: Option[T], b: T)(op:(T,T)=>T) =
      a map (op(b,_)) getOrElse b
    
    def optMin[T:Numeric](a: Option[T]) =
      (b:T) => optWith(a, b)(implicitly[Numeric[T]].min)
    
    def optMax[T:Numeric](a: Option[T]) =
      (b:T) => optWith(a, b)(implicitly[Numeric[T]].max)
    
    def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT])
      (implicit ev:Numeric[T], fv:FT=>T, cv:CT=>T) =
      optMin(ceil map cv) compose optMax(floor map fv) apply(x)
    

    更新2 :还有这个版本,可以更好地利用 Numeric

    def optWith[T](a: Option[T])(op:(T,T)=>T) =
      (b:T) => a map (op(b,_)) getOrElse b
    
    def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT])
      (implicit n:Numeric[T], fv:FT=>T, cv:CT=>T) =
      optWith(ceil map cv)(n.min) compose optWith(floor map fv)(n.max) apply(x)
    

    希望你喜欢打字签名:)

    更新3 :这里有一个同样适用于 界限

    def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) =
      (b:T) => a map (op(b,_)) getOrElse b
    
    def restrict[T:Numeric, FT <% T, CT <% T]
    (floor:Option[FT], ceil:Option[CT], amt:T) = {
      val n = implicitly[Numeric[T]]; import n._
      optWith(min)(ceil) compose
      optWith(max)(floor) apply(amt)
    }
    

    如果没有别的…这很清楚地说明了为什么导入参数是一件好事(tm)。假设以下代码有效:

    def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) =
      (b:T) => a map (op(b,_)) getOrElse b
    
    def restrict[import T:Numeric,FT <% T,CT <% T]
    (floor:Option[FT], ceil:Option[CT], amt:T) = {
      optWith(min)(ceil) compose
      optWith(max)(floor) apply(amt)
    }
    

    更新4 :把溶液倒过来。这一个为将来的扩展提供了一些更有趣的可能性。

    implicit def optRhs[T:Ordering](lhs:T) = new Object {
      val ord = implicitly[Ordering[T]]; import ord._
    
      private def opt(op: (T,T)=>T)(rhs:Option[T]) =
        rhs map (op(lhs,_)) getOrElse lhs
    
      def max = opt(ord.max) _
      def min = opt(ord.min) _
    }
    
    def restrict[T : Ordering](floor:Option[T], cap:Option[T], amt:T) =
      amt min cap max floor 
    

    如果运气好的话,我会鼓励其他人用我的方法构建一个更好的解决方案。这就是这些事情通常的解决方法…

        7
  •  2
  •   Rex Kerr    15 年前

    这在scalaz中并不比在普通scala中容易:

    def restrict(floor: Option[Double], cap: Option[Double], amt: Double) =
      floor.map(amt max).orElse(Some(amt)).map(x => cap.map(x min).getOrElse(x)).get
    

    (添加) _ 之后 max min 如果它能让您更好地看到参数的去向。)

    不过,一旦理解了操作符的作用,scalaz就更容易阅读了。

        8
  •  2
  •   Community Mohan Dere    8 年前

    我发现当一个问题要求使用 Option 为了指示可选参数,通常有一种更自然的方法来表示缺少的参数。所以我将在这里稍微改变一下接口,并使用默认参数定义函数和命名参数来调用函数。

    def restrict(amt:Double,
                floor:Double = Double.NegativeInfinity,
                cap:Double=Double.PositiveInfinity):Double =
        (amt min cap) max floor
    

    然后你可以打电话给:

    restrict(6)
    restrict(6, floor = 7)
    restrict(6, cap = 5)
    

    ( Another example of the same principle. )

        9
  •  2
  •   Community Mohan Dere    8 年前

    这是 based on Ken Bloom's answer :

    sealed trait Constrainer { def constrain(d : Double) : Double }
    
    trait Cap extends Constrainer
    trait Floor extends Constrainer
    case object NoCap extends Cap { def constrain(d : Double) = d }
    case object NoFloor extends Floor { def constrain(d : Double) = d }
    implicit def d2cap(d : Double) = new Cap { def constrain(amt : Double) = d min amt }
    implicit def d2floor(d : Double) = new Floor { def constrain(amt : Double) = d max amt }
    
    def restrict(amt : Double, cap : Cap = NoCap, floor: Floor = NoFloor) : Double = {
      cap.constrain(floor.constrain(amt))
      //or (cap.constrain andThen floor.constrain) amt
    }
    

    最后写下这样的代码:

    restrict(amt, cap = 5D)
    restrict(amt, floor = 0D)
    

    我认为这是非常棒的,并且不会受到Ken解决方案的问题的影响(在我看来),这是一个 乱劈 !

        10
  •  1
  •   Community Mohan Dere    8 年前

    这是另一种解决方法 Landei's first answer

    def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
      val chopBottom = (floor.getOrElse(amt) max amt) 
      chopBottom min cap.getOrElse(chopBottom)
    }
    
        11
  •  0
  •   oxbow_lakes    15 年前

    我正在添加另一个答案,它受到了两个问题的启发 反义词 德比尔斯基 -基本上,它相当于将上限和下限转换为功能( Double => Double ,如果它们存在的话),然后用构图将标识函数折叠起来:

    def restrict(floor: Option[Double], cap: Option[Double], amt: Double) = {
      (identity[Double] _ /: List(floor.map(f => (_: Double) max f), cap.map(c => (_: Double) min c)).flatten){ _ andThen _ }(amt)
    }
    
        12
  •  0
  •   Nikolay Artamonov    14 年前

    简单的解决方案,使用普通的scala和匿名lambda,不带任何映射、折叠、双精度。最小/最大值,等等:

    def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double =
      ((x:Double) => x min cap.getOrElse(x))(amt max floor.getOrElse(amt))
    
        13
  •  0
  •   user unknown    14 年前

    我最喜欢匹配案例的初始解决方案——除此之外,我不明白 amt 方法 amount (在德国,“amt”的意思是“office”),我只知道 cap 作为我头上穿的东西…

    下面是一个真正没有灵感的解决方案,使用内部方法:

    def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
      def restrict (floor: Double, cap: Double, amt: Double) =
        (floor max amt) min cap
      var f = floor.getOrElse (amt)            
      val c = cap.getOrElse (amt)   
      restrict (f, c, amt) 
    }
    
    推荐文章