Difference between abstract def and def

Well, IMO, they don’t make documentation any better. They make it more confusing, and add more verbosity to the language.

They are not a thought or an idea, or not having concrete existence (it’s concrete code written out, for child classes to follow). I believe what they are doing is the total opposite of what the definition is. However, since abstract is not unique to Crystal, this seems more like a personal pet peeve, not an issue with Crystal.

I’ve been told you can’t have logic in abstract defs, but they are full of logical code. It’s just they can’t use the logic directly, only children that are inherited from them can use it. I feel like this is a big difference!

edit: How is this functionality “abstraction?”. Doesn’t even make sense. There must be a different definition of abstract in the context of programming.

Yeah, I think it’s a matter of context. We were introduced via a world of base classes and single extension hierarchy with mixins (assuming you’re mostly a Rubyist prior). Someone coming to Crystal from another statically typed language with multiple inheritance and required abstract classes will likely find this a comforting bit of syntax until they learn about crystal magic. But at the end of the day we can extend ruby-style without penalty if we’re already used to taking it for granted.

@girng Take this for example.

abstract class EmailProvider

  abstract def get_message : String
  
  def send : Nil
    puts self.get_message
  end
end

class SomeEmailCompany1 < EmailProvider
  def get_message : String
    "Message format 1"
  end
end

class SomeEmailCompany2 < EmailProvider
  def get_message : String
    "Message format 2"
  end
end

SomeEmailCompany1.new.send # => "Message format 1"

In this example we have an abstract class with 2 methods. A send method and an abstract get_message. The abstract EmailProvider class implements logic that would be usable by ALL children, in this case how to actually send an email. However the format each email provider could be different. So the EmailProvider class also defines an abstract def that would force each child to implement. It also tells the compiler that that method will exist, so it can be used in the send method.

But lets say you’re still not convinced and refuse to use it and just setup a normal def that returns an empty string in the abstract EmailProvider class.

abstract class EmailProvider

  def get_message : String
    ""
  end
  
  def send : Nil
    puts self.get_message
  end
end

class SomeEmailCompany1 < EmailProvider
end

class SomeEmailCompany2 < EmailProvider
  def get_message : String
    "Message format 2"
  end
end

SomeEmailCompany1.new.send # => ""

Since there is nothing enforcing the a child class implements a get_message method, it can lead to bugs, like “why is this an empty string?” whereas it wouldnt compile if you had the def as abstract.

HOWEVER, it is totally fine to make a def NOT abstract in an abstract parent class in some cases. For example:

abstract class EmailProvider
  def can_send : Bool
    true
  end

  def send
    if self.can_send
      puts "Do something"
    else
      puts "Do something else"
    end
  end
end

class SomeEmailCompany1 < EmailProvider
end

class SomeEmailCompany2 < EmailProvider
  def can_send : Bool
    false
  end
end

SomeEmailCompany1.new.send # => "Do something"
SomeEmailCompany2.new.send # => "Do something else"

In this case can_send has a common default value that will be inherited within each child. The child can override it if they so choose.

Or, just remove abstract, and use:

class EmailProvider
  def send : Nil
    puts self.get_message
  end
end

class SomeEmailCompany1 < EmailProvider
  def get_message : String
    "Message format 1"
  end
end

class SomeEmailCompany2 < EmailProvider
  def get_message : String
    "Message format 2"
  end
end

SomeEmailCompany1.new.send # => "Message format 1"

SomeEmailCompany2.new.send # => "Message format 2"

Which is the same functionality? If you want a specific format, the developer can edit that format in the specific child class.

Since there is nothing enforcing the a child class implements a get_message method, it can lead to bugs, like “why is this an empty string?”

If a developer doesn’t know there is a required method for a class… the developer should look at the API / parent class for functionality. This is one of the main benefits of a parent->child relationship.

Since there is nothing enforcing the a child class implements a get_message method, it can lead to bugs, like “why is this an empty string?” whereas it wouldnt compile if you had the def as abstract.

This throws an error. Abstract isn’t doing any magic or benefiting the developer, the compiler is already smart enough.

In this specific case, yes its functionality the same. However you lose any safeguards if you forgot to define it.

Without the abstract def you wouldn’t know a child is required to have a get_message by just looking at the API docs.

Sure it is, Error in line 2: abstract 'def EmailProvider#get_message()' must be implemented by SomeEmailCompany2 is a lot more clear than a generic undefined method error.

abstract in your example just provided, literally just transformed a simple Parent → Class example that works flawlessly with Crystal’s object-oriented design, into something that looks far more verbose than what it’s actually doing. I just can’t fathom why we should litter Crystal’s beautiful syntax

What’s next, abstract private protected def? And a couple annotations above it? There is a time where enough is enough, and where too much syntax becomes convoluted.

in line 3: undefined method ‘get_message’ for SomeEmailCompany2

Inherently implies that though. I mean, the developer just has to find SomeEmailCompany2 and put in the get_message method that they forgot.

It’s still a parent → child class. Docs should show what methods are needed regardless of some silly prefixed keyword.

Crystal is an OO language, these are just OO patterns. Sure somethings may not be as required as other languages but IMO they’re still worth doing as you’re making it more clear what your intent is.

abstract protected def get_message : String would actually be a better way to implement my previous example. This would remove get_message from the public API docs, and just make it an implementation detail. Having a smaller public API makes the class easier to understand.

Do remember there could be other people working on your code. Defining the method as abstract makes it clear to any other dev that knows OOP what is expected. Same idea with the diff error message.

But in your example the parent class has no knowledge of what methods the child would have.

versus

The latter image tells you much more about the expected behavior. The class is abstract so you know its not expected to be able to be instantiated on its own. There is an abstract def, so you know each child is 100% going to have a get_message method.

1 Like

Nowhere else on god’s green earth will you find me littering my code with unneeded syntax just to make it prettier for a docs page that will never get generated.

Not every developer is using Crystal just to make shards. Also, the docs should generate that kind of expressiveness with parent -> child classes regardless of using abstract.

Doc generation syntax should not hinder the syntax of Crystal!

1 Like

It’s not just for documentation, it makes the intent much more clear. However you seem to refuse to use most of OOP patterns so your stance on this somehow doesn’t surprise me.

How would it know what your intention is if you don’t tell it?

It’s already a Parent → Child class/relationship. That’s more than enough?

The compiler is really smart. I see a lot of developers relying on certain syntax to save them from “future compiler errors”, which isn’t necessarily true. A great example of this is my example above. In reality, it ends up being verbose and diminishes Crystal’s minimalist syntax.

OOP is great when it’s simple. Adding 2-4 prefix keywords behind every def/class/struct hinders that simplicity. To a point where only the core developers understand what’s going on, while peasants like me are stuck in the back trying to stay afloat.

Going to give my last thoughts on this then go eat.

No, quite frankly it’s not. Without the abstract def there is nothing to tell someone what the intended behavior of that relationship should be. Is get_message optional? Is it required? Should it be implemented by all children or only some? You just don’t know. What happens if you have a subclass that returns a Bool instead of a string, is that the expected behavior or an error? For how simple it is to say abstract def get_message : String you make it SO much more clear on what the intended behavior is. Now anyone would know, ok if i want to implement a new email provider, i need to inherit from this class and implement a get_message method that returns a string. There. Done.

I know where you are coming from. As a relatively new programmer myself, it is very easy to go “oh my code works fine, I don’t need any of this extra stuff”. There is a reason all this was defined and is common in OOP languages. Those prefixes control HOW that def/struct should behave. It’s also very easy to get into the mindset the “my code is perfect”. No, it’s probably not. You’ll look back a year from now and go “wow i see how xxx could have helped”. Learning all of these concepts and patterns is not something you pick up overnight. However, if you stick with it, keep learning, and get some exposure to applications other than your own; then hopefully you’ll start to understand the reasoning behind all of this “extra stuff”.

My code is definitely not perfect. However, it works. And it functions in a way that is beautiful to me.
How I can comprehend it is the most important part.

This definitely does not fit under the definition of abstraction. These seem like requirements and rule specifications more than abstraction. Again, abstract is really a misnomer. And it’s sad you don’t see that. I feel like you have completely nullified/ignored my points made in this thread.

If written out correctly and coherently, it should be obvious to the developer what methods they can use in their classes to change functionality… It’s really that simple. You don’t need to litter code with abstract to achieve this…

The issue is, when seeing all these keywords, it’s an immediate turn off. Too complex, too verbose. It just re-affirms my initial belief.

Oh, I think I’m starting to understand it. I also am starting to understand why it’s completely unnecessary…

The fact is many languages use this pattern. It is commonly accepted and most people don’t have a problem with it. It is not perfect for all situations, but as Crystal is a typed language, it does improve code quality and gives the compiler a better idea of what you’re trying to achieve.

In C++ abstract classes are called virtual classes. This might make more sense to you, but it’s not really a huge semantic leap.

2 Likes

The word misnomer exists for a reason. Not everything is perfect (which it seems like everyone here (and gitter) believes everything in programming is set in stone and should never be questioned!)

Actually, I think in all the programming communities I’ve been a part of, this mindset is evident. Not just Crystal.

I’ve heard you call abstract a misnomer, but I haven’t heard you give a better name for it. Personally I think that both abstract and virtual pretty well describe what is happening. That, and they’re both short enough so as not to be a pain in the ass to type over and over.

Also general consensus is important. One developer may feel like the incorrect word is being used, but the community as a whole obviously feels different. That should make you stop and wonder if there is just something you’re not understanding.

2 Likes

abstract makes very little sense on what it’s actually doing. I already explained this above so many times.

It’s not a thought or idea, it’s concrete code written out for children to follow. That’s not something that is “abstract”.

The programming community in general would tend to disagree. It’s abstract because it cannot be initialized. It’s barely more than a framework for other classes to be based on. It would be one thing if it could actually be used, but since it can’t abstract makes as much sense as anything else.

Anyway, I’m not sure what else anyone can say to explain what they’re for. They are useful, but generally more so when you’re tying to expose an API for other developers to use or for yourself to reuse. If you don’t typically expose libraries for other people then it might be hard to understand why you’d want to use an abstract class. Same for if you don’t typically reuse code and instead prefer to rewrite things.

I don’t see how specifying rules for child classes to require a method is “useful”. If anything, it’s a hindrance. The developer should have free reign on classes. Regardless if a Parent class has rules or not.

Also, the compiler is smart enough to pin point where the developer needs to add a missing method. Adding an abstract makes it a bit more clear, but the former compiler error is already explicit enough.

I would rather have the first error be shown, instead of reading code littered with abstract.

You don’t have to use abstract. If you don’t like it, then just don’t.

But for many developers, it is a valuable feature. Yes, you can explore which interface a class has to implement by following the compiler errors pointing you to the missing classes. Or you can just have all the methods specified as abstracts (in the best case including explicit types and documentation) and you know exactly what to implement, in order to provide the intended interface. That’s why you use abstract definitions. They document the intent and function of an interface.

4 Likes