contracts.ruby icon indicating copy to clipboard operation
contracts.ruby copied to clipboard

Strange issue with Contracts 0.11.0 interaction with lambdas

Open alexanderdean opened this issue 8 years ago • 8 comments

First off apologies if this has been fixed in a more recent version; we use 0.11.0 at Snowplow.

In Snowplow we have a module function:

      Contract String, String => Func[String, String => String]
      def self.build_fix_filenames(region, collector_format)
        return lambda { |basename, filepath|
        ...

Now the strange thing is that when we call the function from elsewhere in the module like:

  fix_filenames_lambda = build_fix_filenames("foo", "bar")

Then the value of fix_filenames_lambda is set to the Contract - it is of class Contract - rather than set to the lambda (of class Proc).

Very odd! Removing the Contract assignment line fixes it.

alexanderdean avatar Jul 29 '16 12:07 alexanderdean

That's weird. This gist works as expected for me and prints out 2. Does it work for you?

egonSchiele avatar Jul 29 '16 16:07 egonSchiele

Thanks for checking this @egonSchiele ! Super-weird. I took your example and ran it inside a very similar irb environment to our end-app, and it worked. I then tweaked it to make it closer to our failing code:

require "contracts"


module Foo
  module Bar

    include Contracts

    Contract String => Func[Num => Num]
    def self.test(some_arg)
      return lambda { |x|
        return x + 1
      }
    end

    def self.print_incr
      func = test("foo")
      p func[1]
    end

  end
end

Foo.print_incr

That worked too. We run in JRuby - whatever is going wrong I think is very deep in the runtime...

Thanks for looking into it! I think if you can't reproduce we will have to close this?

alexanderdean avatar Jul 29 '16 17:07 alexanderdean

Hi Alexander ,

When you actually call that function, does it work or fail (regardless of its type)?

I'm asking since I am assuming that we are wrapping returned proc into the contract, and contract is still callable. Maybe I'm slightly off here. Need more input. Alexander Dean [email protected] schrieb am Fr., 29. Juli 2016 um 7:54 PM:

Thanks for checking this @egonSchiele https://github.com/egonSchiele ! Super-weird. I took your example and ran it inside a very similar irb environment to our end-app, and it worked. I then tweaked it to make it closer to our failing code:

require "contracts"

module Foo module Bar

include Contracts

Contract String => Func[Num => Num]
def self.test(some_arg)
  return lambda { |x|
    return x + 1
  }
end

def self.print_incr
  func = test("foo")
  p func[1]
end

endend Foo.print_incr

That worked too. We run in JRuby - whatever is going wrong I think is very deep in the runtime...

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/egonSchiele/contracts.ruby/issues/238#issuecomment-236248790, or mute the thread https://github.com/notifications/unsubscribe-auth/AHjleoxzdgtD0psRVcqQmEkXo3Td3GHWks5qaj5sgaJpZM4JYJeZ .

alex-fedorov avatar Jul 29 '16 19:07 alex-fedorov

Hey @alex-fedorov - that's the odd thing, the lambda is not callable. In my downstream code where I try to figure out the lambda's arity, I get this error:

NoMethodError (undefined method `arity' for #<Contract:0x675d9d69>):
    uri:classloader:/gems/sluice-0.4.0/lib/sluice/storage/s3/s3.rb:582:in `rename_file'
    uri:classloader:/gems/sluice-0.4.0/lib/sluice/storage/s3/s3.rb:494:in `block in process_files'
    org/jruby/RubyKernel.java:1290:in `loop'
    uri:classloader:/gems/sluice-0.4.0/lib/sluice/storage/s3/s3.rb:412:in `block in process_files'

alexanderdean avatar Jul 29 '16 19:07 alexanderdean

@alexanderdean As I thought. We are wrapping lambda over here: https://github.com/egonSchiele/contracts.ruby/blob/master/lib/contracts/call_with.rb#L90-L92

Therefore calling that lambda would work just fine, but not #arity. I believe we are violation LSP here, we should be implementing a proper Proc protocol/interface for this wrapper, not just one method #call.

waterlink avatar Jul 29 '16 19:07 waterlink

Ah! I think I understand now. The contract on the lambda-generating function implies some kind of wrapping of the returned lambda, otherwise how can the contract of that lambda be enforced?

alexanderdean avatar Jul 29 '16 19:07 alexanderdean

Exactly!

waterlink avatar Jul 29 '16 19:07 waterlink

The strange thing is that this test case is working:

require "contracts"


module Foo
  module Bar

    include Contracts

    Contract String => Func[Num => Num]
    def self.test(some_arg)
      return lambda { |x|
        return x + 1
      }
    end

    def self.print_arity
      func = test("foo")
      p func.arity
    end

  end
end

Foo.print_arity

Not sure what I am doing wrong...

alexanderdean avatar Jul 29 '16 20:07 alexanderdean