Pergunta

Acho que deve haver uma maneira melhor funcional de expressar o seguinte:

def foo(i: Any) : Int

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

Então neste exemplo f == foo e p == _ < _. É provável que haja alguma inteligência magistral em Scalaz para isso! Eu posso ver isso usando BooleanW Eu consigo escrever:

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

Mas eu tinha certeza de que seria capaz de escrever algum código que apenas referido uma e b uma vez. Se isso existe, deve estar em alguma combinação de Function1W E outra coisa além de Scalaz é um mistério para mim!

EDITAR: Acho que o que estou perguntando aqui não é "Como escrevo isso?" Mas "qual é o nome e a assinatura corretos para essa função e tem algo a ver com coisas de FP que ainda não entendo como kleisli, comonad etc?"

Foi útil?

Solução

Apenas no caso de não estar em 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

Alternativa com alguma sintaxe aérea, mas melhor.

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

Outras dicas

Não acho que setas ou qualquer outro tipo especial de computação possam ser úteis aqui. Afinal, você está calculando com valores normais e geralmente pode levantar um puro cálculo que no tipo especial de computação (usando arr para flechas ou return para mônadas).

No entanto, uma flecha muito simples é arr a b é simplesmente uma função a -> b. Você pode usar setas para dividir seu código em operações mais primitivas. No entanto, provavelmente não há razão para fazer isso e isso apenas torna seu código mais complicado.

Você pode, por exemplo, levantar a chamada para foo para que seja feito separadamente da comparação. Aqui está uma definição de flechas simples em F# - declara *** e >>> combinadores de seta e também arr Para transformar funções puras em flechas:

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)

Agora você pode escrever seu código assim:

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

o *** O Combinator pega duas entradas e executa a primeira e a segunda função especificada no primeiro, respectivamente, segundo argumento. >>> Em seguida, compõe essa seta com a que faz comparação.

Mas como eu disse - provavelmente não há razão para escrever isso.

Aqui está a solução baseada em seta, implementada com o Scalaz. Isso requer tronco.

Você não obtém uma grande vitória ao usar a abstração de seta com funções antigas simples, mas é uma boa maneira de aprendê -las antes de se mudar para as setas Kleisli ou 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

Bem, eu olhei para cima Hoogle para uma assinatura de tipo como a em Thomas Jung's responda, e aqui está on. Isso é o que eu procurei:

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

Onde (a -> b) é o equivalente a foo, (b -> b -> Bool) é o equivalente a <. Infelizmente, a assinatura para on retorna outra coisa:

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

Isso é quase o mesmo, se você substituir c com Bool e a Nos dois lugares, aparece, respectivamente.

Então, agora, suspeito que não existe. Ocorreu -me que há uma assinatura de tipo mais geral, então eu tentei também:

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

Este não produziu nada.

EDITAR:

Agora acho que não estava tão longe. Considere, por exemplo, isso:

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

A função maximumBy assinatura é (a -> a -> Ordering) -> [a] -> a, que, combinado com on, está bem perto do que você especificou originalmente, dado que Ordering IS tem três valores - quase um booleano! :-)

Então, diga que você escreveu on em 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))

Você poderia escrever select assim:

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

E use assim:

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

O que realmente funciona melhor com a notação curry e sem pontos. :-) Mas vamos tentar com implícitos:

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 
  } 
}

Então você pode escrever

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

Muito perto. Eu não imaginei nenhuma maneira de remover o qualificador de tipo a partir daí, embora suspeite que seja possível. Quero dizer, sem seguir o caminho da resposta de Thomas. Mas talvez isso é o caminho. Na verdade, eu acho on (_.length) select (_ < _) lê melhor do que map (_.length) select (_ < _).

Esta expressão pode ser escrita com muita elegância em Linguagem de programação fatorial - uma linguagem em que a composição da função é a maneira de fazer as coisas, e a maioria dos códigos é escrita de maneira livre de pontos. A semântica da pilha e o polimorfismo de linha facilitam esse estilo de programação. É assim que a solução para o seu problema será no fator:

# 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 ?

Do nosso interesse aqui é o combinador 2keep. É um "preservação do combinador de fluxo de dados", o que significa que ele mantém suas entradas após a execução da função.


Vamos tentar traduzir (mais ou menos) esta solução para Scala.

Primeiro de tudo, definimos um combinador de preservação da 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)

E um eagerIf Combinador. if Ser uma estrutura de controle não pode ser usado na composição da função; daí este construto.

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

Também o on Combinador. Desde que se chocam com um método com o mesmo nome de Scalaz, eu vou nomear upon em vez de.

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]

E agora coloque esta maquinaria para usar!

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)

Essa abordagem não parece particularmente elegante em Scala, mas pelo menos mostra mais uma maneira de fazer as coisas.

Há uma maneira agradável de fazer isso com on e Monad, mas infelizmente é muito ruim na programação sem ponto. Sua pergunta é basicamente: "Posso reduzir o número de pontos neste programa?"

Imagine se on e if foram de maneira diferente e parecida:

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
}

Então você pode usar a Mônada do Leitor:

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

O equivalente Haskell seria:

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

Ou...

flip =<< flip =<< (if' .) . on (<) f
  where if' x y z = if x then y else z
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top