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

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

def foo(i: Any) : Int

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

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

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

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

Править: Я предполагаю то, что я спрашиваю, вот не, "как я пишу это?" но, "Каковы корректное имя и подпись для такой функции и это имеет какое-либо отношение к материалу FP, который я еще не понимаю как Kleisli, Comonad и т.д.?"

14
задан oxbow_lakes 19 February 2010 в 09:46
поделиться

4 ответа

На всякий случай, если этого нет в 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
6
ответ дан 1 December 2019 в 13:08
поделиться

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

Вы не получите большого выигрыша от использования абстракции стрелок с простыми старыми функциями, но это хороший способ изучить их, прежде чем переходить к стрелкам 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
4
ответ дан 1 December 2019 в 13:08
поделиться

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

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

3
ответ дан 1 December 2019 в 13:08
поделиться

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

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

Но, как я уже сказал, писать это, скорее всего, вообще не имеет смысла.

5
ответ дан 1 December 2019 в 13:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: