Equivalente funcional de if (p (f (a), f (b)) a else B
-
21-09-2019 - |
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?"
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