smalltalk block - могу ли я явно установить возвращаемое значение и прекратить выполнение блока? - PullRequest
3 голосов
/ 25 сентября 2011

Возвращаемое значение сообщения #value: при отправке в блок является значением последнего предложения в этом блоке. Таким образом, [ 1 + 2. 3 + 4. ] value оценивается в 7. Я нахожу это трудно использовать иногда. Есть ли способ явно установить возвращаемое значение и прекратить выполнение блока?

Для тренировки попробуйте переписать этот блок, не используя мое воображаемое сообщение #return: и посмотрите, насколько он уродлив. Я должно быть что-то упустил.

[ :one :two |
  one isNil ifTrue: [ two isNil ifTrue: [ self return: nil ] ifFalse: [ self return: true ] ].
  two ifNil: [ self return: false ].

 (one > two)
  ifTrue: [ self return: true ]
  ifFalse: [ (one < two)
              ifTrue: [ self return: false ]
              ifFalse: [ self return: nil ]
            ].
]

РЕДАКТИРОВАТЬ: self return: sth действительно ерунда, но это имеет смысл на некоторые уровень:)

Ответы [ 3 ]

7 голосов
/ 26 сентября 2011

Нет ничего похожего на выражение охраны - blah ifTrue: [^ foo] - внутри блока, потому что ^ является нелокальным возвращением, возвращаемым из метода, вызывающего блок, а не из самого блока.

Большие блоки- как большие вещи - должны быть реорганизованы в меньшие, более понятные / поддающиеся обработке части, но иногда это не всегда возможно.Я имею в виду этот ответ, чтобы предложить варианты вариантов, когда вы не можете действительно упростить обычными способами.

Если ваш блок действительно настолько сложен, и вы не можете сделать его проще (например, разделение ее на части делокализует информацию слишком много), тогда, возможно, вы можете использовать явное возвращаемое значение.В частности, если ваш блок не возвращает nil, вы могли бы сделать что-то вроде

[:one :two | | result |
    result := (one isNil and: [two isNil]) ifTrue: [false].
    result ifNil: ["do one thing, possibly setting result"].
    result]

Если ваш блок может вернуть nil, вам понадобится другое значение Sentinel:

[:one :two | | result marker |
    result := marker := Object new.
    (result == marker) ifTrue: ["do one thing, possibly setting result"].
    result]

Наконец, - и я не решаюсь предложить это - вы могли бы сделать это:

[1 + 2.
thisContext return: 5.
3 + 4] value

, который возвращает 5.

(Проверка того, как это взаимодействует с ^ и встроенными селекторамикак #ifTrue:ifFalse: оставлено в качестве упражнения для читателя.)

2 голосов
/ 26 сентября 2011

Кажется, что ваш код пытается обработать nil как значение infinity при сравнении one и two . Следующий код может быть более читабельным в зависимости от контекста:

a := [:one :two |
    | x y |
    x := one ifNil: [Float infinity].
    y := two ifNil: [Float infinity].
    (x = y) ifTrue: [nil] ifFalse: [x > y]]

Полезная особенность #ifTrue: ifFalse :, #ifNil: ifNotNil: и подобных тестирующих методов заключается в том, что они возвращают значение блока, который оценивается. например (4 > 1) ifTrue: ['greater'] ifFalse: ['not-greater'] оценивается как 'больше' . Эта функция часто позволяет возвращать значение из вложенного блока в хвостовой позиции.

Когда код внутри блока становится слишком сложным, я предлагаю вашему рефакторингу передать его в метод. Но посмотрите ответ Фрэнка для обходных путей.

Edit:

Как указано в комментариях, код выше предполагает цифры. Я также придумал что-то, что работает с другими сопоставимыми объектами:

a:=
[ :one :two |
  true caseOf: {
    [one = two]->[nil].
    [one isNil]->[true].
    [two isNil]->[false]
  } otherwise: [one>two]]

Эта #caseOf: конструкция редко используется, но она, безусловно, лучше, чем thisContext return:

1 голос
/ 18 июля 2012

Вы хотели бы сделать перерыв, продолжить, выйти ... Обычный способ управления потоком в Smalltalk - с помощью блоков. Таким образом, одно забавное решение состоит в том, чтобы использовать вспомогательный метод с возвращаемым значением Block для прерывания потока, как описано здесь .

Object>>exitThru: aBlock
    ^aBlock value: [:result | ^result]

Теперь давайте посмотрим, как его использовать:

| aBlock |
aBlock :=   [ :one :two |
    self exitThru: [:exit |
        one isNil ifTrue: [ two isNil ifTrue: [exit value: nil ] ifFalse: [ exit value: true ] ].
        two isNil ifTrue: [ exit value: false ].
        one > two ifTrue: [ exit value: true ].
        one < two ifTrue: [ exit value: false ].
        exit value: nil] ].

#(('abc' nil) (nil nil) (nil 'def') ('y' 'abc') ('y' 'y') ('y' 'z'))
    collect:
        [:pair |
        aBlock value: pair first value: pair last ]
-> #(false nil true true nil false)

РЕДАКТИРОВАТЬ моя первая версия была излишне сложной, не могу вспомнить, что привело меня к дополнительному косвенному указанию:

| aBlock |
aBlock :=  [:wrapOne :wrapTwo |
    self exitThru: [:exit |
        [ :one :two |
        one isNil ifTrue: [ two isNil ifTrue: [exit value: nil ] ifFalse: [ exit value: true ] ].
        two isNil ifTrue: [ exit value: false ].
        one > two ifTrue: [ exit value: true ].
        one < two ifTrue: [ exit value: false ].
        exit value: nil ]
            value: wrapOne value: wrapTwo ] ].

Что ж, более забавный, чем полезный, я надеюсь, вы найдете более простой и выразительный способ написания кода.

...