class Test
def say
puts "Hello World!"
end
end
test = Test.new
test.send(:say)
$ bin/crystal hello.cr
Error in crystal/hello.cr:10: undefined method 'send' for Test
test.send(:say)
^~~~
send, eval and other dynamic functionalities are not supported in Cyrstal. Maybe they will work in the future if we add run-time refleciton.
Hmm... The lack of send is a hard pill to swallow. Ruby allows for some very versatile code via the combination of method-missing and send.
Yes, but at what price? Also, Ruby is an interpreted language and Crystal is compiled. Crystal do not aim to replace and/or being compatible with Ruby. There are many other compiled languages out there not supporting "send" like capabilities.
Don't get me wrong. Ruby is really great and send is really powerful, I agree with that. But we're trying to make a more efficient language that makes the best use of each cpu cycle ;-)
I am glad that Crystal isn't aiming to be _yet another Ruby_. But that doesn't discount the fact that not having send means some really useful designs that are possible in Ruby won't be possible in Crystal.
If we might discuss the technical details of the issue, I wonder, what is it exactly that prevents send from being possible? Is it just a problem with type inference, or is there more to it then that?
There are many issues to send (and each time I think of it I find more issues):
# Never instantiated, so it doesn't exist in the executable
de foo(x)
x + 2
end
sym = :foo
send sym, 1 # will never work
One way to "solve" this is to somehow tell the compiler to instantiate some methods for some types... but it's an ugly hack :-P
send? You have to make it be Object and then cast it to another type. We don't have casting right now (except pointer casting, which is needed for low level code). Casting is ugly and unsafe.In conclusion, it raises a lot of problems. But tell me where you want to use a send method and I'll try to give you a solution built only with compile-time execution facilities.
Some days ago we were discussing with @waj how we would do, for example, routes in Rails: match "foo/bar" => "foo#bar". Do you really need that hash value to be a String? In Crystal you could do it like this: "foo/bar" => ->Foo.bar(self) (a function pointer). Or you could generate that function pointer from the string at compile time.
Finally, most people in Ruby-land recommend you not to use send or eval because it's unsafe (you can accidentally enable arbitrary code execution). Why would you want that "feature"? :-P
One of my favorite bits of Ruby is a "Functor".
class Functor < BasicObject
def initialize(&function)
@function = function
end
def method_missing(op, *a, &b)
@function.call(op, *a, &b)
end
end
What this allows, for example, are things like this:
class String
def file
Functor.new do |op, *a|
File.send(op, self, *a)
end
end
end
"~/somefile".file.mtime
Another example,
module Enumerable
def every
Functor.new do |op, *a|
map{ |e| e.send(op, *a) }
end
end
end
[1, 2, 3].every.succ => [2, 3, 4]
Generally speaking the Functor allows one to control execution conditionally based on the operation applied. In practice it is generally a means of quick and easy delegation. In a sense a functor is to a full-blown delegator as a lambda is to a full-blown method.
require "benchmark"
class Functor < BasicObject
def initialize(&function)
@function = function
end
def method_missing(op, *a, &b)
@function.call(op, *a, &b)
end
end
module Enumerable
def every
Functor.new do |op, *a|
map{ |e| e.send(op, *a) }
end
end
end
n = 500000
Benchmark.bm(10) do |x|
x.report("functor") { n.times { [1, 2, 3].every.succ } }
x.report("map") { n.times { [1, 2, 3].map &:succ } }
end
user system total real
functor 0.990000 0.010000 1.000000 ( 0.994023)
map 0.230000 0.000000 0.230000 ( 0.225312)
Now to support every.succ you had to write a Functor class and define an every method, and the code you have to write has exaclty the same length as an ordinary map. In addition, you also get slower code.
Is that what you really want? :-P
No. I've actually been looking for ways to optimize the code. Its the fluent notation that is the desirable part. I even submitted a feature request for Ruby once to support a kind of literal functor in order to avoid the intermediate object.
Truth is, Crystal is far from having a stable set of features that we want to implement. Right now things like "send" are quite out of scope and also seems to go a little bit against the goals of the language (or at least I cannot imagine a feasible and performant implementation).
Maybe some other features like more powerful macros can instead provide another approach to obtain the same functionality without the performance penalty. I don't know, just thinking....
I'll close this issue now because at the moment this is just by design. Please, feel free to join the group (https://groups.google.com/forum/?fromgroups#!forum/crystal-lang) and discuss how we could implement new ideas in the future.
Most helpful comment
There are many issues to
send(and each time I think of it I find more issues):One way to "solve" this is to somehow tell the compiler to instantiate some methods for some types... but it's an ugly hack :-P
send? You have to make it be Object and then cast it to another type. We don't have casting right now (except pointer casting, which is needed for low level code). Casting is ugly and unsafe.In conclusion, it raises a lot of problems. But tell me where you want to use a
sendmethod and I'll try to give you a solution built only with compile-time execution facilities.Some days ago we were discussing with @waj how we would do, for example, routes in Rails: match "foo/bar" => "foo#bar". Do you really need that hash value to be a String? In Crystal you could do it like this: "foo/bar" => ->Foo.bar(self) (a function pointer). Or you could generate that function pointer from the string at compile time.
Finally, most people in Ruby-land recommend you not to use send or eval because it's unsafe (you can accidentally enable arbitrary code execution). Why would you want that "feature"? :-P