Функциональный эквивалент if (p (f (a), f (b)) a else b

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

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 и т.д.?"

Ответы

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

Здесь решение на основе 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

Ответ 4

Ну, я посмотрел Hoogle для сигнатуры типа, подобной той, что была в Thomas Jung , и есть 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 signature (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")

Что действительно работает лучше с каррированием и безточечной нотации.:-) Но пусть попробует это с implicits:

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

Ответ 5

Это выражение может быть написано очень элегантно в языке факторов программирования - язык, где композиция функций - это способ делать вещи, а большинство кода написанный беспроблемным образом. Семантика стека и полиморфизм строк облегчают этот стиль программирования. Это то, что решение вашей проблемы будет выглядеть в 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 ] [email protected] > ] 2keep ?

# We find the shroter of two lists here. The expression returns { 1 2 3 }.
{ 1 2 3 } { 4 5 6 7 8 } [ [ length ] [email protected] < ] 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, но по крайней мере он показывает вам еще один способ сделать что-то.

Ответ 6

Здесь есть хороший способ сделать это с помощью on и 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
}

Затем вы можете использовать монаду читателя:

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