2

I'm using Pharo 5.0. In a couple of cases, I have found a limit or possibly a bug in an existing Pharo class (e.g., something in DBXTalk/Garage, or Regex-Core). I would like to be able to modify a selector or set of selectors that exist(s) in a class outside of my own project and make that part of my package.

I have found several instructions on how to create a new selector in an outside class and move that into my package (e.g., as shown in this tutorial). That's very cool. But in some cases I actually want to modify an existing selector in the outside class and have my copy of that selector override the one in the outside class when I use it. I don't wish to modify the existing 3rd party or Pharo pre-supplied package.

In GNU Smalltalk, I can do that just as a normal part of doing a class extend. For example:

Kernel.MatchingRegexResults extend [
    at: anIndex [
        "My updated version of the 'official' Kernel.MatchingRegexResults#at: selector"
        "This is part of my package so overrides the 'official' version"
        ...
    ]

    foo [
        "My new foo selector"
    ]
]

How can I do this in Pharo 5.0? I've done quite a bit of searching but couldn't find a means of doing it. The words "extend" or "override" do not appear in the Pharo by Example and Deep Into Pharo books.

lurker
  • 56,987
  • 9
  • 69
  • 103
  • 1
    Have you tried the *Move to package...* command? It is available from the right click menu on the selector list. – Leandro Caniglia Jun 12 '16 at 03:33
  • @LeandroCaniglia yes I did. In fact, I mentioned it in my question. The move works great if I want to create a new selector for an existing external class and move that new selector into my package. But it doesn't work for modifying/overriding an existing selector since it will move the implementation of that selector into my package and remove it from the external one. I don't want to remove the overridden selector from the external package, but just wish to override it. – lurker Jun 12 '16 at 03:36
  • 1
    Override means override... you will re-write the method, it will be moved to your package, but it will be changed for all the system. Pharo does not have a module system, so you cannot have different methods with same name in same class... – EstebanLM Jun 12 '16 at 13:18

2 Answers2

3

Please note that, in addition to what Milan Vavra wrote, every method in a protocol named *YourPackage-(something) will belong to the package YourPackage, regardless of the package that the class belongs to. At least in Squeak, there is the convention to put methods overridden like this in the *YourPackage-override protocol. Pharo probably has a similar naming convention. The Move to package feature moves the method to such a "starred" protocol.

However, it is discouraged to use such overrides because you cannot have two packages provide an implementation for the same method simultaneously. Monticello will try to preserve both the original method and the overriding method (see senders of PackageInfo>>isOverrideCategory:), but it's still possible that your override method will be overridden by updates to its original package or you will miss updates to the original method, possibly breaking things.

The "Right Way" is to refactor the original method in the original package to make its behavior more easily customizable.

codefrau
  • 4,583
  • 17
  • 17
JayK
  • 3,006
  • 1
  • 20
  • 26
  • Yes, I understand that. Unfortunately, there are a couple of cases where a package that I require but did not write has a bug or lacks a fundamental capability.In these cases, there's no small work-around but to modify the package. When I do that, I want to be able to track it easily. One example is having a [Capture string in regex replacement](http://stackoverflow.com/questions/37403092/capture-string-in-regex-replacement) (enhancement). Another is in DBXTalk/Garage, it gives an error when an integer attribute is NULL in the database (should return `nil`). – lurker Jun 12 '16 at 10:19
  • Of course, in the short term there is usually no way around it (if you do not wish to send other messages). But I also wanted to point it out for other readers coming here. :-) – JayK Jun 12 '16 at 10:24
  • The starred protocol and what constitutes a package is explained in PBE's chapter that covers Monticello. http://pharo.gforge.inria.fr/PBE1/PBE1ch7.html#x32-1060003 – Milan Vavra Jun 12 '16 at 12:19
2

The code in GNU Smalltalk syntax

Kernel.MatchingRegexResults extend [
    at: anIndex [
        "method body"
    ]
    foo [
        "My new foo selector"
    ]
]

would look like this

!MatchingRegexResults methodsFor: 'protocol'!
at: anIndex 
    "method body"
!
foo
    "My new foo selector"
! !

in a Pharo change set that can be filed in from File Browser.

But please note that in both cases, GNU Smalltalk and Pharo Smalltalk, you are actually replacing the original version of the method in that class.

In GNU Smalltalk you might not be accustomed to saving the image and hence you might think that the extend syntax does not modify the original method in the original class.

Actually it does exactly that.

With GNU Smalltalk you typically start with the same old unmodified image each time you run gst. This is because the default place for the image in GNU Smalltalk is not writable to the normal user. So gst reads the same read-only image each time, you modify it in-memory with class definitions and extensions, your modified image only lives temporarily while your program is running, and is discarded on program exit.

  • I suppose from a change management perspective, the best thing to do is probably to move the modified selector into my package, as @LeandroCaniglia says in his comment, even though it means modifying the existing package to move it. At least then it's easy to keep track of. If I wanted to share this package and someone else tried using it, the modified one in my package then supersedes the one in their standard package? Or does that just depend upon which package was updated in their environment last? – lurker Jun 12 '16 at 10:10
  • 1
    There can be only one version of a method in a class. The most recently loaded method version becomes the current version. The starred protocol only serves to group the extension methods in a different package for System Browser and Monticello Browser. The method remains part of the same class regardless of the package. – Milan Vavra Jun 12 '16 at 12:09
  • Right, but if it's defined in more than one package,which one "wins"? Is it based on order of package load? When I redefine a selector defined package X in my package Y, I understand the Y version replaces it. But if I subsequently load a newer version of package X, does the Y version if the selector get replaced by that newer version? Or does it just get skipped and my Y package version remains? – lurker Jun 12 '16 at 12:25
  • If you file in a change set then all the methods defined in it replace what was in the image. If you use Monticello .mcz file you can click on 'Changes' to see what is going to happen when you load it. In short - yes, what you load replaces what is already there. Which means if you load an extension, the previous version is 'deleted' in the package it was in previously. – Milan Vavra Jun 12 '16 at 12:36
  • Ok, thanks for explaining that. So when needing to make changes in a package outside my own, it's going to require some careful manual management to maintain those particular changes. For example, if a new version of Regex comes along, I have to remember that I have a special version of one of the selectors before I do the upgrade to preserve my version. – lurker Jun 12 '16 at 13:39