2014-11-24 3 views
2

Я думаю, что я понимаю, что здесь происходит, но я бы очень признателен, если кто-то проливает свет на то, почему это происходит.Mutable опорные ячейки не работают со статическими элементами

В принципе (и я кодирую на лету здесь, чтобы дать простейшую возможную иллюстрацию) У меня есть класс S, который, между прочим, поддерживает таблицу символов, связывающую имена с объектами класса. Так, в классе я определяю:

static member (names:Map<string,S> ref) = ref Map.empty 

..., а затем позже, также в S, я определяю:

member this.addSymbol (table: Map<string,S> ref) (name:string) 
    table := (!table).Add(name, this) 
    this 

Я называю это из других, таким образом:

ignore (s.addSymbol S.names "newname") 
... 

Внутри S.addSymbol Я вижу новый ключ/значение, добавляемое в таблицу, но S.names не обновляется. Если, вместо этого, я называю addSymbol так:

ignore (
    let tmp = S.names 
    s.addSymbol tmp "newName" 
) 

, то я могу видеть TMP обновляется с новой пары ключ/значение, но S.names еще не обновляется.

Вот что я думаю, что знаю: нет ничего плохого в рефлекторе - это исправление референта правильно как внутри, так и снаружи функции. Но, похоже, что-то странное в статическом члене - это похоже на то, что он просто не передавал мне один и тот же статический ref 'каждый раз, он копирует' a, а затем создает новый ref для этого.

Есть ли у меня это право? Если да, то почему он это делает? И что я могу сделать, чтобы заставить себя вести себя более разумно?

Заранее спасибо.

+0

@ Vandroiy поставил диагноз вашей непосредственной проблемы, но вот еще одна мысль: если вы собираетесь мутировать коллекцию, то часто имеет смысл использовать стандартные изменчивые коллекции .NET, а не ссылку, обернутую неизменной коллекцией, которая вы обновляетесь. В вашем случае это будет означать использование 'System.Collections.Generic.Dictionary <_,_>' вместо 'Map <_,_> ref'. – kvb

+0

Я действительно рассматривал использование коллекции .net, но решил против этого, частично по архитектурным причинам (у меня есть много таблиц, которые нужно синхронизировать, поэтому я не хотел раскрывать метод Add), но также и потому, что мне показалось, что это коп-аут - я только начинаю F #, и я пытаюсь сделать что-то «правильным», а не просто для C# -alike. Я отмечаю из руководства, что нужно использовать объекты для большого материала и функции для тонких вещей. Не знаю, в чем мой вопрос, но я думаю, что я чувствую недостаток информированного мнения :-). –

ответ

6

Статический член names является объект, а не статическое значение. Это внутренняя функция, которая создает новую карту каждый раз, когда она вызывается.

Если значение должно быть в классе, используйте static let, чтобы определить его внутри класса. Таким образом, эталонная ячейка создается только один раз.

type S() = 
    static let names = ref Map.empty : Map<string, S> ref 
    static member Names = names 

S.Names := ["Don", S()] |> Map.ofList 
S.Names // gives map with one element 

Oftentimes, класс не раскрывает саму эталонную камеру, а члены читать, добавлять или удалять имена. Затем Names вернется !names - или имена были бы простой изменяемые вместо ссылки на ячейку - и другие методы, такие как AddName бы изменить значение. Если нет необходимости в такой инкапсуляции, см. Комментарий kvb о том, как сократить этот пример до одного члена. (Спасибо за подсказку!)


Альтернатива: Обычно, это тот случай, в котором используются F # модулей. Просто определить

let names = ref Map.empty : Map<string, S> ref 

или

let mutable names = Map.empty : Map<string, S> 

в модуле, с дополнительными ограничениями доступа или инкапсуляции в соответствии с требованиями.

+1

Вы также можете использовать «автоматически реализованное свойство»: 'static member val names: Map ref = ref Map.empty' – kvb

+0

Спасибо, Вандрой - это было очень четкое объяснение. Жаль, по вашему первому предложению, что это дополнительное имя. Мне больше нравится второй ответ. Но ответ kvb, вероятно, является самым кратким способом достижения того, что я пытался сделать, и я только что многому научился о том, что делает val, поэтому спасибо вам тоже. Однако мне приходит в голову, что данный статический член каждый раз создает новый объект, а val конкретно означает, что файл создается один раз, во время построения, что неупорядоченный статический член является довольно бесполезной вещью - если я не пропущу что нибудь? –

+0

@Jules Это вопрос инкапсуляции. Часто у вас есть частные значения для хранения исходной структуры данных, а затем для разрешенных операций с ними. «Имена» могут разыменовывать значение, так что он только считывает карту, но не может ее изменить. Затем члены типа «AddName» могут выполнять определенные, четко определенные операции. Это зависит от желаемых требований безопасности или ограничений по изменяемой величине. (Я отредактировал ответ, чтобы указать на комментарий kvb и упомянуть об этом.) – Vandroiy

Смежные вопросы