Crystal: Can not call 'send' method

Created on 25 Sep 2013  路  9Comments  路  Source: crystal-lang/crystal

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)
     ^~~~

Most helpful comment

There are many issues to send (and each time I think of it I find more issues):

  1. You have to include a class/methods table in the executable to be able to search it for runtime execution.
  2. Since methods in Crystal are instantiated when you use them, if you don't use a method then it won't appear in the executable:
# 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

  1. What's the type of 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

All 9 comments

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):

  1. You have to include a class/methods table in the executable to be able to search it for runtime execution.
  2. Since methods in Crystal are instantiated when you use them, if you don't use a method then it won't appear in the executable:
# 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

  1. What's the type of 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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oprypin picture oprypin  路  3Comments

lbguilherme picture lbguilherme  路  3Comments

relonger picture relonger  路  3Comments

costajob picture costajob  路  3Comments

pbrusco picture pbrusco  路  3Comments