[英]Functional equivalent of 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 == _ < _
。 對於scalaz來說,必然會有一些熟練的聰明才智! 我可以看到使用BooleanW
我可以寫:
p(f(a), f(b)).option(a).getOrElse(b)
但我確信我能夠編寫一些只引用a和b一次的代碼 。 如果存在,它必須是Function1W
和其他東西的某種組合,但scalaz對我來說有點神秘!
編輯 : 我想我在這里問的不是“我怎么寫這個?” 但是“這個功能的正確名稱和簽名是什么?它與FP的東西有什么關系,我還不知道像Kleisli,Comonad等?”
萬一它不在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
我不認為Arrows或任何其他特殊類型的計算在這里有用。 畢竟,你使用正常值進行計算,你通常可以將純計算提升到特殊類型的計算中(使用arr
表示箭頭或return
monad)。
然而,一個非常簡單的箭頭是arr ab
只是一個函數a -> b
。 然后,您可以使用箭頭將代碼拆分為更原始的操作。 但是,可能沒有理由這樣做,它只會使您的代碼更復雜。
例如,您可以將調用解除為foo
以便與比較分開完成。 這是F#中箭的simiple定義-它聲明***
和>>>
箭頭組合程序,也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實現。 這需要后備箱。
使用帶有普通舊函數的箭頭抽象並沒有獲得巨大的勝利,但在移動到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
好了,我抬頭一看Hoogle對於像一個在類型簽名托馬斯·榮格的 回答 ,並沒有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")
使用currying和dot-free表示法真的更好。 :-)但是讓我們試着用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 (_ < _)
. on (_.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
是不同的咖喱和tupled:
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
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.