Como você declara os valores de uma entrada de dicionário como mutável?
-
05-07-2019 - |
Pergunta
O Google produz muito exemplo de adição e exclusão de entradas em um dicionário F# (ou outra coleção). Mas não vejo exemplos para o equivalente a
myDict["Key"] = MyValue;
eu tentei
myDict.["Key"] <- MyValue
Eu também tentei declarar o dicionário como
Dictionary<string, mutable string>
Além disso, várias variantes sobre isso. No entanto, ainda não atingi a combinação correta ... se for é realmente possível em f#.
Editar: o código ofensivo é:
type Config(?fileName : string) =
let fileName = defaultArg fileName @"C:\path\myConfigs.ini"
static let settings =
dict[ "Setting1", "1";
"Setting2", "2";
"Debug", "0";
"State", "Disarray";]
let settingRegex = new Regex(@"\s*(?<key>([^;#=]*[^;#= ]))\s*=\s*(?<value>([^;#]*[^;# ]))")
do File.ReadAllLines(fileName)
|> Seq.map(fun line -> settingRegex.Match(line))
|> Seq.filter(fun mtch -> mtch.Success)
|> Seq.iter(fun mtch -> settings.[mtch.Groups.Item("key").Value] <- mtch.Groups.Item("value").Value)
O erro que estou recebendo é:
System.NotSupportedException: This value may not be mutated
at Microsoft.FSharp.Core.ExtraTopLevelOperators.dict@37-2.set_Item(K key, V value)
at <StartupCode$FSI_0036>.$FSI_0036_Config.$ctor@25-6.Invoke(Match mtch)
at Microsoft.FSharp.Collections.SeqModule.iter[T](FastFunc`2 action, IEnumerable`1 sequence)
at FSI_0036.Utilities.Config..ctor(Option`1 fileName)
at <StartupCode$FSI_0041>.$FSI_0041.main@()
stopped due to error
Solução
F# tem duas estruturas de dados associativas comuns:
Aquele que você está mais acostumado, o dicionário mutável que herda que é a presença na BCL e usa uma hashtable sob o capô.
let dict = new System.Collections.Generic.Dictionary<string,int>()
dict.["everything"] <- 42
O outro é conhecido como Mapa e é, no estilo funcional comum, imutável e implementado com árvores binárias.
Em vez de operações que mudariam um dicionário, os mapas fornecem operações que retornam um novo mapa, que é o resultado de qualquer alteração solicitada. Em muitos casos, sob o capô, não há necessidade de criar uma cópia totalmente nova de todo o mapa; portanto, as partes que podem ser compartilhadas normalmente são. Por exemplo:
let withDouglasAdams = Map.add "everything" 42 Map.empty
O valor que withDouglasAdams
permanecerá para sempre como uma associação de "tudo" para 42. Então, se você mais tarde o fizer:
let soLong = Map.remove "everything" withDouglasAdams
Então o efeito dessa 'remoção' é apenas visível através do soLong
valor.
O mapa de F#é, como mencionado, implementado como uma árvore binária. A pesquisa é, portanto, O (log n), enquanto um dicionário (bem comportado) deve ser O (1). Na prática, um dicionário baseado em hash tenderá a superar a árvore baseada em quase todos os simples (número baixo de elementos, baixa probabilidade de colisão), como tal é comumente usado. Dito isto, o aspecto imutável do mapa pode permitir que você o use em situações em que o dicionário exigiria bloqueio mais complexo ou escreveria mais código 'elegante' com menos efeitos colaterais e, portanto, continua sendo uma alternativa útil.
No entanto, essa não é a fonte do seu problema. O dicto 'Operador' retorna uma explicidade imutável IDictionary<K,T>
implementação (apesar de não indicar isso em sua documentação).
A partir de fslib-extra-pervasives.fs (Observe também o uso de opções nas chaves):
let dict l =
// Use a dictionary (this requires hashing and equality on the key type)
// Wrap keys in an Some(_) option in case they are null
// (when System.Collections.Generic.Dictionary fails). Sad but true.
let t = new Dictionary<Option<_>,_>(HashIdentity.Structural)
for (k,v) in l do
t.[Some(k)] <- v
let d = (t :> IDictionary<_,_>)
let c = (t :> ICollection<_>)
let ieg = (t :> IEnumerable<_>)
let ie = (t :> System.Collections.IEnumerable)
// Give a read-only view of the dictionary
{ new IDictionary<'key, 'a> with
member s.Item
with get x = d.[Some(x)]
and set (x,v) = raise (NotSupportedException(
"This value may not be mutated"))
...
Outras dicas
Que erro você recebe? Eu tentei o seguinte e ele compila muito bem
let map = new System.Collections.Generic.Dictionary<string,int>()
map.["foo"] <- 42
EDITAR Verifique se esse código também funcionou muito bem.