سؤال

أظن أنه يجب أن يكون هناك طريقة وظيفية أفضل للتعبير عن ما يلي:

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 وشيء آخر ولكن 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

نصائح أخرى

لا أعتقد أن الأسهم أو أي نوع خاص آخر من الحساب يمكن أن يكون مفيدًا هنا. في وقت لاحق ، أنت تحسب مع القيم العادية ويمكنك عادة رفع أ نقي حساب ذلك في النوع الخاص من الحساب (باستخدام arr للسهام أو return للموناد).

ومع ذلك ، سهم واحد بسيط جدا هو arr a b هي مجرد وظيفة a -> b. يمكنك بعد ذلك استخدام الأسهم لتقسيم التعليمات البرمجية إلى عمليات أكثر بدائية. ومع ذلك ، ربما لا يوجد سبب للقيام بذلك وهذا يجعل الكود أكثر تعقيدًا.

يمكنك على سبيل المثال رفع المكالمة إلى foo بحيث يتم بشكل منفصل عن المقارنة. فيما يلي تعريف simiple للسهام في 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

ال *** يأخذ Combinator مدخلين ويدير الوظيفة الأولى والثانية في الوسيطة الثانية على التوالي. >>> ثم يتكون هذا السهم مع الس واحد الذي يقوم بالمقارنة.

لكن كما قلت - ربما لا يوجد سبب على الإطلاق لكتابة هذا.

إليك الحل القائم على السهم ، يتم تنفيذه مع Scalaz. هذا يتطلب الجذع.

لا تحصل على فوز كبير من استخدام تجريد السهم مع وظائف قديمة بسيطة ، لكنها طريقة جيدة لتعلمها قبل الانتقال إلى Kleisli أو Cokleisli Arrows.

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 ?

من اهتمامنا هنا هو combinator 2keep. إنه "الحفاظ على حركية بيانات البيانات" ، مما يعني أنه يحتفظ بمدخلاته بعد تنفيذ الوظيفة المحددة عليها.


دعنا نحاول ترجمة (نوع) هذا الحل إلى Scala.

بادئ ذي بدء ، نحن نحدد Arity-2 الحفاظ على combinator.

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 combinator. 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 combinator. نظرًا لأنه يشتبك بطريقة تحمل نفس الاسم من 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, ، لكن سكالا للأسف سيء للغاية في البرمجة الخالية من النقاط. سؤالك هو في الأساس: "هل يمكنني تقليل عدد النقاط في هذا البرنامج؟"

تخيل 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
}

ثم يمكنك استخدام موناد القارئ:

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