4 ms·
This looks great in general, but does anyone else find Sorbet's signature formulation offputting? Feels cluttered and not at-a-glance readable. > sig {params(n
by aerovistae 6y ago
This looks great in general, but does anyone else find Sorbet's signature formulation offputting? Feels cluttered and not at-a-glance readable.
> sig {params(name: String, id: String).returns(Integer)}
My ruby isn't quite good enough to parse the language constructs that make up that line, but it's not pretty. I guess....sig is a class method which takes a block....and params is a class method (Added to BasicObject or something, perhaps?) to which you can pass any number of keyword params, and which returns an object that has a return method, to which you can pass any object type. I guess I don't know why you need to call sig at all, as opposed to just params( args ).returns( type ). I also have no idea how the sig call gets associated with the method that follows it.
I just wonder if there was a cleaner way to phrase this that's still syntactically viable.
- weaksauce 6y agoparams in rails is pretty standard for controllers. wrapping it in something like sig makes the namespacing not collide. https://github.com/search?l=Ruby&q=params&type=code https://github.com/search?l=Ruby&q=params&type=code
- jaggederest 6y agoThe block passed to sig is evaluated in a different context, one where the local object has those methods. The methods aren't added globally, which is why you need the first method to switch the context. It's generally a good policy to avoid adding those class methods at the top level, which Sorbet does assiduously. Sig also turns into a no-op if you have runtime verification turned off, which is another good reason not to call params right away, because (in ruby) you can ignore everything in the block if the block is not called, sort of like a debug macro in C, but you can't do that with a method that is called - it must evaluate its parameters.
- aerovistae 6y agoThat's super informative, thank you! Can you also add a word of explanation as to how the `sig` call gets associated with the method that follows it? What ties them together, is there some static parser?
- jaggederest 6y agoSorbet has its own written-in-C++ parser that does the actual parsing. At runtime, the sig call basically sets a flag for the next method that is defined which hooks into it to validate that the parameters passed in are as defined, and that the return value is as expected. I believe they're delving into the dark magic in the interpreter directly, the docs are here: https://sorbet.org/docs/runtime https://sorbet.org/docs/runtime
- ufuk 6y agoFor the runtime, there is not much dark magic. Each type that has an `extend T::Sig` has `method_added` hooks registered, which notifies Sorbet runtime whenever a method is defined on the type. When that `method_added` hook is called, Sorbet runtime uses the sig flag that you mention to associate the `sig` with the method definition that follows it. It is, more or less, an implementation of this idea: https://yehudakatz.com/2009/07/11/python-decorators-in-ruby/ https://yehudakatz.com/2009/07/11/python-decorators-in-ruby/
- jaggederest 6y agoYes indeedy, I'd consider `method_added` to be pretty dark magic though :)
- deleted 6y ago[deleted]
- chc 6y agoThe actual signature checking is complicated, but associating the sig call with the method is pretty simple in Ruby — you just hook `method_added`. (Of course the actual implementation in something like Sorbet is a lot more sophisticated than just `def method_added`, but that's the basics of how you make a method call alter the next method definition.)
- resonious 6y agoI imagine they could (in theory) simplify it to sig(name: String, id: String).returns Integer Losing the block maybe has some undesirable performance implications, but it looks a little nicer without the curly braces.
- rattray 6y agoI think they did this originally but the performance implications were a deal breaker, and had to move to blocks. Iirc, which is questionable.