Hi all,
Excited to be learning this new language. I have a noob-ish question that has probably been solved before. I'm looking for light-weight data structures in Nim, equivalent to JS's JSON.
I'm looking for the Nim-equivalent of the following JS JSON;
}
]
Moreover, I'd like it to be easy to add properties, just like how it's possible in JSON.
I'd first note that your JavaScript code isn't actually valid. I assume that you meant for the outer structure to use {} (an Object).
Also, I'm a Nim neophyte. Stick around for better answers from more experienced users.
Now that I've got all that out of my system, could you describe more specifically the data you need to represent?
You said that you'd like to be able to add properties. Is that true for all the objects in your structure, or just some? Nim is strongly typed, so it requires a bit more thought when planning your data.
Nim has several ways to represent data, but if you need to add members with arbitrary names, then a table would seem to be what you want. That's probably the most analogous to JavaScript's Object type. You'll find them in the tables module. Though again, it isn't going to be loosey goosey like in JavaScript where you can store any data type anywhere.
You can probably make your data storage somewhat more heterogenous by storing references. Remember, JS objects are actually lightweight references to the actual object, so copies of the entire object aren't made on assignment. To get the same thing, table or object references would need to be used.
Anyway, the more definition you give about your data, the better people will be able to help.
Also, you've mentioned JSON, but your example uses JavaScript object literal syntax. JS developers often confuse the two, but they're distinct entities.
Yeah, it's similar to an event system....a plain config file wouldn't work because the program would like to do callbacks as well.
An easier question might be: what is a lightweight way of representing "packages.json" in nim? e.g. https://github.com/nim-lang/packages/blob/master/packages.json
The strong type system is going to be the biggest difference when deciding your structure. From your link it looks like the Objects in the Array have a consistent set of property names. If no other names need to be represented, I'd use a reference to an object. I'll also assume that no inheritance is needed.
A tuple could also be used instead of an object. In this situation, I don't know if one would be better than the other. Someone can weigh in on that if they wish.
So to replicate what you showed, you could represent the object structure like this:
type
Entry = ref object
name: string
url: string
theMethod: string
tags: seq[string] # A sequence (mutable array) of strings
description: string
license: string
web: string
(Notice that I changed method to theMethod since method is a keyword.)
Again, it assumes that no arbitrary property names need to be added. The representation you see above is static, and will be the same for every instance.
Also, notice I'm using a ref object. This is a reference to the object so that passing one around doesn't make a copy. If we didn't use a ref, I imagine there would be lots of copies made every time we add a new item to the array.
Then you'd simply create a sequence of that object type...
# Create an empty sequence of `Entry`
var items: seq[Entry] = @[]
And assuming you don't need some sort of constructor function, you can just go ahead and add new instances to the sequence.
items.add(Entry(
name: "argument_parser",
url: "git://github.com/gradha/argument_parser/",
theMethod: "git",
tags: @["library", "commandline", "arguments", "switches", "parsing"],
description: "Provides a complex commandline parser",
license: "MIT",
web: "https://github.com/gradha/argument_parser"
))
items.add(Entry(
name: "genieos",
url: "git://github.com/gradha/genieos/",
theMethod: "git",
tags: @["library", "commandline", "sound", "recycle", "os"],
description: "Too awesome procs to be included in nimrod.os module",
license: "MIT",
web: "http://gradha.github.io/genieos/"
))
And of course the code above is adding items individually. You could use the literal syntax if you have an entire list to create at once.
var items = @[Entry(
name: "argument_parser",
url: "git://github.com/gradha/argument_parser/",
theMethod: "git",
tags: @["library", "commandline", "arguments", "switches", "parsing"],
description: "Provides a complex commandline parser",
license: "MIT",
web: "https://github.com/gradha/argument_parser"
),
Entry(
name: "genieos",
url: "git://github.com/gradha/genieos/",
theMethod: "git",
tags: @["library", "commandline", "sound", "recycle", "os"],
description: "Too awesome procs to be included in nimrod.os module",
license: "MIT",
web: "http://gradha.github.io/genieos/"
)
]
Notice that I didn't need to set the type for the items variable using : seq[Entry]. It was inferred by the assignment.
Thanks for that, but I really want a data type where I can just add attributes and delete attributes. Maybe the right name for what I'm looking for is just a property list, as outlined in a plenty of detail here: http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html
There must be some way of using JsonNode / JsonNodeObj...is there a dictionary data t ype? I think what I really would like is something like Python's dictionary data structure...
...I'd like to add that very often it's detrimental to good coding to try to mimick in one language the patterns you'd use in another. That's why I was trying to dig a little deeper to find out more about what you ultimately were doing.
It could be that a slightly different structure would make more sense rather than having a simple, flat list of key/value pairs. I know that makes it easy, and you can get away with it in JS, but in a strongly typed language, it isn't necessarily best.
For example, if all the unknown properties you need to add are always going to be string to string pairs, and the rest are pretty much known, then an object structure like I originally defined may still make more sense. The arbitrary key/value pairs could be in a table nested in the object.
...I'd like to add that very often it's detrimental to good coding to try to mimick in one language the patterns you'd use in another
I see your intention, though the high-level idea I'm trying to mimick is basically Python's dictionary data type. Should that idea be idiomatic to Nim?
Anyway, I did some more searching on the internet and I found that this actually does compile. The key operator being '%'
},
} ]
I don't know what % does there. Did you import a module? Doesn't compile for me.
I don't know Python, but isn't its Dictionary type just a set of key/value pairs? If so, then how is that different from the table in Nim? Aren't its Dictionaries strongly typed as well?
% comes from the json module and is a constructor for json objects from tables. The main difference between Python dictionaries and Nim tables is that Nim is typed, so tables are homogeneous, while Python dictionaries can be heterogeneous.
I second the idea that, while JSON-like structures are common and widespread in untyped languages, they would be a bad fit for typed languages such as Nim
Yeah, I imported json and made use of the % operator. Adding on to what andrea said, an example of Python dictionaries is that you could simply paste the packages.json and it would be valid Python.
andrea, why do you think JSON-like structures would be a bad fit for Nim? It seems to me that JSON-like structures are one of the reasons why high-level languages like Python/Lisp are so expressive and powerful.
@Kasprosian andrea already said it.
It is strong typed vs dynamically typed (aka untyped).
Python and Lisp are dynamically typed. That means a variable can hold a value of any type. a can be first a string and later a float.
This makes it possible to put any type you want into an array or list.
In Nim you need to tell the compiler what type a has and that can't be changed later on. If you have an array or a list the compiler wants that all elements are of the same type. How would it check if the type is correct otherwise. What if element nr 4 suddenly is int whereas all the others are float. You could only create a "float / int" or maybe a typeclass "numbers". But that would exclude strings and objects.
You can often work around this by defining a "type" which holds all the different information you want to represent with it. Like a variant type or something like using "a string for everything".
But basically one reason to use Nim over Python & Lisp is it's strong typing. Which makes is easier to reason about the code and prevents errors.
If you use "anything" which does break this (anonymous pointers + casting for example), you lose a feature of the language.
Thats a bit like the freedom to shot yourself into the foot. Not possible if you don't have a gun. But if you have it will happen eventually :)
Agreed with the others and it's the main thing I've wanted to get across since my first reply. Strongly typed languages take a bit more planning in your data representations... but it really pays off in different ways.
My primary experience had been JavaScript before I started using Go. I found that it was an entirely different exercise in deciding how to treat with data. Having now moved from Go to Nim, even though it has a strong type system, it's very liberating compared to Go since there's just so much more power. I can represent things far more easily and intuitively.
Either way, JSON won't work since you want to store procs and such. Since AFAIK this is meant for use in your app only, JSON doesn't make sense anyway since its really meant for data transmission.
Though I'm not entirely convinced, I will think about the thoughts in this thread. I've spent a lot of time in both statically-typed languages and dynamically-typed languages, having written many web apps, python & shell scripts, as well as kernels and distributed systems. So my opinion is not entirely naive here. I do think there should at least be the option of a fluid Python-like dictionary data structure, mainly for prototyping rapidly.
Thanks for your thoughts guys. Btw, is Araq listening to this thread at all?
Of course one can represent JSON-like data structures in typed languages. In fact, JSON is clearly an algebraic data type, so you can easily represent it -say - in Haskell or Scala. I have mainly experience with Scala, where there is no shortage of libraries to represent JSON, and to certain extents to make working on such structure as seamless as possible.
But still there is some friction. An instance of JsonString will not be an actual string, but rather a wrapper that contains one, which will be usually extracted by pattern matching. Ditto for lists, and so on.
Usually, I have found that almost all structures I need fall into two cases. In the first case, I know all the keys beforehand, so I can make a static structure to hold them (a Scala case class, a Nim object, a Haskell record). In the second case, the set of keys is unknown in advance, but usually in this case the values are uniform - otherwise how would I choose how to treat a value, not knowing anything about the keys? In this latter case I use a typed map structure (a Scala Map[A, B], or a Nim table).
Doing this has proven to be most productive in a typed language. Working with completely dynamic structures may be doable if your language is flexible enough - for instance you will definitely want pattern matching - but I always had the feeling of working against the grain.
Also, declaring your structure beforehand tends to make things clearer. You usually know that the field products is meant to contain a list, and that the elements of this list have the field price, which is a number, but in a dynamic language this will be used implicitly at the time of iterating over products keeping the sum of prices. In a typed language, you can be able to centralize this information.
That said, there can be situations where having to type everything is a burden and keeping things dynamic would be a benefit. This is the reason why dynamic languages have their place, and a situation where I would not use Nim, but Python or Clojure instead