14

我猜想必须有一种更好的功能方式来表达以下内容:

def foo(i: Any) : Int

if (foo(a) < foo(b)) a else b 

所以在这个例子中f == foop == _ < _. scalaz 中肯定有一些高超的聪明才智!我可以看到使用BooleanW我可以写:

p(f(a), f(b)).option(a).getOrElse(b)

但我确信我能够编写一些只引用ab一次的代码。如果这存在,它一定是Function1W和其他东西的某种组合,但 scalaz 对我来说有点神秘!

编辑我想我在这里问的不是“我怎么写这个?” 但是“这样一个函数的正确名称和签名是什么?它与我还不了解的 FP 东西有什么关系,比如 Kleisli、Comonad 等?”

4

6 回答 6

6

以防万一它不在Scalaz中:

def x[T,R](f : T => R)(p : (R,R) => Boolean)(x : T*) =
  x reduceLeft ((l, r) => if(p(f(l),f(r))) r else l)

scala> x(Math.pow(_ : Int,2))(_ < _)(-2, 0, 1)
res0: Int = -2

有一些开销但语法更好的替代方案。

class MappedExpression[T,R](i : (T,T), m : (R,R)) {
  def select(p : (R,R) => Boolean ) = if(p(m._1, m._2)) i._1 else i._2 
}

class Expression[T](i : (T,T)){
  def map[R](f: T => R) = new MappedExpression(i, (f(i._1), f(i._2)))
}

implicit def tupleTo[T](i : (T,T)) = new Expression(i)

scala> ("a", "bc") map (_.length) select (_ < _)
res0: java.lang.String = a
于 2010-02-19T09:27:28.553 回答
5

我不认为箭头或任何其他特殊类型的计算在这里有用。毕竟,您正在使用正常值进行计算,并且通常可以将计算提升为特殊类型的计算(arr用于箭头或return单子)。

然而,一个非常简单的箭头就是arr a b一个函数a -> b。然后,您可以使用箭头将代码拆分为更原始的操作。但是,可能没有理由这样做,它只会使您的代码更加复杂。

例如,您可以取消调用,foo以便与比较分开完成。这是 F# 中箭头的简单定义 - 它声明了***箭头>>>组合器,也arr用于将纯函数转换为箭头:

type Arr<'a, 'b> = Arr of ('a -> 'b)
let arr f = Arr f
let ( *** ) (Arr fa) (Arr fb) = Arr (fun (a, b) -> (fa a, fb b))
let ( >>> ) (Arr fa) (Arr fb) = Arr (fa >> fb)

现在您可以像这样编写代码:

let calcFoo = arr <| fun a -> (a, foo a)    
let compareVals = arr <| fun ((a, fa), (b, fb)) -> if fa < fb then a else b

(calcFoo *** calcFoo) >>> compareVals

组合器***接受两个输入并分别在第一个和第二个参数上运行第一个和第二个指定函数。>>>然后将此箭头与进行比较的箭头组成。

但正如我所说 - 可能根本没有理由写这篇文章。

于 2010-02-19T13:42:03.137 回答
4

这是使用 Scalaz 实现的基于 Arrow 的解决方案。这需要主干。

将箭头抽象与普通的旧函数一起使用并不会带来巨大的好处,但这是在转向 Kleisli 或 Cokleisli 箭头之前学习它们的好方法。

import scalaz._
import Scalaz._

def mod(n: Int)(x: Int) = x % n
def mod10 = mod(10) _
def first[A, B](pair: (A, B)): A = pair._1
def selectBy[A](p: (A, A))(f: (A, A) => Boolean): A = if (f.tupled(p)) p._1 else p._2
def selectByFirst[A, B](f: (A, A) => Boolean)(p: ((A, B), (A, B))): (A, B) =
  selectBy(p)(f comap first) // comap adapts the input to f with function first.

val pair = (7, 16)

// Using the Function1 arrow to apply two functions to a single value, resulting in a Tuple2
((mod10 &&& identity) apply 16) assert_≟ (6, 16)

// Using the Function1 arrow to perform mod10 and identity respectively on the first and second element of a `Tuple2`.
val pairs = ((mod10 &&& identity) product) apply pair
pairs assert_≟ ((7, 7), (6, 16))

// Select the tuple with the smaller value in the first element.
selectByFirst[Int, Int](_ < _)(pairs)._2 assert_≟ 16

// Using the Function1 Arrow Category to compose the calculation of mod10 with the
// selection of desired element.
val calc = ((mod10 &&& identity) product) ⋙ selectByFirst[Int, Int](_ < _)
calc(pair)._2 assert_≟ 16
于 2010-02-20T14:05:11.730 回答
3

好吧,我在Hoogle中查找了类似Thomas Jung 的 答案中的类型签名,并且有on. 这是我搜索的内容:

(a -> b) -> (b -> b -> Bool) -> a -> a -> a

其中(a -> b)是 的等价物foo,(b -> b -> Bool)是 的等价物<。不幸的是, for 的签名on返回了其他东西:

(b -> b -> c) -> (a -> b) -> a -> a -> c

这几乎是一样的,如果你分别cBool和替换a它出现的两个地方。

所以,现在,我怀疑它不存在。我突然想到有一个更通用的类型签名,所以我也尝试了它:

(a -> b) -> ([b] -> b) -> [a] -> a

这个一无所获。

编辑:

现在我认为我根本没有那么远。例如,考虑一下:

Data.List.maximumBy (on compare length) ["abcd", "ab", "abc"]

函数maximumBy签名是(a -> a -> Ordering) -> [a] -> a, 结合 , 与on您最初指定的非常接近,因为它Ordering具有三个值 - 几乎是一个布尔值!:-)

所以,假设你on在 Scala 中写道:

def on[A, B, C](f: ((B, B) => C), g: A => B): (A, A) => C = (a: A, b: A) => f(g(a), g(b))

你可以这样写select

def select[A](p: (A, A) => Boolean)(a: A, b: A) = if (p(a, b)) a else b

并像这样使用它:

select(on((_: Int) < (_: Int), (_: String).length))("a", "ab")

使用柯里化和无点表示法确实更有效。:-) 但是让我们尝试一下隐式:

implicit def toFor[A, B](g: A => B) = new { 
  def For[C](f: (B, B) => C) = (a1: A, a2: A) => f(g(a1), g(a2)) 
}
implicit def toSelect[A](t: (A, A)) = new { 
  def select(p: (A, A) => Boolean) = t match { 
    case (a, b) => if (p(a, b)) a else b 
  } 
}

然后你可以写

("a", "ab") select (((_: String).length) For (_ < _))

很接近。我还没有想出任何办法从那里删除类型限定符,尽管我怀疑这是可能的。我的意思是,不走托马斯回答的方式。但也许这就是方式。事实上,我认为on (_.length) select (_ < _)读起来比map (_.length) select (_ < _).

于 2010-02-19T13:07:20.397 回答
1

这个表达式可以用Factor 编程语言非常优雅地编写- 一种以函数组合为处理方式的语言并且大多数代码都是以无点方式编写的。堆栈语义和行多态性促进了这种编程风格。这就是您的问题的解决方案在 Factor 中的样子:

# We find the longer of two lists here. The expression returns { 4 5 6 7 8 }
{ 1 2 3 } { 4 5 6 7 8 } [ [ length ] bi@ > ] 2keep ?

# We find the shroter of two lists here. The expression returns { 1 2 3 }.
{ 1 2 3 } { 4 5 6 7 8 } [ [ length ] bi@ < ] 2keep ?

我们感兴趣的是组合子2keep。它是一个“保留数据流组合器”,这意味着它在对它们执行给定功能后保留其输入。


让我们尝试将这个解决方案翻译(某种程度)到 Scala。

首先,我们定义了一个保留 arity-2 的组合器。

scala> def keep2[A, B, C](f: (A, B) => C)(a: A, b: B) = (f(a, b), a, b)
keep2: [A, B, C](f: (A, B) => C)(a: A, b: B)(C, A, B)

和一个eagerIf组合器。if作为控制结构不能用于函数组合;因此这个结构。

scala> def eagerIf[A](cond: Boolean, x: A, y: A) = if(cond) x else y
eagerIf: [A](cond: Boolean, x: A, y: A)A

还有,on组合器。由于它与 Scalaz 中同名的方法发生冲突,我将upon改为命名它。

scala> class RichFunction2[A, B, C](f: (A, B) => C) {
     |   def upon[D](g: D => A)(implicit eq: A =:= B) = (x: D, y: D) => f(g(x), g(y))
     | }
defined class RichFunction2

scala> implicit def enrichFunction2[A, B, C](f: (A, B) => C) = new RichFunction2(f)
enrichFunction2: [A, B, C](f: (A, B) => C)RichFunction2[A,B,C]

现在把这台机器投入使用!

scala> def length: List[Int] => Int = _.length
length: List[Int] => Int

scala> def smaller: (Int, Int) => Boolean = _ < _
smaller: (Int, Int) => Boolean

scala> keep2(smaller upon length)(List(1, 2), List(3, 4, 5)) |> Function.tupled(eagerIf)
res139: List[Int] = List(1, 2)

scala> def greater: (Int, Int) => Boolean = _ > _
greater: (Int, Int) => Boolean

scala> keep2(greater upon length)(List(1, 2), List(3, 4, 5)) |> Function.tupled(eagerIf)
res140: List[Int] = List(3, 4, 5)

这种方法在 Scala 中看起来不是特别优雅,但至少它向您展示了另一种做事方式。

于 2012-02-09T18:00:38.310 回答
1

on使用and有一种不错的方法Monad,但不幸的是,Scala 在无点编程方面非常糟糕。你的问题基本上是:“我可以减少这个程序的点数吗?”

想象一下,如果on并且if是不同的咖喱和元组:

def on2[A,B,C](f: A => B)(g: (B, B) => C): ((A, A)) => C = {
  case (a, b) => f.on(g, a, b)
}
def if2[A](b: Boolean): ((A, A)) => A = {
  case (p, q) => if (b) p else q
}

然后你可以使用 reader monad:

on2(f)(_ < _) >>= if2

Haskell 等价物是:

on' (<) f >>= if'
  where on' f g = uncurry $ on f g
        if' x (y,z) = if x then y else z

或者...

flip =<< flip =<< (if' .) . on (<) f
  where if' x y z = if x then y else z
于 2012-02-09T19:06:47.937 回答