Вопрос

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

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)

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

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

Это было полезно?

Решение

На случай, если это не в Скалаз:

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

Другие советы

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

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

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

Вот решение на основе стрелы, реализованное со 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

Ну, я посмотрел вверх Гугл для подписи типа, подобной той, что в Томас Юнг отвечать, и есть 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

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

РЕДАКТИРОВАТЬ:

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

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

Функция maximumBy подпись есть (a -> a -> Ordering) -> [a] -> a, который в сочетании с on, довольно близко к тому, что вы изначально указали, учитывая, что Ordering Имеет три значения - почти логический! :-)

Итак, скажем, вы написали on В Скала:

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

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

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

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

Тогда вы можете использовать читателя Monad:

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

Эквивалент Хаскелла будет:

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top