= Intro = HasType provides a means of specifying type signatures (http://en.wikipedia.org/wiki/Type_signature) for Ruby methods. It's flexible enough to work well with the dynamic properties of Ruby, but helps provide some rigidity to "tighten up" the code. The type signatures can be checked for correctness at runtime, raising exceptions with helpful error messages for locating type mismatches. Since type checking incurs performance overhead, when performance matters, HasType can be "turned off". = Benefits = - Documentation - Knowing that the first argument is expected to be, say, either an Integer or nil doesn't tell you what that arguments *means*, but it's something. - Locating Problems - Write a type signature for a method, and then when you try running the code, if something's amiss (from a *type* standpoint), you can see exactly where it happened. Instead of a null pointer exception up at level A, you get a TypeMismatch exception three layers deeper, exactly where the nil was mistakenly returned from some method. Again, it doesn't test the semantics of your code, but it's something. - Tracking Usage - HasType keeps track of where each method (actually) gets called from. So, after running your code for a while, you may then ask about any (type-signed) method where it got called from (if anywhere). It's not comprehensive, of course; if one potential call site itself never got called, you won't see that. But again, it's something. = Design Goals = + Type signature documentation. - types of input arguments - type of result + Terse and expressive. - Single-line. Simple format. + Stays in sync. (It's code that's validated.) - However, checks are at RUN-time, not load-time. - Method must be called in order for its signature to be checked. + Facilities for code/signature maintenance: - Records all callers of method, to see how it's used. (Consequently, one can see which methods did not get called.) - For called methods, record which types were *actually* sent, to help "tighten up" too-permissive signatures. + No performance penalty. - Turn on (dev): raises, flagging errors. - Turn off (prod): does nothing. = Usage = 1. include HasType 2. Make declarations: - 'sig' for methods - 'type' for type synonyms = How it Works = + Creates a "wrapper" method around original. + Each time method is called: - Checks input and result types. - Raises exception if something is amiss. - Gathers info: + actual types of args and result + the name/location of most-recent call spot = Example = class Foo include Glyde::HasType ## ## Applies appropriate signatures to ## auto-generated reader/writer methods. ## attr_accessor :my_int do Integer end ## ## #foo takes: ## 1. an Integer ## 2. a Bool (either true|false) (but _not_ nil [to mean false]) ## 3. either a Symbol OR nil ## #foo returns: nothing meaningful (i.e. won't be checked) ## sig do Integer * Bool * (Symbol|nil) >> () end def foo(a, b, c) ... end ## ## Create re-usable type, namespaced locally. ## type :MyKey do String|Symbol end type :MyVal do Integer|String end type :MyHash do {MyKey => MyVal} end ## ## Use those types ## sig do MyHash * MyKey * MyVal >> MyHash end def bar(hash, k, v) hash[k] = v hash end end = NOTES: Using 'nil' = + () == nil + like Maybe: sig do Integer|nil * String >> Foo end First arg may be either an Integer or nil. Second arg may be only a String. If nil: TypeMismatch error. + not the same as default sig do String >> () end def foo(str) end If arg is absent, that's an ArgmentError. If arg's val is nil, that's a TypeMismatch error. sig do String.opt >> () end def foo(str='') end If arg isn't provided, that's ok; default will apply. If arg IS provided, it must be a String. (Not nil.) sig do (String|nil).opt >> () end def foo(str='') end Arg may be provided or not. If provided, can be String or nil. + N.B. Bool means only true|false. If nil is acceptable (to mean 'false'), say so: Bool|nil.