29 karma · joined November 11, 2023
f :: { ; print(args) }
Brevity is especially nice for inline/anonymous functions.
You can definitely use args.num, args.type[], and args.indices[] to figure out which optional parameters were passed, but I’ve decided that it’s usually easier to pass a full set of parameters into C and have the scripted wrapper handle the optional params. This is easy in Cicada because of ‘code substitution’ (one of the innovations I’m proudest of and if you’ve seen this elsewhere please let me know!). Example:
callC :: {
mandatoryArgs :: { int, int }
optionalArgs :: { str :: string; str = “default” }
code
mandatoryArgs = args
optionalArgs(), (optionalArgs<<args)() | set default, then allow user to change it
$Cfunction(mandatoryArgs, optionalArgs)
}Then you can call it with or without modifying the optional parameters from their default values.
callC(2, 3) | uses the default string
callC(2, 3; str = “modified param”)
callC() runs its arguments as a function, substituted into the params variable, allowing the arguments to modify params. This is weird and I haven’t seen it elsewhere, but it’s very useful.
I think what you’re suggesting would require the <a, b> syntax to produce a proper iterator type, which it doesn’t currently do. That’s definitely worth considering — then you could attach methods, etc.
Thanks for the suggestion! I’ll think about the best way to fix this..
i :: int | 1 reference
a := @i | 2 references
remove i | 1 reference
The data originally allocated for ‘i’ should persist because its reference count hasn’t hit zero yet.
The safety comes because there is no way to access a pointer address within the scripting language. The main functionality of pointers is replaced by aliases (e.g. a = @b.c, a = @array[2], etc.). The only use of pointers is behind the scenes, e.g. when you write ‘b.c’ there is of course pointer arithmetic behind the scenes to find the data in member ‘b’.
Having said that, it is certainly possible for a C callback routine to store an internal pointer, then on a second callback try to use that pointer after it has fallen out of scope. This is the only use-after-free I can imagine.
There’s actually no ‘free’, but in the (member -> variable data) ontology of Cicada there are indeed a few ways memory can become disused: 1) members can be removed; 2) members can be re-aliased; 3) arrays or lists can be resized. In those conditions the automated/manual collection routines will remove the disused memory, and in no case is there any dangling ‘pointer’ (member or alias) pointing to unallocated memory. Does this answer your question?
I agree that my earlier statement wasn’t quite a complete explanation.
Of course, since it interfaces with C, it’s easy to overwrite memory in the callback functions.
Scripted functions have no set arity, and the same applies to callback C functions. Scripted functions collect their arguments inside an ‘args’ variable. Likewise, each C function has a single ‘argsType’ argument which collects the argument pointers & type info, and there are macros to help unpack them but if you want to do the unpacking manually then the function can be called variadically:
ccInt myCfunction(argsType args)
{ for (int a = 0; a < args.num; a++) printf(“%p\n”, args.p[a]); return 0; }
So all functions are automatically variadic.
It’s good to know that these GC/etc. solutions are even used by the big languages..
The example I gave was strange and I’ll have to change it. Not sure what I was trying to show there. The basic syntax is just:
for counter in <1, 5> print(counter)
backfor counter in <1, 5> print(counter)
It’s not overloaded because ‘for’ is basically a macro, expanding to ‘iterate, increment counter, break on counter > 5’ where ‘>’ is hard-coded. If ‘for’ was a fundamental operator then yes, there would be a step option and it would be factored into the exit condition.
You’ve got me thinking, there’s probably a way to overload it even as a macro.. hmmm…
The name came when I was living in Seattle and missed the sounds of east coast summer..
Aliases are strongly-typed which helps avoid some issues. Memory mods come with the territory —- if ‘a’ and ‘b’ point to the same array and ‘a’ resizes that array, then the array behind ‘b’ gets resized too. The one tricky situation is when ‘a’ and ‘b’ each reference range of elements, not the whole array, because a resize of ‘a’ would force a resize of the width of ‘b’. Resizing in this case is usually not allowed.
Garbage collection is indeed done (poorly) by reference counting, and also (very well) by a tracing function that Cicada’s command line script runs after every command.
You’re exactly right, the library is lean because I figure it’s easy to add a C function interface for any capability you want. There’s a bit of personal bias as to what I did include - for example all the basic calculator functions are in, right down to atan(), but no regex. Basic IO (save, load, input, print) is included.
Type marshaling — the Cicada int/float types are defined by cicada.h and can be changed! You just have to use the same types in your C code.
When you run Cicada you pass a list of C functions paired with their Cicada names: { “myCfunction”, &myCfunction }. Then, in Cicada, $myCfunction() runs the callback.
Thanks for the questions! This is exactly the sort of feedback that helps me learn more about the landscape..
One personal preference is that a scripting syntax be somewhat ‘C-like’.. which might recommend a straight C embedded implementation although I think that makes some compromises.
Beyond NNs, my use case to embed fast C calculations into the language to make scientific programming easier. But the inspiration was less about the use case and more about certain programming innovations which I’m sure are elsewhere but I’m not sure where — like aliases, callable function arguments, generalized inheritance, etc.
That’s a great list — most of those languages I’ve honestly never heard of..