Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
February 19, 2022 09:57 am GMT

Why I love Ruby (part 3)

Right on the main page of the Ruby language there's this beautiful quote from Matz:

Ruby is simple in appearance, but is very complex inside, just like our human body.

It's Ruby's simplicity that I really like.

If you ask me "What do you see when you see Ruby code?" my answer is method calls.

Take a look at this code:

require "set"class MySet  include Enumerable  attr_reader :inner_set  # ...end

There are some things here:

  • we require some external code
  • we declare a class with the class keyword
  • we include functionality from another module with include
  • we declare a read-only property with attr_reader

It turns out, only class is special here: it's a language construct that lets you define classes. But require is a method call defined by Ruby, and the same is true about include and attr_reader. They look like keywords, like something special, but they are just regular methods.

This ability of having almost everything look the same, in a consistent way, is what lets you, the Ruby user, also define things that look like language keywords, but are just ordinary methods.

In fact, it's very easy to define attr_reader ourselves. Because attr_reader already exists, lets use the name getter for that:

class Module  def getter(name)    symbol = "@#{name}".to_sym    define_method(name) do      instance_variable_get(symbol)    end  endendclass Point  getter :x  getter :y  def initialize(x, y)    @x = x    @y = y  endendpoint = Point.new 1, 2point.x # => 1point.y # => 2

And there we have it!

Well, using getter is simple. Maybe defining it is a bit more complex... but it's definitely possible!

Maybe the additional feature of not having to put parentheses around method calls makes this look more like a keyword than a method. Imagine if parentheses were required for method calls:

require("set")class MySet  include(Enumerable)  attr_reader(:inner_set)  # ...end

They don't look like keywords anymore, right? My guess is that allowing to omit parentheses was done for exactly this reason, but of course you can omit parentheses anywhere because Ruby doesn't know if you are going to use something "like a keyword" or not.

This of course is used in Ruby a lot. Here's an example of Rails' ActiveRecord:

class User < ApplicationRecord  has_many :postsend

To be honest, a snippet like that might have been one of the first code snippets I stumbled upon when I was learning Ruby together with Ruby on Rails. With this, you can kind of create your own mini-language for users. And all these languages will be relatively easy to use: they will all be based around method calls. Of course the names will vary, or the number of arguments. But in the end they are all method calls.

One other language that did this, and it actually did it better, or more consistently, is Elixir. In Elixir you define a module like this:

defmodule SomeModule do  # ...end

You define a function like this:

def method doend

You use an if like this:

if something doend

Note that there's always a do in the end. These are all functions defined by Elixir! (well, technically they are macros) For example, here are the docs of defmodule, from which you can jump to the source code. This is brilliant!

Like in Ruby, this also means that in Elixir you will be using the same small language (calls) for almost everything.

In other languages you might be able to do the same thing, but there might be other syntax involved. For example in Rust you call macros with a bang at the end:

format!("Hello, {}!", "world");

In Julia you use @ to call macros:

macro sayhello()  return :( println("Hello, world!") )end@sayhello()

In D you can use the mixin keyword to generate code at compile-time:

template GenStruct(string Name, string M1){    const char[] GenStruct = "struct " ~ Name ~ "{ int " ~ M1 ~ "; }";}mixin(GenStruct!("Foo", "bar"));

This might seem like a little thing, but from a user perspective you have to know what you are using. "Oh, I'm using a macro so I have to do it this way." In Ruby and Elixir it's just "I call it."

Carole King couldn't have said it better:

Winter, spring, summer or fall
All you have to do is call

Because with Ruby... you've got a friend

For Crystal we considered having a different syntax for invoking macros, but in the end we used the same syntax as for method calls. And we really like the end result! Take a look at this Crystal code:

record Point, x : Int32, y : Int32point = Point.new(1, 2)

It looks like record is a keyword that defines a type with two properties. But it's actually a macro, here's the documentation.

As a comparison, Java 14 added records to the language (where did they get that name from?! ) by introducing a new keyword:

record Rectangle(float length, float width) { }

If Java had a way to reduce such boilerplate right in the language itself, it wouldn't need to introduce keywords: they could just do it with the language itself. As a bonus things would get automatically documented in the API docs.

One other benefit of having such constructs be methods or macros in a language is that, at least in Ruby and Crystal, a user can redefine them. For example zeitwerk redefines require and makes it work in a different, better way. In what other language can you do this?

Coming up next...

In the next blog post I'll be talking about being able to design that perfect API.


Original Link: https://dev.to/asterite/why-i-love-ruby-part-3-2oin

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To