Change the class comparator function

Hi, today for a special class, I need to change the way how my class compare it property version (it’s a String).

I did that, but I have got an error:

In ISM/SoftwareDependency.cr:17:20

 17 | def version>(other : String) : Bool
                 ^
Error: unexpected token: ">"

My class:

module ISM

    class SoftwareDependency

        property name : String
        property options : Array(ISM::SoftwareOption)

        def initialize
            @name = String.new
            @version = String.new
            @options = Array(ISM::SoftwareOption).new
        end

        def version=(@version)
        end

        def version>(other : String) : Bool
            return (SemanticVersion.parse(version) > SemanticVersion.parse(other))
        end

        def version<(other : String) : Bool
            return (SemanticVersion.parse(version) < SemanticVersion.parse(other))
        end

        def version>=(other : String) : Bool
            return (SemanticVersion.parse(version) >= SemanticVersion.parse(other))
        end

        def version<=(other : String) : Bool
            return (SemanticVersion.parse(version) <= SemanticVersion.parse(other))
        end

        def version
            availableVersion = @version

            if @version.includes?("<") || @version.includes?(">")
                if @version[0] == ">" && @version[1] != "="
                    availableVersion = @version.tr("><=","")

                    Ism.softwares.each do |software|
                        if @name == software.name
                            temporaryVersion = @version.tr("><=","")

                            software.versions.each do |information|
                                temporarySoftwareVersion = information.version.tr("><=","")
                                if temporaryVersion < temporarySoftwareVersion && availableVersion < temporarySoftwareVersion
                                    availableVersion = temporarySoftwareVersion
                                else
                                    availableVersion = @version
                                end
                            end

                        end
                    end
                end

                if @version[0] == "<" && @version[1] != "="
                    availableVersion = @version.tr("><=","")

                    Ism.softwares.each do |software|
                        if @name == software.name
                            temporaryVersion = @version.tr("><=","")

                            software.versions.each do |information|
                                temporarySoftwareVersion = information.version.tr("><=","")
                                if temporaryVersion > temporarySoftwareVersion && availableVersion > temporarySoftwareVersion
                                    availableVersion = temporarySoftwareVersion
                                else
                                    availableVersion = @version
                                end
                            end
                        end
                    end
                end

                if @version[0..1] == ">="
                    availableVersion = @version.tr("><=","")

                    Ism.softwares.each do |software|
                        if @name == software.name
                            temporaryVersion = @version.tr("><=","")

                            software.versions.each do |information|
                                temporarySoftwareVersion = information.version.tr("><=","")
                                if temporaryVersion <= temporarySoftwareVersion && availableVersion <= temporarySoftwareVersion
                                    availableVersion = temporarySoftwareVersion
                                else
                                    availableVersion = @version
                                end
                            end
                        end
                    end
                end

                if @version[0..1] == "<="
                    availableVersion = @version.tr("><=","")

                    Ism.softwares.each do |software|
                        if @name == software.name
                            temporaryVersion = @version.tr("><=","")

                            software.versions.each do |information|
                                temporarySoftwareVersion = information.version.tr("><=","")
                                if temporaryVersion >= temporarySoftwareVersion && availableVersion >= temporarySoftwareVersion
                                    availableVersion = temporarySoftwareVersion
                                else
                                    availableVersion = @version
                                end
                            end
                        end
                    end
                end
            end

            return availableVersion
        end

        def getDependencies : Array(ISM::SoftwareDependency)
            dependencies = Array(ISM::SoftwareDependency).new

            Ism.softwares.each do |software|

                if software.name == @name

                    software.versions.each do |softwareVersion|

                        if version == softwareVersion.version
                            dependencies = dependencies + softwareVersion.dependencies
                            break
                        end

                    end

                end
            end

            return dependencies
        end

        def getInformation : ISM::SoftwareInformation
            dependencyInformation = ISM::SoftwareInformation.new

            Ism.softwares.each do |software|

                if software.name == @name

                    software.versions.each do |softwareVersion|

                        if version == softwareVersion.version
                            dependencyInformation = softwareVersion
                            break
                        end

                    end

                end
            end

            return dependencyInformation
        end

        def == (other : ISM::SoftwareDependency) : Bool
            return @name == other.name && @version == other.version && @options == other.options
        end

    end

end

If the expectation is that #version is the only thing that should be taken into consideration when comparing two SoftwareDependency, then I would look into Comparable(T) - Crystal 1.7.2. Otherwise if you’re wanting a way to compare each value on its own, then you’ll just have to use method names that don’t have those special symbols in them.

What do you mean with comparable ? I’m not sure to understand. I only understood your last advice
Maybe can you show me an example ?

I did that, do you think it’s okay ?
I’m just afraid to do a bad edit in the standard library

struct SemanticVersion

    def >(other : String | SemanticVersion) : Bool
        cmp = self <=> (other.is_a?(String) ? SemanticVersion.parse(other) : other)
        cmp ? cmp > 0 : false
    end

end

The Comparable module can be included into a type, and by defining 1 method, you can then compare instances of that type. E.g.

class Foo
  include Comparable(self)

  property name : String
  property priorty : Int32

  def_equals @name, @priorty

  def initialize(@name : String, @priorty : Int32 = 0); end

  def <=>(other : self) : Int32?
    @priorty <=> other.priorty
  end
end

f1 = Foo.new "One"
f2 = Foo.new "Two", 2
f3 = Foo.new "Three", -1
f4 = Foo.new "Four"

pp f2 > f3  # => true
pp f1 > f2  # => false
pp f1 > f3  # => true
pp f1 == f4 # => false

So in your case if SoftwareDependency should only be comparsed based on their @version ivar, then the <=> method would just be like @version <=> other.version. Could still keep the #== method if equality should also take the name into account.

I wouldn’t do that no. Be easier to just have your SoftwareDependency type accept String | SemanticVersion but normalize them to the latter so internally it’s only @version : SemanticVersion.

I can’t put the version property as SemanticVersion, specially for this class, because version can be structured like that:

">=1.2.6" (with the comparators in the String)

SoftwareDependency is very special for that. Because a dependency can require a version equal or higher. (or opposite)

Break it out into two ivars. some @operator typed as an enum, and the actual semantic version.

then you should create a custom class for your version string.

struct Version
  def initialize(@version : String)
  include Comparable(self)

  def <=>(other : Version)
    # implement your comparison method here, return -1 if smaller, 0 if equal or 1 if greater. 
  end

then you can compare versions directly:

a = Version.new("1.2")
b = Version.new("1.3")
puts a > b # => false

I’m a bit annoying to be honest, because a package manager need to face a lot of different version naming methodology.

Some follow almost the SemanticVersion class, but others just follow the date when they was build, or something else.

Developers don’t have the same way

yeah that’s true, but your original question is how to implement
version>, version<… or something can achieve the same thing.

well you can’t directly create a version> method, but you sure can compare two version object (version_a > version_b) using the Comparable module which will generate all the comparison methods you want, only require you to implement the spaceship (<=>) operator.

Hi again. So after thinks and listening your advices, I start to implement my own version class.

I was thinking about inherit this class from String.

module ISM

    class Version < String

        def <=>
            return SemanticVersion.parse(value) <=> SemanticVersion.parse(other.value)
        end

        def value
            return tr("><=","")
        end

        def includeComparators
            return includes?("<") || includes?(">")
        end

        def includeGreaterOrEqual
            return self[0..1] == ">="
        end

        def includeLessOrEqual
            return self[0..1] == "<="
        end

        def includeGreater
            return self[0] == ">" && self[1] != "="
        end

        def includeLess
            return self[0] == "<" && self[1] != "="
        end

    end

end

But why I got this error ??? I mean really ?

zohran@alienware-m17-r3 ~/Documents/Programmation/ISM $ crystal build Main.cr -o ism
Showing last frame. Use --error-trace for full trace.

In RequiredLibraries.cr:13:1

 13 | require "./ISM/Version"
      ^
Error: Cannot inherit from String

It’s a bit frustrating, with Ruby (and a lot of other languages as well) we can do that normally

For my code actually it’s more convenient to have my own custom String class than integrate a new object as class attribute

Yes, extending classes you don’t own is usually a bad idea. It would be better to use the decorator pattern. Something like

struct Version
  @inner : String

  forward_missing_to @inner

  def initialize(@inner : String); end

  def includes_comparators? : Bool
    @inner.includes?('>') || @inner.includes?('<')
  end

  # ...
end
1 Like

Finally I found a much better work around, I thought a bit about the problem and I finally realized I can do something a lot more simple, and without change a lot of my implementation:

And like this, I don’t need to make a custom string class or a Version class. SoftwareDependency manage the complexity itself.

I just use SemanticVersion class when I need only to compare 2 versions

module ISM

    class SoftwareDependency

        property name : String
        property options : Array(ISM::SoftwareOption)

        def initialize
            @name = String.new
            @version = String.new
            @options = Array(ISM::SoftwareOption).new
        end

        def version=(@version)
        end

        def includeComparators : Bool
            return @version.includes?("<") || @version.includes?(">")
        end

        def greaterComparator : Bool
            return @version[0] == ">" && @version[1] != "="
        end

        def lessComparator : Bool
            return @version[0] == "<" && @version[1] != "="
        end

        def greaterOrEqualComparator : Bool
            return @version[0..1] == ">="
        end

        def lessOrEqualComparator : Bool
            return @version[0..1] == "<="
        end

        def version
            availableVersion = @version

            if includeComparators
                if greaterComparator
                    temporaryAvailableVersion = SemanticVersion.parse(@version.tr("><=",""))

                    Ism.softwares.each do |software|
                        if @name == software.name
                            temporaryVersion = SemanticVersion.parse(@version.tr("><=",""))

                            software.versions.each do |information|
                                temporarySoftwareVersion = SemanticVersion.parse(information.version.tr("><=",""))
                                if temporaryVersion < temporarySoftwareVersion && temporaryAvailableVersion < temporarySoftwareVersion
                                    availableVersion = temporarySoftwareVersion.to_s
                                else
                                    availableVersion = @version
                                end
                            end

                        end
                    end
                end

                if lessComparator
                    temporaryAvailableVersion = SemanticVersion.parse(@version.tr("><=",""))

                    Ism.softwares.each do |software|
                        if @name == software.name
                            temporaryVersion = SemanticVersion.parse(@version.tr("><=",""))

                            software.versions.each do |information|
                                temporarySoftwareVersion = SemanticVersion.parse(information.version.tr("><=",""))
                                if temporaryVersion > temporarySoftwareVersion && temporaryAvailableVersion > temporarySoftwareVersion
                                    availableVersion = temporarySoftwareVersion.to_s
                                else
                                    availableVersion = @version
                                end
                            end
                        end
                    end
                end

                if greaterOrEqualComparator
                    temporaryAvailableVersion = SemanticVersion.parse(@version.tr("><=",""))

                    Ism.softwares.each do |software|
                        if @name == software.name
                            temporaryVersion = SemanticVersion.parse(@version.tr("><=",""))

                            software.versions.each do |information|
                                temporarySoftwareVersion = SemanticVersion.parse(information.version.tr("><=",""))
                                if temporaryVersion <= temporarySoftwareVersion && temporaryAvailableVersion <= temporarySoftwareVersion
                                    availableVersion = temporarySoftwareVersion.to_s
                                else
                                    availableVersion = @version
                                end
                            end
                        end
                    end
                end

                if lessOrEqualComparator
                    temporaryAvailableVersion = SemanticVersion.parse(@version.tr("><=",""))

                    Ism.softwares.each do |software|
                        if @name == software.name
                            temporaryVersion = SemanticVersion.parse(@version.tr("><=",""))

                            software.versions.each do |information|
                                temporarySoftwareVersion = SemanticVersion.parse(information.version.tr("><=",""))
                                if temporaryVersion >= temporarySoftwareVersion && temporaryAvailableVersion >= temporarySoftwareVersion
                                    availableVersion = temporarySoftwareVersion.to_s
                                else
                                    availableVersion = @version
                                end
                            end
                        end
                    end
                end
            end

            return availableVersion
        end

        def getDependencies : Array(ISM::SoftwareDependency)
            dependencies = Array(ISM::SoftwareDependency).new

            Ism.softwares.each do |software|

                if software.name == @name

                    software.versions.each do |softwareVersion|

                        if version == softwareVersion.version
                            dependencies = softwareVersion.dependencies
                            break
                        end

                    end

                end
            end

            return dependencies
        end

        def getInformation : ISM::SoftwareInformation
            dependencyInformation = ISM::SoftwareInformation.new

            Ism.softwares.each do |software|

                if software.name == @name

                    software.versions.each do |softwareVersion|

                        if version == softwareVersion.version
                            dependencyInformation = softwareVersion
                            break
                        end

                    end

                end
            end

            return dependencyInformation
        end

        def == (other : ISM::SoftwareDependency) : Bool
            return @name == other.name &&
            version == other.version &&
            @options == other.options
        end

    end

end

Thanks a lot, with your advices and just you answer, this helped me to solve the problem