On Tue, May 29, 2007 at 07:55:05PM +0900, Jon H. wrote:
p key
end
I’m not necessarily a friend of this technique, but it seems easy on
the minds of some people.
Right, this is exactly what I guessed it was doing (it is the same in
SML/OCaml/F#) but what value was being thrown away in the Ruby program and
where did it come from?
(1…n).inject(x) { |acc, _| yield(acc) }
You can read that as
let l = range 1 n in
List.fold_left (fun acc _ -> f acc) (List.hd l) (List.tl l)
f being the function corresponding to the implicit block called by
yield, and
range : int -> int -> int list.
Actually, in Ruby 1…n is a Range object which responds to the #inject
message
without creating an intermediate array, and the #inject method is
implemented
elsewhere, so the above works a bit like this:
let val_of = function
Some x -> x
| None -> failwith “val_of”
(* In Ruby, Enumerable is a module that can be included (“mixin”) into
- classes that define an #each method. It provides many useful methods
like
-
#map, #find, #find_all, #reject, #max, #min, #sort, #sort_by,
#partition,
-
#each_with_index, #include?.. built atop #each.)
class virtual ['value] enumerable =
object(self: 'b)
method virtual each : ('value -> unit) -> 'b ( returning self allows
to
* chain method calls *)
method inject :
(* Ruby behaves like
* 'acc. ?first_value:'acc -> ('acc -> 'value -> 'acc) -> 'acc =
* but this doesn’t type in ocaml since we’ll pass the first
* value (:'value) to f if no first_value is given, forcing
* 'acc = 'value.
)
?first_value:'value -> ('value -> 'value -> 'value) -> 'value =
fun ?first_value f ->
let acc = ref first_value in
( written this way to mimic Ruby’s implementation,
* which uses an accumulator initialized to Qundef *)
ignore (self#each (fun x ->
match !acc with
None -> acc := Some x
| Some v -> acc := Some (f v x)));
val_of !acc
end
class ['value] range (first : 'value) (last : 'value) succ inclusive =
object(self)
inherit ['value] enumerable
(* this gives us lots of methods implemented using #each *)
val upper_bound : 'value = if inclusive then succ last else last
method each f =
let rec loop v =
if v < upper_bound then (f v; loop (succ v))
in
loop first;
self
end
let _ =
(* r = 1…10 )
let r = new range 1 10 succ true in
( r = 1…10 would be new range 1 10 succ false )
Printf.printf “%d %d\n”
( r.inject{|s,| s * 2} )
(r#inject (fun s _ -> s * 2))
( r.inject(10){|s,| s * 2} *)
(r#inject ~first_value:10 (fun s _ -> s * 2))
Of course, being dynamically typed, Ruby doesn’t have/need parameterized
classes, and instead of using a succ function, Range#each repeatedly
calls the
#succ method of the lower bound.
One last note: while things like #inject are as powerful as their OCaml
counterparts (and more convenient thanks to dynamic typing, but you know
there’s a price for that…), they are often slower than simpler
iteration
methods:
require ‘benchmark’
Benchmark.bm(10) do |bm|
a = (0…10000).to_a # create an array with values 0 to 10000
bm.report("each") do
100.times { sum = 0; a.each{|x| sum += x}; sum }
end
bm.report("inject") { 100.times { a.inject{|s,x| s + x} } }
end
>> user system total real
>> each 0.610000 0.000000 0.610000 ( 0.618960)
>> inject 1.800000 0.020000 1.820000 ( 1.850604)
(yes, it really is that slow)
Also, Ruby doesn’t optimize tail calls, so you cannot write the sort of
recursive functions OCaml excels at. OTOH, the core classes provide much
more
functionality, and more often than not the existing higher-order
functions
will fit the bill.