Функциональный эквивалент if (p (f (a), f (b)) a else b - PullRequest
14 голосов
/ 19 февраля 2010

Я предполагаю, что должен быть лучший функциональный способ выразить следующее:

def foo(i: Any) : Int

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

Так что в этом примере f == foo и p == _ < _. Для этого в скалазе должен быть какой-то мастерский ум! Я вижу, что используя BooleanW я могу написать:

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

Но я был уверен, что смогу написать некоторый код, который ссылается только на a и b один раз . Если он существует, он должен быть в какой-то комбинации Function1W и чем-то еще, кроме скаляза для меня загадка!

РЕДАКТИРОВАТЬ : Я предполагаю, что я спрашиваю здесь не "как мне написать это?" но «Каково правильное имя и подпись для такой функции и имеет ли это какое-либо отношение к вещам FP, которые я пока не понимаю, например, Kleisli, Comonad и т. д.?»

Ответы [ 6 ]

6 голосов
/ 19 февраля 2010

На всякий случай, это не в 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

Альтернатива с некоторыми накладными расходами, но более приятным синтаксисом.

5 голосов
/ 19 февраля 2010

Я не думаю, что здесь могут быть полезны стрелки или любые другие специальные типы вычислений.В конце концов, вы рассчитываете с нормальными значениями и обычно можете поднять вычисление pure в специальный тип вычисления (используя 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

Комбинатор *** принимает два входа и запускает первую и вторую заданную функцию для первого, соответственно второго аргумента.>>> затем соединяет эту стрелку с той, которая выполняет сравнение.

Но, как я уже сказал, для написания этого, вероятно, нет никаких оснований.

4 голосов
/ 20 февраля 2010

Вот решение на основе Arrow, реализованное с помощью Scalaz. Для этого требуется багажник.

Вы не получите огромного выигрыша от использования абстракции стрелок с простыми старыми функциями, но это хороший способ изучить их, прежде чем переходить к стрелкам Клейсли или Коклейсли.

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
3 голосов
/ 19 февраля 2010

Ну, я посмотрел Hoogle на подпись типа, такую ​​как в Томас Юнг ответ , и есть on. Это то, что я искал:

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

Где (a -> b) соответствует foo, (b -> b -> Bool) соответствует <. К сожалению, подпись для on возвращает что-то еще:

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

Это почти то же самое, если заменить c на Bool и a в двух местах, которые появляются соответственно.

Итак, сейчас я подозреваю, что его не существует. Мне пришло в голову, что есть более общая сигнатура типа, поэтому я тоже попробовал:

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

Этот ничего не дал.

EDIT:

Теперь я не думаю, что я был так далеко вообще. Рассмотрим, например, это:

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 (_ < _).

1 голос
/ 09 февраля 2012

Есть хороший способ сделать это с on и Monad, но, к сожалению, Scala очень плохо работает с бессмысленным программированием.Ваш вопрос в основном: «Могу ли я уменьшить количество баллов в этой программе?»

Представьте себе, если on и if были по-разному каррированы и совмещены:можно использовать монаду читателя:

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
1 голос
/ 09 февраля 2012

Это выражение может быть очень элегантно написано на Факторном языке программирования - языке, в котором композиция функций является способом выполнения действий, и большая часть кода написана бессмысленно.Семантика стека и полиморфизм строк облегчают этот стиль программирования.Вот как будет выглядеть решение вашей проблемы в Факторе:

# 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, но, по крайней мере, он показывает вам еще один способ сделать что-то.

...