#pharo
#pharo
Вопрос:
normalize: sum
| strategy normalizingSum |
strategy := sum collect: [ :each | each max: 0.0 ].
normalizingSum := strategy sum.
^strategy collect: [ :each |
normalizingSum strictlyPositive
ifTrue: [ each / normalizingSum ]
ifFalse: [ 1.0 / Action subclasses size ]
]
Я хочу сделать приведенный выше метод экземпляра вместо того, чтобы явно передавать в него sum. Проблема не столько в том, что я не могу создать метод или не могу поместить его в Array
класс. Просто, изучая функциональные языки, я обнаруживаю, что не понимаю, чего именно от меня здесь ожидают.
Насколько я могу судить, по соглашению большинство методов Pharo работают непосредственно с экземплярами, в то время как в функциональных языках можно было бы определить функцию, аналогичную статическим методам в C # / Java.
Один из вариантов — поместить метод в метакласс в Pharo, но синтаксис Pharo плохо подходит для этого стиля программирования. Например, в нем нет оператора pipe, который объясняет сильный крен в сторону методов экземпляра во всем библиотечном коде, который я наблюдал.
Расширение стандартного библиотечного класса путем непосредственного помещения в него метода кажется мне немного неправильным. Когда придет время вносить изменения в Github, как именно все будет происходить? Кажется, что если поместить это непосредственно в Array
класс, то в конечном итоге это превратится в кошмар управления версиями.
Другой вариант — наследовать от Array
. Это могло бы подойти для проблемы, которой я занимаюсь сейчас, но позже я хочу решить другую проблему и не хочу делиться реализациями.
Должен ли я поместить это в признак и добавить признак к Array
?
Ответ №1:
Проблема здесь возникает из-за того факта, что мы рассматриваем только одно сообщение, на которое должны ответить ваши коллекции: #normalized
. В вашем коде коллекция sum
— это объект, который необходимо нормализовать. Поэтому было бы заманчиво сказать sum normalized
. Однако я бы не рекомендовал добавлять #normalized
в Array
(или Collection
), потому что логика вашей конкретной нормализации не присуща Array
: она зависит от Action
, которая выглядит как концепция, имеющая смысл в контексте вашего проекта.
Это не означает, что вам никогда не следует расширять Array
свои материалы. Это означает только то, что если ваше расширение не является встроенным, такое решение потребует больше размышлений.
В этом случае я бы предложил проанализировать, имеют ли коллекции, подобные sum
вашему проекту, какое-либо другое поведение, присущее им самим. В таком случае я бы рассмотрел возможность создания класса для их представления и добавления в этот класс сообщений, таких как #normalized
, плюс любых других, имеющих отношение к этим объектам.
Просто в качестве примера давайте предположим, что у вашего класса есть имя StrategyCollection
. Вы могли бы определить в нем
strategy
^sum collect: [:each | each max: 0.0].
где sum
теперь находится ivar вашего класса. Тогда вы могли бы определить
normalized
strategy := self strategy.
normalizingSum := strategy sum.
^strategy collect: [:each |
normalizingSum strictlyPositive
ifTrue: [each / normalizingSum]
ifFalse: [1.0 / Action subclasses size]]
которые, кстати, можно было бы переписать как
normalized
| strategy normalizingSum |
strategy := self strategy.
normalizingSum := strategy sum.
^normalizingSum strictlyPositive
ifTrue: [strategy collect: [:each | each / normalizingSum]]
ifFalse: [strategy class new: sumstrategy size withAll: 1.0 / Action subclasses size]
Если у вас есть другие стратегии помимо #max: 0
, вы могли бы легко изменить приведенный выше код, чтобы сделать его более общим, используя perform:
или имея специальный подкласс StrategyCollection
, который реализует свою собственную версию #strategy
.
Я бы также предложил добавить метод для выражения Action subclasses size
. Что-то вроде
actionCount
^Action subclasses size
а затем использовать это в #normalized
. Выражение Action subclasses size
хрупкое и, вероятно, будет отображаться в других методах. Например, если завтра вы решите сгруппировать некоторые подклассы Action
в другой абстрактный подкласс, количество подклассов Action
не будет адаптировано к такому рефакторингу. В более общем плане поведение ваших объектов не должно зависеть от того, как вы организуете свой код, поскольку это относится к мета-уровню абстракции.
Ответ №2:
Вам определенно следует сделать это экземпляром Array
или даже лучше Collection
, поскольку ваш код работает со всем, что можно повторять (и имеет номера).
Причина в том, что это более понятно в использовании:
#(3 5 49 3 1) normalized
вместо:
SomeMisteriousThirdParty normalize: #(3 5 49 3 1)
Кроме того, если у вас есть какие-то специальные коллекции или другие классы, они могут определять свои собственные версии normalized
для правильной обработки данных.
Философия Pharo заключается в том, что никто не может создать идеальную среду именно для вашего проекта. Таким образом, легко изменить уже доступные библиотеки в соответствии с вашими потребностями.
Способ сделать это — использовать методы расширения, в которых ваш пакет расширяет какой-либо другой класс с помощью метода. Эта функциональность присутствует в Pharo (и Smalltalk в целом) более десяти лет, именно по этой причине, когда вам нужно расширить другой класс, но внести изменения вместе с вашим кодом.
Раз уж мы затронули тему нормализации, стоит упомянуть, что есть по крайней мере два крупных проекта, которые, скорее всего, нуждаются в нормализации для выполнения того, что они делают. Один из них — Roassal, используемый для визуализации данных, а другой — Polymath, используемый для различных вычислений. Вам может быть полезно взглянуть на то, что они делают.
Комментарии:
1. Способ выполнения методов расширения — это создать метод, скажем, в Array, а затем переместить его в другой пакет, верно? Мне также интересно, как в Pharo обрабатываются конфликты имен для методов расширения.
2. @MarkoGrdinic в целом да. Дополнительная информация: концепция называется «методы расширения». Классически протокол метода должен начинаться с имени пакета, к которому он принадлежит. Но в последней версии Pharo есть браузер классов Calypso , который использует новую модель кода и позволяет определять, к каким пакетам принадлежат методы, не ограничивая вас именем протокола. Конфликты разрешаются каждым средством загрузки независимо, но я ожидаю, что более новый код переопределит существующий