Literally is a tiny pre-processor for Ruby that lets you write methods with runtime type checking using Literal.
Literally uses genuine Ruby syntax but pre-processes it to be interpreted in a different way.
You can specify a return type for a method.
def say_hello = String do
"Hello World!"
endThis will add a runtime assertion that the return value is a String.
Note
The return value must be specified for Literally to process the method at all. You can always specify that it returns _Void or _Any?.
We use Ruby’s default value for arguments as the place to specify the type.
def say_hello(name = String) = String do
"Hello #{name}!"
endOkay, but how do we specify defaults?
def say_hello(name = String {"World"}) = String do
"Hello #{name}!"
endKeyword arguments work the same way
def say_hello(name: String {"World"}) = String do
"Hello #{name}!"
endSplats are typed by using an Array literal
def say_hello(names = [String]) = String do
"Hello #{names.join(", ")}!"
endThis is the equivalent to def say_hello(*names). In this context [String] makes the *names splat and types it as _Array(String).
You don’t need to specify a default because splats always default to an empty Array.
But what if you wanted to use the literal value [String] itself as a type? [String] is in fact a type that only matches an array that contains the String class.
As you can see
[String] === [String]In this case, you can wrap the type in parentheses
def say_hello(names = ([String])) = String do
# the positional argument `names` here must be
# an array with the class `String` and nothing else inside it.
endKeyword splats are the same but using a Hash literal to specify K/V types. In this context, {String => String} makes the **greetings keyword splat and types it as _Hash(String, String).
def say_hello(greetings: {String => String}) = String do
greetings.map do |greeting, name|
"#{greeting} #{name}"
end.join("\n")
endAgain, defaults don’t need to be specified here because the default will always be an empty Hash.
Let’s say you have a type called Position.
Position = _Tuple(Integer, Integer, Integer)And you want to accept *position as Position, you could specify that like this
def move_to(position = [*Position]) = _Void do
endSame with keyword arguments and keyword splats
Position = _Map(x: Integer, y: Integer, z: Integer)def move_to(position: {**Position}) = _Void do
endBlocks are always optional and always Procs so there’s no reason to type them. If you want to require a block, we recommend adding raise unless block_given? to your method.