Hello. Don't you think that Object constructors are too long?
In C code we have:
typedef struct Cricket
{
const char *team1;
const char *team2;
const char *ground;
int result;
} Cricket;
Cricket match[4] = {
{"IND","AUS","PUNE",1},
{"IND","PAK","NAGPUR",1},
{"IND","NZ","MUMBAI",0},
{"IND","SA","DELHI",1}
};
The same in Nim is:
var
arc: array[3,Cricket] =
[Cricket(teama:"One",teamb:"Two",ground:"Meera",result:0),
Cricket(teama:"BOne",teamb:"BTwo",ground:"BMeera",result:1),
Cricket(teama:"COne",teamb:"CTwo",ground:"CMeera",result:1)]
What is the reason to write every time field names. It is defined already and the order of fields is defined too.
May be it is possible to do:
var
arc: array[3,Cricket] =
[("One","Two","Meera",0),
("BOne","BTwo","BMeera",1),
("COne","CTwo","CMeera",1)]
Or something like so...
Thanks
type
Cricket = tuple
team1: string
team2: string
ground: string
result: int
var match: array[4, Cricket] = [("IND", "PAK", "NAGPUR", 1), ("IND", "PAK", "NAGPUR", 1),
("IND", "NZ", "MUMBAI", 0), ("IND", "SA", "DELHI", 1)]
Seems to work fine, but it fails for cstring or cint unfortinatelly.
To make it work again, you may use
type
Cricket = tuple
team1: cstring
team2: string
ground: string
result: int
var match: array[4, Cricket] = [("IND".cstring, "PAK", "NAGPUR", 1), ("IND".cstring, "PAK", "NAGPUR", 1),
("IND".cstring, "NZ", "MUMBAI", 0), ("IND".cstring, "SA", "DELHI", 1)]
Yes, it's possible to construct tuples in that way.
What is the reason to construct objects in different way?
And another question: do tuples wrap c structs? I cann't test it right now.
Nim tuple should be fine for wrapping C struct. I think both are very similar, while Nim objects can contain additional information.
See reply of filwit for details: http://forum.nim-lang.org/t/1908
I agree with you. But why is there too big syntax to make instances of objects?
What is the reason?
The reason is that you may want to omit elements or provide them in a different order; code shouldn't break because you're reordering object fields. If you want to save typing, use a template or compile-time procedure, such as:
type Cricket = object
teama, teamb: string
ground: string
result: int
template C(a, b, g: string, r: int): Cricket =
Cricket(teama: a, teamb: b, ground: g, result: r)
var
arc: array[3,Cricket] = [
C("One", "Two", "Meera", 0),
C("BOne", "BTwo", "BMeera", 1),
C("COne", "CTwo", "CMeera", 1)
]
Hello again. About wrap c with tuples.
This code works:
type
Cricket {.importc,
header: "links.h".} = object
teama {.importc: "_team1".}: cstring
teamb {.importc: "_team2".}: cstring
ground {.importc.} : cstring
result {.importc.} : cint
But this does not:
type
Cricket {.importc,
header: "links.h".} = tuple
teama {.importc: "_team1".}: cstring
teamb {.importc: "_team2".}: cstring
ground {.importc.} : cstring
result {.importc.} : cint
because pragma {.importc.} in field is impossible.
This code:
type
Cricket {.importc,
header: "links.h".} = tuple
teama: cstring
teamb: cstring
ground: cstring
result: cint
when pass object instances to c code gives error:
error: no member named 'Field0' in 'struct Cricket'
error: no member named 'Field1' in 'struct Cricket'
...and so on
How to wrap c structs with tuples, please?
The reason is that you may want to omit elements or provide them in a different order; code shouldn't break because you're reordering object fields.
That is exception from common rule. How often do you reorder fields when instantiate object? This cases may be wrapped with construction functions.
As variant. It may be possible to reorder object fields with their names. But by default: instantiate object like tuple.
I believe that:
var match: array[4, Cricket] = [("IND".cstring, "PAK", "NAGPUR", 1), ("IND".cstring, "PAK", "NAGPUR", 1),
("IND".cstring, "NZ", "MUMBAI", 0), ("IND".cstring, "SA", "DELHI", 1)]
can be
var match: array[4, Cricket] = [("IND".cstring, "PAK", "NAGPUR", 1), ("IND", "PAK", "NAGPUR", 1),
("IND", "NZ", "MUMBAI", 0), ("IND", "SA", "DELHI", 1)]
EDIT: Forget that! Just tested... my code is
var crownData*: array[0..21, cstring] = [
# columns rows colors chars-per-pixel
cstring("16 16 5 1"),
" c #a0a0a0",
". c #808080",
"- c #707070",
"+ c #606060",
"* c #505050",
# pixels #
" ",
" ",
" - -- - ",
" .*+++**+++*. ",
"-.************.-",
"+*****++++*****+",
"-***+-. .-+***-",
".++. .. .++.",
" ++- -**- -++ ",
" +.+++*..*+++.+ ",
" .+ .- -. +. ",
" +. .+ ",
" *+. .+* ",
" -++++++- ",
" ",
" "
]
But it seems not to work in your case.
karatin: That is exception from common rule. How often do you reorder fields when instantiate object?
It does not matter how often you do it. The point is that if you do it (or introduce new fields, or move a field to a supertype), your existing code shouldn't break. Furthermore, partial object constructors are frequently used for partial initialization; as an example, Obj() will create a fresh object, and you may see things like:
type Person = ref object
name, first, last: string
proc newPerson(first, last: string): Person =
result = Person(first: first, last: last)
result.name = first & " " & last
Tuple types exist for when you want your type's layout to reflect its semantic structure and are willing to guarantee that indefinitely.
That said, for interoperability with external C code it is probably still better to use an object type (because layout assumptions may not carry over between the two languages).
karatin: This cases may be wrapped with construction functions.
In Nim, it's the other way around; if you want easy initialization of constant structures, then you can write convenience compile-time procedures for that. Heck, you can even build a hash table at compile time if you want:
import tables
const ht = {"a": 1, "b": 2, "c": 3}.toTable
karatin: How to wrap c structs with tuples, please?
Your code seems to work for me (to the extent that I have been able to reconstruct it). Without a longer excerpt that demonstrates the error, I cannot tell what's wrong.
Yeah I know other languages require named parameters with the argument that it prevents bugs/errors.. but I don't find that convincing. I have written ObjC code and that feature is very annoying. IMO (and as you brought up) the better solution is in IDE tools. Proper refactoring tools can [1] prevent the same bugs/errors, and [2] automate the process of changing parameter arrangement throughout the entire codebase whenever the definition is modified. That's much more practical on both accounts (calling is easier and modifying is easier).
But aside from the abstract, I do not think the reason Nim requires named constructor fields has anything to do with these arguments. Nim has them to distinguish between an object constructor and a type conversion. That way objects, tuples, and conversion all have distinct syntax to the parser.. IMO, that is a design mistake, and the concept of type construction should have been associated with type conversion (both implicit and explicit conversion) since they do very similar things (ie, give you an specific type based on some input). The result would be a much more unified syntax all around, eg:
# NOTE: Person could be either a object or tuple here
converter toPerson(name:string): Person =
result.name = name
converter toPerson(name:string, age:int): Person =
result.name = name
result.age = age
let joe: Person = "Joe" # or ("Joe") or Person("Jon")
let bob: Person = "Bob", 35 # or ("Bob", 35) or Person("Bob", 35)
# ---
converter toPerson(dog:Dog): Person =
result.name = dog.name
result.age = dog.age * 7
proc printPerson(p:Person) = echo p
let spot = Dog("Spot", 2)
printPerson(spot) # prints '(name:"Spot", age:14)'
Of course, it's far too late for something like this (and I'm sure others would have arguments for/against it), so I'll refrain from pushing the point and causing pointless discussion over it here. I just think we should be accurate about the reasons for Nim's design on this point, and it's more likely they're rooted Nim's history rather than Nim's philosophy.
@filwit wrote
I just think we should be accurate about the reasons for Nim's design on this point, and it's more likely they're rooted Nim's history rather than Nim's philosophy.
Maybe, but I find @Jehan's arguments thoroughly convincing. The use of a template for object construction should satisfy anyone. Just pretend it's like a C++ constructor if you want.
Your code seems to work for me (to the extent that I have been able to reconstruct it). Without a longer excerpt that demonstrates the error, I cannot tell what's wrong.
I have this code in Nim:
type
Cricket {.pure, final, importc,
header: "links.h".} = tuple
teama {.importc: "team1".}: cstring
teamb {.importc: "team2".}: cstring
ground {.importc.} : cstring
result {.importc.} : cint
## Error: ':' or '=' expected, but found '{.'
Change it to:
type
Cricket {.pure, final, importc,
header: "links.h".} = tuple
teama: cstring
teamb: cstring
ground: cstring
result: cint
proc mkCr(a,b,c:cstring,d:cint): Cricket =
(teama:a,teamb:b,ground:c,result:d)
var
arc: seq[Cricket] =
@[mkCr("One","Two","Meera",0),
mkCr("BOne","BTwo","BMeera",1),
mkCr("COne","CTwo","CMeera",1)]
proc getCricket(i: int): ptr Cricket {.importc, header: "links.h".}
proc setCricket(cr: ptr Cricket; il: int) {.importc, header: "links.h".}
when isMainModule:
echo getCricket(3)[]
setCricket(addr (arc[0]), 3)
## error: no member named 'Field0' in 'struct Cricket'
## result.Field0 = a;
## ~~~~~~ ^
##error: no member named 'Field1' in 'struct Cricket'
## result.Field1 = b;
## ~~~~~~ ^
##error: no member named 'Field2' in 'struct Cricket'
## result.Field2 = c;
## ~~~~~~ ^
##error: no member named 'Field3' in 'struct Cricket'
## result.Field3 = d;
## ~~~~~~ ^
##
C code is:
typedef struct Cricket
{
const char *team1;
const char *team2;
const char *ground;
int result;
} Cricket;
Cricket* getCricket(int i);
void setCricket(Cricket* cr, int il);
Cricket* getCricket(int i)
{
Cricket *ptr = &match[i];
printf("\nMatch : %d",i);
printf("\n%s Vs %s",ptr->team1,ptr->team2);
printf("\nPlace : %s\n",ptr->ground);
if(match[0].result == 1)
printf("nWinner : %s\n",ptr->team1);
else
printf("nWinner : %s\n",ptr->team1);
printf("\n");
return ptr;
}
void setCricket(Cricket* cr, int il)
{
Cricket *ptr = cr; // By default match[0]
for(int i=0;i<il;i++)
{
printf("\nMatch : %d",i+1);
printf("\n%s Vs %s",ptr->team1,ptr->team2);
printf("\nPlace : %s\n",ptr->ground);
if(match[i].result == 1)
printf("nWinner : %s",ptr->team1);
else
printf("nWinner : %s",ptr->team1);
printf("\n");
// Move Pointer to next structure element
ptr++;
}
}
## there is initialization of Cricket var to test
The same with object in Nim works well:
type
Cricket {.pure, final, importc,
header: "links.h".} = object
teama {.importc: "team1".}: cstring
teamb {.importc: "team2".}: cstring
ground {.importc.} : cstring
result {.importc.} : cint
## No errors
That's why I answered about wrapping c struct with Nim tuple.
Constructors in Go:
type person struct {
name string
age int
}
func main() {
fmt.Println(person{"Bob", 20})
fmt.Println(person{name: "Alice", age: 30})
fmt.Println(person{name: "Fred"})
s := person{"Sean", 50}
fmt.Println(s)
}
It is like C code. That is much clear code syntax. Field names in Nim object constructors make it much longer.
Language can implement both versions of constructors: with field names (strong fields order) and without (soft fields order, you can change it in constructor).
Nim is elegant. So why make it hard.
That's why I answered about wrapping c struct with Nim tuple.
Oh, now I see it. The problem is that the code generator uses a canonicalized form for code generation (presumably to not have to worry about field renamings among tuples with identical structures), so you do have to use object if you want to use the data as a non-opaque structure.
Nim is elegant. So why make it hard.
Okay, let's get started. First of all, introducing object constructors of the form Obj(x) instead of just Obj(x: x) would introduce ambiguities, in particular, because Obj(x) is already the syntax for type conversions. You could invent an additional syntax, but then you'd complicate the language, making it arguably less elegant.
Writing up lots of initializers in program code isn't something that you do a lot (if you have a LOT of data, you probably want to keep it in a format that's more easily editable and generate code from that), and where you do it, Nim has metaprogramming facilities that make this easier, can add compile-time sanity checks, and so forth. If you absolutely need it and it bugs you enough, you can even write a {} macro so that the following works:
var match = [
Cricket {"IND","AUS","PUNE",1},
Cricket {"IND","PAK","NAGPUR",1},
Cricket {"IND","NZ","MUMBAI",0},
Cricket {"IND","SA","DELHI",1}
]
Nim's metaprogramming has enough flexibility that not every little thing needs to be hardcoded into the language.
In any event: I'm not the language author. The only reason I'm posting in this thread is to offer some help and possibly insights. If you want to see the language changed, you need to talk to Araq.
Thanks, Jehan.
I do not mean language changes. I can not understand macros enough yet. Could you show some example how to implement this:
var match = [
Cricket {"IND","AUS","PUNE",1},
Cricket {"IND","PAK","NAGPUR",1},
Cricket {"IND","NZ","MUMBAI",0},
Cricket {"IND","SA","DELHI",1}
]
That will be useful for everyone. Thanks
And this too:
var match: array[4,Cricket] = [
{"IND","AUS","PUNE",1},
{"IND","PAK","NAGPUR",1},
{"IND","NZ","MUMBAI",0},
{"IND","SA","DELHI",1}
]
In Nim {} is used to declare Set. So may be this variant of previous:
var match: array[4,Cricket] = [
("IND","AUS","PUNE",1),
("IND","PAK","NAGPUR",1),
("IND","NZ","MUMBAI",0),
("IND","SA","DELHI",1)
]
That is like in Go:
var a [2]person = [2]person{{"Alia",12},{"Inna",10}}
## or
a := [2]person{{"Alia",12},{"Inna",10}}
@karatin
Could you show some example how to implement this
import macros
# `macros` has a tool called `dumptree` which will output the AST
# for any block of Nim code. It's very useful for writing macros..
# The idea is that you write in the code you want to produce, then
# look at the output and reconstruct that AST via a macro. Example:
dumptree:
Foo(a:123, b:"four") # prints...
# ObjConstr
# Ident !"Foo"
# ExprColonExpr
# Ident !"a"
# IntLit 123
# ExprColonExpr
# Ident !"b"
# StrLit four
# ---
macro `{}`(T:typedesc, params:varargs[typed]): untyped =
## This macro converts a type and a variable array of
## expressions into an object constructor for that type.
let objType = getType(T)[1] # extracts the 'type name' symbol from `T`
let objRecs = getType(objType)[1] # extracts the 'object records' node from `T`
assert objRecs.len == params.len # ensure number of records match incoming parameters
# make the result an 'ObjConstr' node with `T` as it's first child
result = newNimNode(nnkObjConstr).add(objType)
# iterate through T's records and add an 'ExprColonExpr' node for each record/param pair
for i in 0 .. <objRecs.len:
let rec = objRecs[i] # current record
let par = params[i] # current parameter
assert sameType(rec, par) # ensure record and parameter are the same type
result.add newNimNode(nnkExprColonExpr).add(rec, par)
# ---
type
Foo = object
a: int
b: string
let f = Foo { 123, "four" }
echo f # prints `(a: 123, b: four)`