2010-11-10 34 views
10

Tôi cố gắng để làm như sau trong ít mã càng tốt và như chức năng càng tốt:Scala thể dục dụng cụ lập trình chức năng

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double 

Rõ ràng các công việc sau:

= (floor -> cap) match { 
    case (None, None)  => amt 
    case (Some(f), None) => f max amt 
    case (None, Some(c))  => c min amt 
    case (Some(f), Some(c)) => (f max amt) min c 
    } 

Tôi đã thực sự hy vọng cho một cái gì đó thanh lịch hơn và sẽ chấp nhận việc sử dụng thư viện Scalaz! Bạn có thể giả định rằng sau đây là đúng:

floor.forall(f => cap.forall(_ > f)) 

Nếu có ai quan tâm, đây là một số mã kiểm tra:

object Comparisons { 
    sealed trait Cf { 
    def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double 
    } 

    def main(args: Array[String]) { 
    val cf : Cf = //TODO - your impl here! 
    def runtest(floor: Option[Double], cap: Option[Double], amt: Double, exp : Double) : Unit = { 
     val ans = cf.restrict(floor, cap, amt) 
     println("floor=%s, cap=%s, amt=%s === %s (%s) : %s".format(floor, cap, amt, ans, exp, if (ans == exp) "PASSED" else "FAILED")) 
    } 
    runtest(Some(3), Some(5), 2, 3) 
    runtest(Some(3), Some(5), 3, 3) 
    runtest(Some(3), Some(5), 4, 4) 
    runtest(Some(3), Some(5), 5, 5) 
    runtest(Some(3), Some(5), 6, 5) 

    runtest(Some(3), None, 2, 3) 
    runtest(Some(3), None, 3, 3) 
    runtest(Some(3), None, 4, 4) 
    runtest(Some(3), None, 5, 5) 
    runtest(Some(3), None, 6, 6) 

    runtest(None, Some(5), 2, 2) 
    runtest(None, Some(5), 3, 3) 
    runtest(None, Some(5), 4, 4) 
    runtest(None, Some(5), 5, 5) 
    runtest(None, Some(5), 6, 5) 

    runtest(None, None, 2, 2) 
    runtest(None, None, 3, 3) 
    runtest(None, None, 4, 4) 
    runtest(None, None, 5, 5) 
    runtest(None, None, 6, 6) 
    } 
} 
+1

nào "hạn chế" chức năng giả sử để làm gì? – OscarRyz

+0

Xin lỗi - nó phải trả về một Double dựa trên 'amt' được cung cấp nhưng được giới hạn hoặc sàn bởi các tham số tùy chọn. Đó là, nếu cung cấp 10 nhưng một nắp của một số (8), phương pháp nên trả lại 8 –

Trả lời

16

Chỉnh sửa 2:

Trong khi suy nghĩ về phương pháp cataX, tôi đã tìm ra rằng cataX là không có gì khác hơn là một lần đồng bằng và đơn giản. Bằng cách đó, chúng ta có thể nhận được một giải pháp Scala thuần túy mà không cần bất kỳ thư viện bổ sung nào.

Vì vậy, ở đây nó là:

((amt /: floor)(_ max _) /: cap)(_ min _) 

mà là giống như

cap.foldLeft(floor.foldLeft(amt)(_ max _))(_ min _) 

(không rằng đây hẳn là dễ hiểu).

Tôi nghĩ bạn không thể có nó ngắn hơn thế.


Dù tốt hay xấu, chúng ta cũng có thể giải quyết nó bằng cách sử scalaz:

floor.map(amt max).getOrElse(amt) |> (m => cap.map(m min).getOrElse(m)) 

hoặc thậm chí:

floor.cata(amt max, amt) |> (m => cap.cata(m min, m)) 

Là một lập trình viên Scala 'bình thường', người ta có thể không biết về các toán tử và phương thức Scalaz đặc biệt được sử dụng (|>Option.cata). Chúng hoạt động như sau:

value |> function chuyển thành function(value) và do đó amt |> (m => v fun m) bằng v fun amt.

opt.cata(fun, v) dịch để

opt match { 
    case Some(value) => fun(value) 
    case None => v 
} 

hoặc opt.map(fun).getOrElse(v).

Xem định nghĩa Scalaz cho cata|>.

Một giải pháp đối xứng hơn sẽ là:

amt |> (m => floor.cata(m max, m)) |> (m => cap.cata(m min, m)) 

Edit: Xin lỗi, nó nhận được lạ bây giờ, nhưng tôi muốn có một phiên bản điểm miễn phí là tốt. cataX mới được xử lý. Tham số đầu tiên có một hàm nhị phân; thứ hai là một giá trị.

class CataOption[T](o: Option[T]) { 
    def cataX(fun: ((T, T) => T))(v: T) = o.cata(m => fun(m, v), v) 
} 
implicit def option2CataOption[T](o: Option[T]) = new CataOption[T](o) 

Nếu o trận Some chúng tôi trả lại chức năng với giá trị của o và tham số thứ hai áp dụng, nếu o trận None chúng tôi chỉ trả lại tham số thứ hai.

Và ở đây chúng tôi đi:

amt |> floor.cataX(_ max _) |> cap.cataX(_ min _) 

Có lẽ họ đã có điều này trong Scalaz ...?

+2

Bạn xứng đáng với tất cả các điểm - câu trả lời ở giữa đầy khiếp sợ. Có thể là một ý tưởng để làm sáng tỏ những gì đang xảy ra –

+1

Thư viện làm sáng tỏ khả năng được đánh giá cao được đánh giá cao vẫn còn rất nhiều công việc đang được tiến hành. Như là một bản xem trước lén mặc dù ... Tôi tin rằng đối với hầu hết các phần nó dựa trên một bằng chứng toán học ít được biết đến chứng minh một homomorphism giữa mã nguồn và mô tả rõ ràng :) –

+0

Điều là, một khi bạn hiểu chữ ký loại '|> ', rõ ràng, bẩm sinh? –

4

Làm thế nào về điều này?

//WRONG 
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
    (floor.getOrElse(amt) max amt) min cap.getOrElse(amt) 

[Chỉnh sửa]

Second try:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
    floor.map(f => f max _).getOrElse(identity[Double] _)(
    cap.map(c => c min _).getOrElse(identity[Double] _)(amt)) 

Trông một chút quá "lispy" cho hương vị của tôi, nhưng qua các bài kiểm tra :-)

[2nd Chỉnh sửa]

Phiên bản đầu tiên cũng có thể được "sửa chữa":

def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double = 
    (floor.getOrElse(-Double.MaxValue) max amt) min cap.getOrElse(Double.MaxValue) 
+0

Đây là bản chất chính xác cùng một câu trả lời được đưa ra bởi Dave Griffith - nó không hoạt động –

+0

"2nd Edit" phiên bản hoạt động tuyệt vời –

4

Không đẹp hơn, không ngắn hơn nhiều và chắc chắn không nhanh hơn! Nhưng đó là composable hơn chung ngày càng có nhiều "chức năng":

EDIT: thực hiện các mã đầy đủ generic :)

def optWith[T](a: Option[T], b: T)(op:(T,T)=>T) = 
    a map (op(b,_)) getOrElse b 

def optMin[T:Numeric](a: Option[T]) = 
    (b:T) => optWith(a, b)(implicitly[Numeric[T]].min) 

def optMax[T:Numeric](a: Option[T]) = 
    (b:T) => optWith(a, b)(implicitly[Numeric[T]].max) 

def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT]) 
    (implicit ev:Numeric[T], fv:FT=>T, cv:CT=>T) = 
    optMin(ceil map cv) compose optMax(floor map fv) apply(x) 

UPDATE 2: Ngoài ra còn có phiên bản này, tận dụng tốt hơn Numeric

def optWith[T](a: Option[T])(op:(T,T)=>T) = 
    (b:T) => a map (op(b,_)) getOrElse b 

def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT]) 
    (implicit n:Numeric[T], fv:FT=>T, cv:CT=>T) = 
    optWith(ceil map cv)(n.min) compose optWith(floor map fv)(n.max) apply(x) 

tôi hy vọng bạn thích chữ ký kiểu :)

CẬP NHẬT 3: Đây là một loại hoạt động tương tự với vọt

def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) = 
    (b:T) => a map (op(b,_)) getOrElse b 

def restrict[T:Numeric, FT <% T, CT <% T] 
(floor:Option[FT], ceil:Option[CT], amt:T) = { 
    val n = implicitly[Numeric[T]]; import n._ 
    optWith(min)(ceil) compose 
    optWith(max)(floor) apply(amt) 
} 

Nếu không có gì khác ... điều này cho thấy khá rõ ràng lý do tại sao các thông số nhập khẩu sẽ là một điều tốt (tm). Hãy tưởng tượng nếu những điều sau đây là mã hợp lệ:

def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) = 
    (b:T) => a map (op(b,_)) getOrElse b 

def restrict[import T:Numeric,FT <% T,CT <% T] 
(floor:Option[FT], ceil:Option[CT], amt:T) = { 
    optWith(min)(ceil) compose 
    optWith(max)(floor) apply(amt) 
} 

CẬP NHẬT 4: Biến giải pháp lộn ngược ở đây. Điều này cung cấp một số khả năng thú vị hơn cho phần mở rộng trong tương lai.

implicit def optRhs[T:Ordering](lhs:T) = new Object { 
    val ord = implicitly[Ordering[T]]; import ord._ 

    private def opt(op: (T,T)=>T)(rhs:Option[T]) = 
    rhs map (op(lhs,_)) getOrElse lhs 

    def max = opt(ord.max) _ 
    def min = opt(ord.min) _ 
} 

def restrict[T : Ordering](floor:Option[T], cap:Option[T], amt:T) = 
    amt min cap max floor 

Với bất kỳ may mắn nào, tôi sẽ truyền cảm hứng cho người khác xây dựng giải pháp tốt hơn. Đó là cách những điều này thường làm việc ra ...

+0

Vâng, nó hoạt động nhưng tôi muốn các dòng mã số ít nhất –

+0

Giải pháp tuyệt vời trong bản cập nhật 4. Khá vô lý có bao nhiêu cách khác nhau để giải quyết vấn đề này có thể bằng một ngôn ngữ. Câu hỏi này sẽ không được hỏi về Java! –

+0

True ... Trong Java bạn chỉ cần thực hiện kiểm tra null rõ ràng, có thể rủi ro NPEs, và phải quá tải N lần cho mỗi nguyên thủy bạn có thể sẽ làm việc với.Thậm chí sau đó, nó vẫn sẽ không được mã hợp lệ cho bất kỳ loại lệnh khác mà bạn có thể quan tâm ... –

5

Tôi sẽ bắt đầu với điều này:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = { 
    val flooring = floor.map(f => (_: Double) max f).getOrElse(identity[Double] _)  
    val capping = cap.map(f => (_: Double) min f).getOrElse(identity[Double] _)   
    (flooring andThen capping)(amt)              
}                      

Nhưng tôi có cảm giác tôi đang thiếu một số cơ hội ở đây, vì vậy tôi có thể không được hoàn thành.

15

Không hẳn như ngắn gọn như phiên bản scalaz, nhưng mặt khác, không phụ thuộc,

List(floor.getOrElse(Double.NegativeInfinity), cap.getOrElse(Double.PositiveInfinity), amt).sorted.apply(1) 
+0

Tôi thích cái này. Tất cả các giải pháp khác cho đến nay buộc bạn phải hiểu những gì hai xét nghiệm làm, nơi mà giải pháp này chọn giữa giữa sàn và nắp. – huynhjl

2

Đây không phải là thực sự dễ dàng hơn trong Scalaz nhiều so với Scala thường xuyên:

def restrict(floor: Option[Double], cap: Option[Double], amt: Double) = 
    floor.map(amt max).orElse(Some(amt)).map(x => cap.map(x min).getOrElse(x)).get 

(Thêm _ sau maxmin nếu nó làm cho bạn cảm thấy tốt hơn để xem nơi tham số đi.)

Scalaz dễ dàng hơn một chút quảng cáo, mặc dù, một khi bạn hiểu những gì các nhà khai thác làm.

+0

Hóa ra giải pháp tốt nhất (ít nhất theo ý kiến ​​của tôi) là giải pháp chỉ sử dụng '/:' từ Debilski –

5

Thay vì sử dụng ngắn gọn, điều này cho thấy bố cục trở nên dễ dàng hơn bao nhiêu nếu bạn bật các hàm capfloor thành các hàm.

scala> val min = (scala.math.min _).curried           
min: (Int) => (Int) => Int = <function1> 

scala> val max = (scala.math.max _).curried           
max: (Int) => (Int) => Int = <function1> 

scala> def orIdentity[A](a: Option[A])(f: A => A => A): (A => A) = a ∘ f | identity 
orIdentity: [A](a: Option[A])(f: (A) => (A) => A)(A) => A 

scala> val cap = 5.some; val floor = 1.some           
cap: Option[Int] = Some(5) 
floor: Option[Int] = Some(1) 

scala> val ffloor = orIdentity(floor)(max)           
ffloor: (Int) => Int = <function1> 

scala> val fcap = orIdentity(cap)(min)            
fcap: (Int) => Int = <function1> 

scala> val capAndFloor = fcap ∘ ffloor            
capAndFloor: (Int) => Int = <function1>  

scala> (0 to 8).toSeq ∘ (capAndFloor)  
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5) 

Từ scalaz, tôi sử dụng MA#∘, bản đồ functor, cả hai như là một cách để sử dụng Option.mapFunction1.andThen; và OptionW#| là bí danh cho Option.getOrElse.

CẬP NHẬT

Đây là những gì tôi đang tìm kiếm:

scala> import scalaz._; import Scalaz._ 
import scalaz._ 
import Scalaz._ 

scala> val min = (scala.math.min _).curried           
min: (Int) => (Int) => Int = <function1> 

scala> val max = (scala.math.max _).curried           
max: (Int) => (Int) => Int = <function1> 

scala> def foldMapEndo[F[_]: Foldable, A](fa: F[A], f: A => A => A): Endo[A] = 
    | fa.foldMap[Endo[A]](a => f(a))          
foldMapEndo: [F[_],A](fa: F[A],f: (A) => (A) => A)(implicit evidence$1: scalaz.Foldable[F])scalaz.Endo[A] 

scala> val cap = 5.some; val floor = 1.some          
cap: Option[Int] = Some(5) 
floor: Option[Int] = Some(1)  

scala> val capAndFloor = List(foldMapEndo(floor, max), foldMapEndo(cap, min)) ∑ 
capAndFloor: scalaz.Endo[Int] = [email protected] 

scala>(0 to 10).toSeq.map(capAndFloor)            
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5) 

scalaz.Endo[A] là một wrapper quanh A => A, có chuyển đổi tiềm ẩn trong cả hai hướng. Có một phiên bản của Monoid được xác định cho Endo[A], Monoid#plus chuỗi các chức năng và Monoid#zero trả về hàm nhận dạng. Nếu chúng tôi có một số List của Endo[A], chúng tôi có thể tổng hợp danh sách và dẫn đến một giá trị duy nhất, có thể được sử dụng làm A => A.

MA#foldMap ánh xạ hàm đã cho trên một loại dữ liệu Foldable và giảm xuống một giá trị đơn lẻ với Monoid. foldMapEndo là một tiện lợi trên đầu trang này. Sự trừu tượng này cho phép bạn dễ dàng thay đổi từ việc chứng minh nắp và sàn thành các loại có thể gập lại, chẳng hạn như List.

val capAndFloor = Seq(foldMapEndo(List(1, 2, max), foldMapEndo(cap, min)).collapsee 
capAndFloor: scalaz.Endo[Int] = [email protected] 

refactoring khác có thể dẫn đến:

val capAndFloor = Seq((cap, min), (floor, max)).foldMap { case (a, f) => foldMapEndo(a, f) } 
capAndFloor: scalaz.Endo[Int] = [email protected] 
+1

Tuyệt vời như mọi khi! Cách tiếp cận này rõ ràng hữu ích nếu tôi muốn soạn cap/floor với các chức năng khác, hoặc ở những nơi khác trong codebase - nó thực sự ở một nơi nhỏ –

+0

Cách tiếp cận này khá giống với câu trả lời của Kevin và Daniel. Thật thú vị khi thấy mọi thứ trở nên dễ dàng hơn thế nào với những chức năng bị quấy rối. Tôi vẫn tự hỏi liệu có một sự trừu tượng ẩn đằng sau 'orIdentity'. – retronym

+0

Tôi lấy nó mà bạn có thể đã sử dụng '| + |' thay vì cách tiếp cận 'List.sum'? –

1

Đây là một cách để sửa chữa Landei's first answer

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = { 
    val chopBottom = (floor.getOrElse(amt) max amt) 
    chopBottom min cap.getOrElse(chopBottom) 
} 
2

Tôi thấy rằng khi một câu hỏi yêu cầu để sử dụng một Option để chỉ ra một tham số tùy chọn , thường có cách tự nhiên hơn để thể hiện tham số bị thiếu. Vì vậy, tôi sẽ thay đổi giao diện một chút ở đây và sử dụng các đối số mặc định để xác định hàm và các tham số có tên để gọi hàm.

def restrict(amt:Double, 
      floor:Double = Double.NegativeInfinity, 
      cap:Double=Double.PositiveInfinity):Double = 
    (amt min cap) max floor 

Sau đó, bạn có thể gọi:

restrict(6) 
restrict(6, floor = 7) 
restrict(6, cap = 5) 

(Another example of the same principle.)

+0

Tôi không chắc mình đồng ý với bạn ở đây; làm thế nào là sử dụng một "thủ thuật" tự nhiên hơn so với thể hiện "sự thật" của tình hình? Điều đó nói rằng, cú pháp trang web gọi là tốt đẹp! Tôi sẽ thêm một câu trả lời dựa trên giải pháp của bạn mà tôi nghĩ là thích hợp hơn –

+0

@oxbow_lakes, Làm thế nào là sử dụng vô cùng có nghĩa là "không có giới hạn" một thủ thuật? Làm thế nào mà không phải là sự thật? (Tôi nghĩ rằng nếu bạn định phản đối, nó sẽ phản đối sự thay đổi giao diện, không phải là việc sử dụng vô hạn như giới hạn.) –

+0

Tôi thấy quan điểm của bạn; nhưng có một sự khác biệt (ít nhất là trong tâm trí của tôi) giữa "không giới hạn" và "giới hạn vô cùng". Hãy đối mặt với nó, "Double.PositiveInfinity" là một hack! –

2

Đây là based on Ken Bloom's answer:

sealed trait Constrainer { def constrain(d : Double) : Double } 

trait Cap extends Constrainer 
trait Floor extends Constrainer 
case object NoCap extends Cap { def constrain(d : Double) = d } 
case object NoFloor extends Floor { def constrain(d : Double) = d } 
implicit def d2cap(d : Double) = new Cap { def constrain(amt : Double) = d min amt } 
implicit def d2floor(d : Double) = new Floor { def constrain(amt : Double) = d max amt } 

def restrict(amt : Double, cap : Cap = NoCap, floor: Floor = NoFloor) : Double = { 
    cap.constrain(floor.constrain(amt)) 
    //or (cap.constrain andThen floor.constrain) amt 
} 

Nó kết thúc với viết code như thế này:

restrict(amt, cap = 5D) 
restrict(amt, floor = 0D) 

Tôi nghĩ điều đó khá tuyệt vời và không gặp vấn đề với giải pháp của Ken (theo ý kiến ​​của tôi), đó là một số hack!

+0

Có. Tôi nghĩ rằng hệ thống phân cấp và các đối tượng singleton bị niêm phong có phần bị bỏ qua. Chúng thường không có ý nghĩa nhiều đối với các thư viện, nhưng chúng thường hiển thị độc đáo trên các quy tắc kinh doanh. –

0

tôi thêm một câu trả lời đã được lấy cảm hứng từ cả retronymDebilski - về cơ bản nó số tiền để chuyển đổi nắp và sàn để chức năng (Double => Double, nếu họ có mặt) và sau đó gấp chức năng nhận diện qua chúng với thành phần:

def restrict(floor: Option[Double], cap: Option[Double], amt: Double) = { 
    (identity[Double] _ /: List(floor.map(f => (_: Double) max f), cap.map(c => (_: Double) min c)).flatten){ _ andThen _ }(amt) 
} 
0

giải pháp đơn giản với đồng bằng Scala và lambda vô danh, mà không cần bất kỳ ánh xạ, nếp gấp, đôi {Min/Max} Value, và vân vân.

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
    ((x:Double) => x min cap.getOrElse(x))(amt max floor.getOrElse(amt)) 
0

Tôi thích giải pháp ban đầu với trường hợp phù hợp nhất - bên cạnh thực tế, rằng tôi không hiểu rằng amt có nghĩa là amount (ở Đức, 'amt' có nghĩa là 'office') và tôi chỉ biết cap đầu tôi ...

Bây giờ đây là một giải pháp thực sự tẻ nhạt, sử dụng phương pháp nội:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = { 
    def restrict (floor: Double, cap: Double, amt: Double) = 
    (floor max amt) min cap 
    var f = floor.getOrElse (amt)    
    val c = cap.getOrElse (amt) 
    restrict (f, c, amt) 
} 
Các vấn đề liên quan