#f#
#f#
Вопрос:
У меня есть объект, который может быть аккуратно описан с помощью различаемого объединения. Дерево, которое оно представляет, обладает некоторыми свойствами, которые могут быть легко обновлены при изменении дерева (но остаются неизменяемыми), но которые относительно дорого пересчитывать.
Я хотел бы сохранить эти свойства вместе с объектом в виде кэшированных значений, но я не хочу помещать их в каждый из выделенных вариантов объединения, поэтому я решил, что здесь подойдет переменная-член.
Тогда возникает вопрос, как мне изменить значение элемента (когда я изменяю дерево) без изменения фактического объекта? Я знаю, что мог бы модифицировать дерево, а затем мутировать эту копию, не нарушая чистоты, но мне кажется, что это неправильный способ сделать это. Для меня имело бы смысл, если бы существовал какой-то заранее определенный способ изменения свойства, но так, чтобы результатом операции был новый объект с измененным этим свойством.
Чтобы пояснить, когда я говорю изменить, я имею в виду сделать это функциональным способом. Like (::) «добавляет» к началу списка. Я не уверен, какая здесь правильная терминология.
Комментарии:
1. Может быть, вы можете привести репрезентативный пример кода того, что вы пытаетесь сделать? Из вашего описания звучит так, как будто вы хотите съесть торт и тоже съесть торт…
2. Поскольку мой фактический вариант использования немного запутан, и я все еще выясняю это, вот упрощенный пример. Допустим, у вас есть дробь, и вы решили представить ее в виде набора целых чисел. Теперь давайте представим, что вычисление десятичного представления дроби является очень дорогостоящим. У меня может быть функция addOne, которая просто выполняет (x, y) -> (x y, y), и поскольку я знаю, что делает addOne, я также знаю, что мне просто нужно ( 1) десятичное представление. Но поскольку мой тип — это просто кортеж, это значение негде кэшировать, поэтому я вынужден каждый раз пересчитывать, и я не могу использовать ярлык ( 1).
3. Поскольку мне нравится просто использовать дробь в виде кортежа вместо чего-то вроде ((x, y), десятичное число), я решаю сохранить кэш как переменную-член. Но тогда, как мне реализовать мою функцию addOne, чтобы она изменяла не только кортеж, но и кэш элементов?
4. Хорошо, тогда позвольте мне привести вам пример кода, а вы скажите мне, насколько близко я был
5. почему бы не использовать синтаксис записи — это выглядит как
let modified a = {a with b=c}
Ответ №1:
F # на самом деле имеет синтаксис для копирования и обновления записей.
Синтаксис выглядит следующим образом:
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
(пример со страницы записей MSDN — http://msdn.microsoft.com/en-us/library/dd233184.aspx).
Это позволяет сделать тип записи неизменяемым и сохранить большую ее часть, в то время как обновляется только небольшая часть.
Комментарии:
1. синтаксис — это то, что вы ищете, но будьте осторожны, если хотите использовать запись для хранения кэшированных значений, которые у вас могут быть, а могут и не быть в данный момент, потому что это повлияет на сравнение структурного равенства в вашем типе.
2. Я понимаю. В моем случае это не будет проблемой.
3. @JohnPalmer Не могли бы вы немного подробнее остановиться на «большая часть этого будет сохранена»? Я предполагаю, что это означает, что копируются не все значения, а только структура, которая содержит указатели, верно? Это означает, что обновление одного поля в большой записи происходит медленнее, чем обновление одного в маленькой записи, верно?
4. @LukaHorvat — Я полагаю, что вы правы, но я не тестировал.
Ответ №2:
На самом деле самым простым способом добиться этого было бы так или иначе перенести «кэшированное» значение, прикрепленное к DU (как часть case). Я мог бы придумать несколько способов реализовать это, я просто приведу вам один, где есть отдельные случаи для кэшированного и некэшированного режимов:
type Fraction =
| Frac of int * int
| CachedFrac of (int * int) * decimal
member this.AsFrac =
match this with
| Frac _ -> this
| CachedFrac (tup, _) -> Frac tup
Совершенно другим вариантом было бы сохранить кэшированные значения в отдельном словаре, это то, что имеет смысл, если все, что вы хотите сделать, это сэкономить некоторое время на их пересчете.
module FracCache =
let cache = System.Collections.Generic.Dictionary<Fraction, decimal>()
let modify (oldFrac: Fraction) (newFrac: Fraction) =
cache.[newFrac] <- cache.[oldFrac] 1 // need to check if oldFrac has a cached value as well.
В принципе, то, что memoize даст вам, плюс у вас будет больше контроля над ним.