Literally [WIP]
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.
Return types
You can specify a return type for a method.
def say_hello = String do
"Hello World!"
end
This 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?
.
Positional arguments
We use Ruby’s default value for arguments as the place to specify the type.
def say_hello(name = String) = String do
"Hello #{name}!"
end
Okay, but how do we specify defaults?
def say_hello(name = String {"World"}) = String do
"Hello #{name}!"
end
Keyword arguments
Keyword arguments work the same way
def say_hello(name: String {"World"}) = String do
"Hello #{name}!"
end
Splats
Splats are typed by using an Array literal
def say_hello(names = [String]) = String do
"Hello #{names.join(", ")}!"
end
This 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.
end
Keyword splats
Keyword 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")
end
Again, defaults don’t need to be specified here because the default will always be an empty Hash.
Splats with named types
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
end
Same with keyword arguments and keyword splats
Position = _Map(x: Integer, y: Integer, z: Integer)
def move_to(position: {**Position}) = _Void do
end
Blocks
Blocks 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.