Passing a method as a parameter in Ruby

21,226

Solution 1

You want a proc object:

gaussian = Proc.new do |dist, *args|
  sigma = args.first || 10.0
  ...
end

def weightedknn(data, vec1, k = 5, weightf = gaussian)
  ...
  weight = weightf.call(dist)
  ...
end

Just note that you can't set a default argument in a block declaration like that. So you need to use a splat and setup the default in the proc code itself.


Or, depending on your scope of all this, it may be easier to pass in a method name instead.

def weightedknn(data, vec1, k = 5, weightf = :gaussian)
  ...
  weight = self.send(weightf)
  ...
end

In this case you are just calling a method that is defined on an object rather than passing in a complete chunk of code. Depending on how you structure this you may need replace self.send with object_that_has_the_these_math_methods.send


Last but not least, you can hang a block off the method.

def weightedknn(data, vec1, k = 5)
  ...
  weight = 
    if block_given?
      yield(dist)
    else
      gaussian.call(dist)
    end
  end
  ...
end

weightedknn(foo, bar) do |dist|
  # square the dist
  dist * dist
end

But it sounds like you would like more reusable chunks of code here.

Solution 2

The comments referring to blocks and Procs are correct in that they are more usual in Ruby. But you can pass a method if you want. You call method to get the method and .call to call it:

def weightedknn( data, vec1, k = 5, weightf = method(:gaussian) )
  ...
  weight = weightf.call( dist )
  ...
end

Solution 3

You can pass a method as parameter with method(:function) way. Below is a very simple example:

def double(a)
  return a * 2 
end
=> nil

def method_with_function_as_param( callback, number) 
  callback.call(number) 
end 
=> nil

method_with_function_as_param( method(:double) , 10 ) 
=> 20

Solution 4

The normal Ruby way to do this is to use a block.

So it would be something like:

def weightedknn( data, vec1, k = 5 )
  foo
  weight = yield( dist )
  foo
end

And used like:

weightenknn( data, vec1 ) { |dist| gaussian( dist ) }

This pattern is used extensively in Ruby.

Solution 5

You can use the & operator on the Method instance of your method to convert the method to a block.

Example:

def foo(arg)
  p arg
end

def bar(&block)
  p 'bar'
  block.call('foo')
end

bar(&method(:foo))

More details at http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html

Share:
21,226
user2018
Author by

user2018

Updated on July 08, 2022

Comments

  • user2018
    user2018 almost 2 years

    I am developing a .NET based web application and I want to connect to a SQL Server, which I created using SQL Server Management Studio 2014. I already followed tutorials from ITWorld and CodeProject with no success. I got an error "Network Path was not found". Although when I test it on Project Properties - > Settings -> Connection Properties, The result is "Test Connection succeeded". Executing Query directly via SQL Management Studio works too.

    How do I fix this problem?

    Web.config

      <connectionStrings>
        <add name="SQL_Server"
          connectionString="Data Source=192.168.60.130;Initial Catalog=TestServer;Persist Security Info=True;User ID=name;Password=password"
          providerName="System.Data.SqlClient" />
      </connectionStrings>
    

    Default.aspx.cs

     protected void Page_Load(object sender, EventArgs e)
     {
                string sqlConn = "SQL_Server";
                ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[sqlConn];
    
                using (SqlConnection conn = new SqlConnection(settings.ConnectionString))
                {
                    conn.Open(); //this is line 31 as shown from the Stack Trace error below
                    SqlCommand command = new SqlCommand("SELECT * from [SQL_DB].[dbo].[users]", conn);
    
                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
    
                            Console.WriteLine(reader);
                        }
                    }
                    Console.ReadLine();
                    Console.Clear();
                }
    }
    

    Stack Trace:

    [Win32Exception (0x80004005): The network path was not found]
    
    [SqlException (0x80131904): Network-related or instance-specific error when connecting to SQL Server. The server was not found or can not be accessed. Verify that the instance name is correct and that SQL Server allows remote connections. (provider: Named Pipes Provider, error: 40 - Could not open connection with SQL Server)]
       System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, DbConnectionPool pool, String accessToken, Boolean applyTransientFaultHandling, SqlAuthenticationProviderManager sqlAuthProviderManager) +1431
       System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) +1085
       System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) +70
       System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) +964
       System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) +109
       System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) +1529
       System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection) +156
       System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection) +258
       System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions) +312
       System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry) +202
       System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry) +413
       System.Data.SqlClient.SqlConnection.Open() +128
       VF.BC._Default.Page_Load(Object sender, EventArgs e) in C:\Users\VM_User\source\repos\SQLProject\Default.aspx.cs:31
       System.Web.UI.Control.OnLoad(EventArgs e) +106
       System.Web.UI.Control.LoadRecursive() +68
       System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3785
    
    • Marco Salerno
      Marco Salerno almost 6 years
      Try to give a normal name to the connection string
    • Risto M
      Risto M almost 6 years
      Check with debugger, what is value of: settings.ConnectionString. Is it the same what you have written in configuration file?
    • user2018
      user2018 almost 6 years
      @MarcoSalerno I changed with a shorter name. Still got same error
    • Marco Salerno
      Marco Salerno almost 6 years
      @User2018 call it "examplename" and try again
    • user2018
      user2018 almost 6 years
      @RistoM Yes it's the same
    • user2018
      user2018 almost 6 years
      @MarcoSalerno Still the same error.
    • user2018
      user2018 almost 6 years
      I added the stack trace error
    • Nick.McDermaid
      Nick.McDermaid almost 6 years
      This is the part of the error that is useful: Network-related or instance-specific error when connecting to SQL Server. The server was not found or can not be accessed. Verify that the instance name is correct and that SQL Server allows remote connections. (provider: Named Pipes Provider, error: 40 - Could not open connection with SQL Server)]
    • Nick.McDermaid
      Nick.McDermaid almost 6 years
      Is the web server, management studio, and the SQL Server all on the same machine? What does I haven't change the Firewall settings because I'm not sure I'm allowed to change them mean? This is a network issue so we need to understand the network situation
    • Nick.McDermaid
      Nick.McDermaid almost 6 years
      First thing to do is change the connection string to force tcp (not names pipes) like this: Data Source=tcp:192.168.60.130;Initial Catalog=TestServer;Persist Security Info=True;User ID=name;Password=password. Then you need to ensure the web server can connect to the SQL Server. This is difficult to troubleshoot in this manner. You need to ask an admin at your work to ensure that the web server can connect to the SQL Server
  • Alex Wayne
    Alex Wayne over 15 years
    When he does weightf = gaussian in the arg list it's actually trying to execute gaussian and assign the result as the default value of weightf. The call doesn't have required args and crashes. So weightf is not even a proc object with a call method just yet.
  • Jimmy Stenke
    Jimmy Stenke over 15 years
    I think that second option is the best option (that is, using Object.send()), the drawback is that you need to use a class for all of it (which is how you should do in OO anyway :)). It is more DRY than passing a block (Proc) all the time, and you could even pass arguments trough the wrapper method.
  • xxjjnn
    xxjjnn over 10 years
    As an addition, if you want to do foo.bar(a,b) with send, it is foo.send(:bar, a, b). The splat (*) operator allows you to do foo.send(:bar, *[a,b]) should you find you want to have an arbitrary lengthed array of arguments - assuming the bar method can soak them up
  • Admin
    Admin about 10 years
    This is interesting. It's worth noting that you call method( :<name> ) just once when converting your method name to a callable symbol. You can store that result in a variable or a parameter and keep passing it to child functions like any other variable from then on...
  • Yahya
    Yahya almost 9 years
    Or maybe, instead of using the method syntax in the argument list, you can use it while invoking the method as follows : weightedknn( data, vec1, k, method( :gaussian) )
  • Developer
    Developer about 8 years
    This is really bad practice, never do it!
  • danuker
    danuker about 8 years
    This method is better than mucking around with a proc or block, since you don't have to handle parameters - it just works with whatever the method wants.
  • medik
    medik over 7 years
    For completion, if you want to pass a method defined somewhere else, do SomewhereElse.method(:method_name). That's pretty cool!
  • V. Déhaye
    V. Déhaye over 7 years
    I faced an issue for a method with a more complicated scope, and finally figured out how to do, hope this can help someone : If your method is for example in another class, you should call the last line of code like method_with_function_as_param(Class.method(:method_name),...‌​) and not method(:Class.method_name)
  • crackpotHouseplant
    crackpotHouseplant almost 7 years
    This may be its own question but, how can I determine if a symbol references a function or something else? I tried :func.class but that's just a symbol
  • jlesse
    jlesse about 6 years
    @Developer why is this considered bad practice?
  • Developer
    Developer almost 6 years
    Among performance reasons, the eval can execute arbitrary code so it is extremely vulnerable to various attacks.
  • user2018
    user2018 almost 6 years
    The SQL Server is running. I have changed the settings in SQL Server Configuration Manager, but I still got the same error. I haven't change the Firewall settings because I'm not sure I'm allowed to change them.
  • Ihtsham Minhas
    Ihtsham Minhas almost 6 years
    There is nothing wrong with your code. It should work. Did you try to access using management studio
  • user2018
    user2018 almost 6 years
    I tried already with SQL management studio and it works too. Test connection via Connection Properties also succeeded.
  • Ihtsham Minhas
    Ihtsham Minhas almost 6 years
    Did you try with port 1433? "Data Source=192.168.60.130, 1433;
  • user2018
    user2018 almost 6 years
    @Nick.McDermaid The Web Server and the SQL Server are on different machines (192.168.60.130 and 192.168.60.131). I'm not sure because this is a work project code. Not a personal project.
  • user2018
    user2018 almost 6 years
    I created the firewall rule and test with port 1433. Now I got an error: The wait operation timed out. I tried also with "MultiSubnetFailover=True".
  • Ihtsham Minhas
    Ihtsham Minhas almost 6 years
    Add following in connection string Network Library=DBMSSOCN;
  • user2018
    user2018 almost 6 years
    Hi @A to Z Tak, Welcome to StackOverflow! This question was already answered a long time ago. So you might continue contributing on other unanswered questions :). Thank you anyway for your answer! And yes, Firewall was the issue.
  • Raja Jee
    Raja Jee almost 6 years
    I miss that answer. Thank you for your comment .
  • rmcsharry
    rmcsharry almost 5 years
    This (ie. doing it wrong and the comment explaining why) actually allowed me to fully understand the accepted answer, so thanks! +1
  • Ludovic Kuty
    Ludovic Kuty over 4 years
    Thanks to your answer, I discovered the method called method. Made my day but I guess that's why I prefer functional languages, no need to make such acrobatics to get what you want. Anyway, I dig ruby