Module: Mal

Extended by:
Mal
Included in:
Mal
Defined in:
lib/mal.rb

Overview

The module allows you to define simple data structure schemas, and to match your data against those schemas. Primary use is for HTTP parameters, JSON-derived datastructures and the like.

Let’s start with the basics. Any “typespec” returned by the library responds to ‘===`. The most basic (and all-encompassing) typespec there is is called `Anything()`.

Anything() === false  #=> true
Anything() === true   #=> true
Anything() === Module #=> true

A more specific type is an Only(), which is similar to just using a class, module or Regexp (which all support ===), but it can be composed with other typespecs.

Only(/hello/) === 123           #=> false
Only(/hello/) === "hello world" #=> true

Interesting things come into play when you use combinations of the typespecs. For example, you want to ensure the value referenced by ‘my_var` is either a Fixnum or a String matching a regular expression. For this, you need to create a compound matcher using an `Either()` typespec (disjoint union):

Either(Fixnum, Both(String, /hello/)) === "hello world"  #=> true
Either(Fixnum, Both(String, /hello/)) === 123            #=> true
Either(Fixnum, Both(String, /hello/)) === Module         #=> false, since it is neither of

You can also use the ‘|` operator on most of the typespecs to create these disjoint unions - but if you have a matchable object on the left side of the expression you mught have to wrap it in an `Only()`:

Only(Fixnum) | Only(String) #=> Either(Fixnum, String)

Even more entertainment becomes possible when you match deeper structures with nesting - hashes for example. There are two methods for those - ‘HashWith()` and `HashOf`. `HashWith` checks for the presence of the given key/value pairs and checks values for matches, but if there are other keys present in the Hash given for verification it won’t complain. ‘HashOf()`, in contrast, will ensure there are only the mentioned keys in the Hash, and will not match if something else is present.

HashWith(age: Fixnum) === {age: 12, name: 'Cisco Kid'} #=> true
HashOf(age: Fixnum) === {age: 12, name: 'Cisco Kid'} #=> false

Note that it is entirely plausible that you would not want to include Mal into your object/class/whatever. For that case, calling methods using their qualified module name (‘Mal.Some()…`) can become a nuisance. If it does, use `Mal.typespec` which is a shortcut to `instance_exec` - but has the convenient property of not alerting your style watchers to your use of `instance_exec` :-P

Defined Under Namespace

Classes: AnythingT, ArrayT, BoolT, CoveringT, EitherT, HashOfOnlyT, HashPermittingT, HashT, IncludingT, LengthT, MaxLengthT, MaybeT, MinLengthT, NilT, ObjectT, OnlyT, SatisfyingT, UnionT, ValueT

Constant Summary collapse

VERSION =
'0.0.5'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.typespec(&blk) ⇒ Object

A shortcut for ‘instance_exec`, for defining types using shorthand method names from outside the module:

Mal.typespec { Either(Value(1), Value(2)) }


420
421
422
# File 'lib/mal.rb', line 420

def self.typespec(&blk)
  instance_exec(&blk)
end

Instance Method Details

#AnythingObject

Just like it says: will match any value given to it



391
392
393
# File 'lib/mal.rb', line 391

def Anything()
  AnythingT.new
end

#ArrayOf(typespec_for_array_element) ⇒ Object

Specifies an Array of at least 1 element, where each element matches the typespec for the array element



338
339
340
# File 'lib/mal.rb', line 338

def ArrayOf(typespec_for_array_element)
  ArrayT.new(typespec_for_array_element)
end

#BoolObject

Specifies a value that is either ‘true` or `false` (truthy or falsy values do not work)



311
312
313
# File 'lib/mal.rb', line 311

def Bool()
  BoolT.new
end

#Both(*matchables) ⇒ Object

Specifies a value that matches both the given matchers. For instance, can be a matcher for both a String and a Regexp



322
323
324
# File 'lib/mal.rb', line 322

def Both(*matchables)
  UnionT.new(*matchables)
end

#CoveredBy(range) ⇒ Object



408
409
410
# File 'lib/mal.rb', line 408

def CoveredBy(range)
  CoveringT.new(range)
end

#Either(*matchables) ⇒ Object

Specifies a value that matches either one of the given speciciers



327
328
329
# File 'lib/mal.rb', line 327

def Either(*matchables)
  EitherT.new(*matchables)
end

#HashOf(**keys_to_values) ⇒ Object

Specifies a Hash containing only the given keys, with values at those keys matching the given matchers. For example, for a Hash having at the ‘:name` key with a corresponding value that is a String:

HashOfOnly(name: String)

Because the match is-strict, it will not match a Hash having additional keys

HashOf(name: String) === {name: 'John Doe', age: 21} #=> false


379
380
381
# File 'lib/mal.rb', line 379

def HashOf(**keys_to_values)
  HashOfOnlyT.new(**keys_to_values)
end

#HashPermitting(**keys_to_values) ⇒ Object

Specifies a Hash containing the given keys/values or their subset, will also match an empty Hash. Will not match a Hash having extra keys.



386
387
388
# File 'lib/mal.rb', line 386

def HashPermitting(**keys_to_values)
  HashPermittingT.new(**keys_to_values)
end

#HashWith(**keys_to_values) ⇒ Object

Specifies a Hash containing at least the given keys, with values at those keys matching the given matchers. For example, for a Hash having at least the ‘:name` key with a corresponding value that is a String:

HashWith(name: String)

Since the match is non-strict, it will also match a Hash having more keys

HashWith(name: String) === {name: 'John Doe', age: 21} #=> true


351
352
353
# File 'lib/mal.rb', line 351

def HashWith(**keys_to_values)
  HashT.new(**keys_to_values)
end

#IncludedIn(*values) ⇒ Object



412
413
414
# File 'lib/mal.rb', line 412

def IncludedIn(*values)
  IncludingT.new(values)
end

#Maybe(matchable) ⇒ Object

Specifies a value that is either matching the given typespec, or is nil



332
333
334
# File 'lib/mal.rb', line 332

def Maybe(matchable)
  MaybeT.new(matchable)
end

#NilObject

Specifies a value that may only ever be ‘nil` and nothing else



306
307
308
# File 'lib/mal.rb', line 306

def Nil()
  NilT.new(NilClass)
end

#ObjectWith(*properties) ⇒ Object

Specifies an object responding to certain methods

ObjectWith(:downcase) === "foo" #=> true


358
359
360
# File 'lib/mal.rb', line 358

def ObjectWith(*properties)
  ObjectT.new(*properties)
end

#OfAtLeastElements(n) ⇒ Object



362
363
364
# File 'lib/mal.rb', line 362

def OfAtLeastElements(n)
  MinLengthT.new(n)
end

#OfAtMostElements(n) ⇒ Object



366
367
368
# File 'lib/mal.rb', line 366

def OfAtMostElements(n)
  MaxLengthT.new(n)
end

#Only(matchable) ⇒ Object

Specifies a value that matches only the given matcher



316
317
318
# File 'lib/mal.rb', line 316

def Only(matchable)
  OnlyT.new(matchable)
end

#Satisfying(&blk) ⇒ Object

Matches the given value if the passed block/Proc returns true when called with that value

Satisfying {|x| x > 10 } === 11 #=> true


404
405
406
# File 'lib/mal.rb', line 404

def Satisfying(&blk)
  SatisfyingT.new(&blk)
end

#Value(value) ⇒ Object

Matches the contained value exactly using the == operator. Will work well where exact matches are desired, i.e. for strings, numbers and native language types.



398
399
400
# File 'lib/mal.rb', line 398

def Value(value)
  ValueT.new(value)
end