{"action":"created","issue":{"url":"https://api.github.com/repos/manastech/crystal/issues/1462","labels_url":"https://api.github.com/repos/manastech/crystal/issues/1462/labels{/name}","comments_url":"https://api.github.com/repos/manastech/crystal/issues/1462/comments","events_url":"https://api.github.com/repos/manastech/crystal/issues/1462/events","html_url":"https://github.com/manastech/crystal/issues/1462","id":106216625,"number":1462,"title":"Proposal: Allow overloading +=, -=, *=, /=, %=, &=, |=, ^= for structs","user":{"login":"lbguilherme","id":546954,"avatar_url":"https://avatars.githubusercontent.com/u/546954?v=3","gravatar_id":"","url":"https://api.github.com/users/lbguilherme","html_url":"https://github.com/lbguilherme","followers_url":"https://api.github.com/users/lbguilherme/followers","following_url":"https://api.github.com/users/lbguilherme/following{/other_user}","gists_url":"https://api.github.com/users/lbguilherme/gists{/gist_id}","starred_url":"https://api.github.com/users/lbguilherme/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lbguilherme/subscriptions","organizations_url":"https://api.github.com/users/lbguilherme/orgs","repos_url":"https://api.github.com/users/lbguilherme/repos","events_url":"https://api.github.com/users/lbguilherme/events{/privacy}","received_events_url":"https://api.github.com/users/lbguilherme/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/manastech/crystal/labels/RFC","name":"RFC","color":"009800"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2015-09-13T13:21:19Z","updated_at":"2015-09-13T13:31:51Z","closed_at":null,"body":"My proposal is to allow the following construct:\r\n\r\n```crystal\r\nstruct SomeStruct\r\n  def +=(obj)\r\n    # does the sum in-place, changing self\r\n  end\r\nend\r\n```\r\n\r\nThis would allow for better optimization on some numeric classes, specially `BigInt`. Currently this creates a temporary: `big1 += big2`, or even `big1 += 1`. It could be implemented more efficiently. Look:\r\n\r\n```crystal\r\nstruct BigInt\r\n  def +(other : BigInt) # unchanged\r\n    BigInt.new { |mpz| LibGMP.add(mpz, self, other) }\r\n  end\r\n\r\n  def +=(other : BigInt)\r\n    LibGMP.add(self, self, other)\r\n  end\r\n\r\n  def +(other : Int) # unchanged\r\n    if other < 0\r\n      self - other.abs\r\n    else\r\n      BigInt.new { |mpz| LibGMP.add_ui(mpz, self, LibC::ULong.cast(other)) }\r\n    end\r\n  end\r\n\r\n  def +=(other : Int)\r\n    if other < 0\r\n      self -= other.abs\r\n    else\r\n      LibGMP.add_ui(self, self, LibC::ULong.cast(other))\r\n    end\r\n  end\r\nend\r\n```\r\n\r\nI believe this would be a huge optimization for `BigInt` (this is how GMP is meant to be used).\r\n\r\nThis overload would only be possible on `struct`s, not `class`es, to preserve semantics.\r\n\r\n```crystal\r\na = SomeClass.new\r\nb = a   # 'a' and 'b' refer to the same object\r\nb += 1  # 'a' should not be affected, so calling a method here makes no sense\r\na == b  # false\r\n```\r\n\r\n```crystal\r\na = SomeStruct.new\r\nb = a   # 'a' and 'b' do not refer to the same object, copy was made\r\nb += 1  # we can call the method overload here, 'a' will not be affected\r\na == b  # false\r\n```\r\n\r\nAnd they would be callable on `self`, only inside a struct, so the following is possible, but only if the method was defined:\r\n\r\n```crystal\r\nself += 3\r\n# If += is defined, this calls it\r\n# Otherwise, this is a syntax sugar for self = self + 3,\r\n#                                      which makes no sense and should error.\r\n```\r\n\r\n---\r\n\r\nThe main point of this is to allow more library optimizations while still maintaining all the semantics and expressiveness we love of Crystal."},"comment":{"url":"https://api.github.com/repos/manastech/crystal/issues/comments/139873782","html_url":"https://github.com/manastech/crystal/issues/1462#issuecomment-139873782","issue_url":"https://api.github.com/repos/manastech/crystal/issues/1462","id":139873782,"user":{"login":"jhass","id":141294,"avatar_url":"https://avatars.githubusercontent.com/u/141294?v=3","gravatar_id":"","url":"https://api.github.com/users/jhass","html_url":"https://github.com/jhass","followers_url":"https://api.github.com/users/jhass/followers","following_url":"https://api.github.com/users/jhass/following{/other_user}","gists_url":"https://api.github.com/users/jhass/gists{/gist_id}","starred_url":"https://api.github.com/users/jhass/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jhass/subscriptions","organizations_url":"https://api.github.com/users/jhass/orgs","repos_url":"https://api.github.com/users/jhass/repos","events_url":"https://api.github.com/users/jhass/events{/privacy}","received_events_url":"https://api.github.com/users/jhass/received_events","type":"User","site_admin":false},"created_at":"2015-09-13T13:31:51Z","updated_at":"2015-09-13T13:31:51Z","body":"The issue with this is that it's a huge semantic discrepancy. `a <op>= b` is defined and known to be syntax sugar for `a = a <op> b`. `a <op> b` for `+`, `-`, `/´, `*` is known to create a new object.\r\n\r\nConsider this:\r\n\r\n```\r\ndef add_maybe(a, b)\r\n  a += b if [true, false].sample\r\n  a\r\nend\r\n\r\nn = 1\r\nm = 2\r\ns = BigInt.new(1)\r\nt =  BigInt.new(2)\r\n\r\no = add_maybe(n, m)\r\np = add_maybe(s, t)\r\n\r\nn #=> 1\r\nm #=> 2\r\no #=> 1, 3\r\n\r\ns #=> 1, 3\r\nt #=> 2\r\np #=> 1, 3\r\n```\r\n\r\nThat's quite subtle and unexpected. But then introducing own operators for BigInt is ugly too, so I'm not sure about the proper way to solve this, but I think this creates more problems than it solves tbh."}}