Test if property exists on a class at runtime - intersystems-cache

Something like this:
##class(MyApp.MyClass).%HasProperty("SomeProperty").
I looked into doing something like this:
set classDefinition = ##class(%Dictionary.CompiledClass).%OpenId(%class.Name)
and then looping through the Properties, however, I need to be able to use any class, not just %class

For a simple OO approach, you can use the following API:
Set tPropExists = ##class(%Dictionary.CompiledProperty).IDKEYExists("SomeClass","SomeProperty")
This should have much less runtime cost than loading the class definition data and looping over its properties (and thus loading the data for those properties as well).
If you still want to create a %HasProperty() helper method for your application classes, you can use the following base method (assuming you are on Cache 2010.2 or higher - I believe the $this special variable and the $classname() function were added in 2010.2, but that may have been in 2010.1.):
ClassMethod %HasProperty(pPropName As %String = "") As %Boolean
{
Set tHasProp = 0
If (pPropName '= "") {
Set tHasProp = ##class(%Dictionary.CompiledProperty).IDKEYExists($classname($this),pPropName)
}
Quit tHasProp
}

You also might want to use a generator method (one of the really nice features in Cache objects) if run-time speed is important to you.
For example:
Method PropertyExists(Name) As %Boolean [ CodeMode = generator, ProcedureBlock = 1, ServerOnly = 1 ]
{
Set %code=0
S ClassDef=##class(%Dictionary.CompiledClass).%OpenId(%class)
i '$IsObject(ClassDef) $$$GENERATE(" Q 0") Q $$$OK
I '$IsObject(ClassDef.Properties) $$$GENERATE(" Q 0") Q $$$OK
S Key="" F S Key=ClassDef.Properties.Next(Key) Q:Key="" D
. S CompiledProperty=ClassDef.Properties.GetAt(Key)
. $$$GENERATE(" I Name="""_CompiledProperty.Name_""" Q 1" )
$$$GENERATE(" Q 0")
q $$$OK
}

Related

ranges::views::generate have generator function signal end of range

I'd like to have a generator that terminates, like python, but I can't tell from ranges::views::generate's interface if this is supported.
You can roll it by hand easily enough:
https://godbolt.org/z/xcGz6657r although it's probably better to use a coroutine generator if you have one available.
You can return an optional in the generator, and stop taking elements when a std::nullopt is generated with views::take_while
auto out = ranges::views::generate(
[i = 0]() mutable -> std::optional<int>
{
if (i > 3)
return std::nullopt;
return { i++ };
})
| ranges::views::take_while([](auto opt){ return opt.has_value();})
;

Cleaner way of declaring many classes in perl

I´m working in a code which follows some steps, and each of this steps is done in one class. Right now my code looks like this:
use step_1_class;
use step_2_class;
use step_3_class;
use step_4_class;
use step_5_class;
use step_6_class;
use step_7_class;
...
use step_n_class;
my $o_step_1 = step_1_class->new(#args1);
my $o_step_2 = step_2_class->new(#args2);
my $o_step_3 = step_3_class->new(#args3);
my $o_step_4 = step_4_class->new(#args4,$args_4_1);
my $o_step_5 = step_5_class->new(#args5);
my $o_step_6 = step_6_class->new(#args6,$args_6_1);
my $o_step_7 = step_7_class->new(#args7);
...
my $o_step_n = step_n_class->new(#argsn);
Is there a cleaner way of declaring this somewhat similar classes wihtout using hundreds of lines?
Your use classes as written are equivalent to
BEGIN {
require step_1_class;
step_1_class->import() if step_1_class->can('import');
require step_2_class;
step_2_class->import() if step_2_class->can('import');
...
}
This can be rewritten as
BEGIN {
foreach my $i ( 1 .. $max_class ) {
eval "require step_${i}_class";
"step_${i}_class"->import() if "step_${i}_class"->can('import');
}
}
The new statements are a little more complex as you have separate variables and differing parameters, however this can be worked around by storing all the objects in an array and also preprocessing the parameters like so
my #steps;
my #parameters = ( undef, \#args1, \#args2, \#args3, [ #args4, $args_4_1], ...);
for ($i = 1; $i <= $max_class; $i++) {
push #steps, "step_${i}_class"->new(#{$parameters[$i]});
}
You can generate the use clauses in a Makefile. Generating the construction of the object will be more tricky as the arguments aren't uniform - but you can e.g. save the exceptions to a hash. This will make deployment more complex and searching the code tricky.
It might be wiser to rename each step by its purpose, and group the steps together logically to form a hierarchy instead of a plain sequence of steps.

How to combine the elements of an arbitrary number of dependent Fluxes?

In the non reactive world the following code snippet is nothing special:
interface Enhancer {
Result enhance(Result result);
}
Result result = Result.empty();
result = fooEnhancer.enhance(result);
result = barEnhancer.enhance(result);
result = bazEnhancer.enhance(result);
There are three different Enhancer implementations taking a Result instance, enhancing it and returning the enhanced result. Let's assume the order of the enhancer calls matters.
Now what if these methods are replaced by reactive variants returning a Flux<Result>? Because the methods depend on the result(s) of the preceding method, we cannot use combineLatest here.
A possible solution could be:
Flux.just(Result.empty())
.switchMap(result -> first(result)
.switchMap(result -> second(result)
.switchMap(result -> third(result))))
.subscribe(result -> doSomethingWith(result));
Note that the switchMap calls are nested. As we are only interested in the final result, we let switchMap switch to the next flux as soon as new events are emitted in preceding fluxes.
Now let's try to do it with a dynamic number of fluxes. Non reactive (without fluxes), this would again be nothing special:
List<Enhancer> enhancers = <ordered list of different Enhancer impls>;
Result result = Result.empty();
for (Enhancer enhancer : enhancers) {
result = enhancer.enhance(result);
}
But how can I generalize the above reactive example with three fluxes to deal with an arbitrary number of fluxes?
I found a solution using recursion:
#FunctionalInterface
interface FluxProvider {
Flux<Result> get(Result result);
}
// recursive method creating the final Flux
private Flux<Result> cascadingSwitchMap(Result input, List<FluxProvider> fluxProviders, int idx) {
if (idx < fluxProviders.size()) {
return fluxProviders.get(idx).get(input).switchMap(result -> cascadingSwitchMap(result, fluxProviders, idx + 1));
}
return Flux.just(input);
}
// code using the recursive method
List<FluxProvider> fluxProviders = new ArrayList<>();
fluxProviders.add(fooEnhancer::enhance);
fluxProviders.add(barEnhancer::enhance);
fluxProviders.add(bazEnhancer::enhance);
cascadingSwitchMap(Result.empty(), fluxProviders, 0)
.subscribe(result -> doSomethingWith(result));
But maybe there is a more elegant solution using an operator/feature of project-reactor. Does anybody know such a feature? In fact, the requirement doesn't seem to be such an unusual one, is it?
switchMap feels inappropriate here. If you have a List<Enhancer> by the time the Flux pipeline is declared, why not apply a logic close to what you had in imperative style:
List<Enhancer> enhancers = <ordered list of different Enhancer impls>;
Mono<Result> resultMono = Mono.just(Result.empty)
for (Enhancer enhancer : enhancers) {
resultMono = resultMono.map(enhancer::enhance); //previousValue -> enhancer.enhance(previousValue)
}
return resultMono;
That can even be performed later at subscription time for even more dynamic resolution of the enhancers by wrapping the whole code above in a Mono.defer(() -> {...}) block.

Roblox- how to store large arrays in roblox datastores

i am trying to make a game where players create their own buildings and can then save them for other players to see and play on. However, roblox doesn't let me store all the data needed for the whole creation(there are several properties for each brick)
All i get is this error code:
104: Cannot store Array in DataStore
any help would be greatly appreciated!
I'm not sure if this is the best method, but it's my attempt. Below is an example of a table, you can use tables to store several values. I think you can use HttpService's JSONEncode function to convert tables into strings (which hopefully can be saved more efficiently)
JSONEncode (putting brick's data into a string, which you can save into the DataStore
local HttpService = game:GetService("HttpService")
-- this is an example of what we'll convert into a json string
local exampleBrick = {
["Size"] = Vector3.new(3,3,3),
["Position"] = Vector3.new(0,1.5,0),
["BrickColor"] = BrickColor.new("White")
["Material"] = "Concrete"
}
local brickJSON = HttpService:JSONEncode(exampleBrick)
print(brickJSON)
-- when printed, you'll get something like
-- { "Size": Vector3.new(3,3,3), "Position": Vector3.new(0,1.5,0), "BrickColor": BrickColor.new("White"), "Material": "Concrete"}
-- if you want to refer to this string in a script, surround it with two square brackets ([[) e.g. [[{"Size": Vector3.new(3,3,3)... }]]
JSONDecode (reading the string and converting it back into a brick)
local HttpService = game:GetService("HttpService")
local brickJSON = [[ {"Size": Vector3.new(3,3,3), "Position": Vector3.new(0,1.5,0), "BrickColor": BrickColor.new("White"), "Material": "Concrete"} ]]
function createBrick(tab)
local brick = Instance.new("Part")
brick.Parent = <insert parent here>
brick.Size = tab[1]
brick.Position= tab[2]
brick.BrickColor= tab[3]
brick.Material= tab[4]
end
local brickData = HttpService:JSONDecode(brickJSON)
createBrick(brickData) --this line actually spawns the brick
The function can also be wrapped in a pcall if you want to account for any possible datastore errors.
Encoding a whole model into a string
Say your player's 'building' is a model, you can use the above encode script to convert all parts inside a model into a json string to save.
local HttpService = game:GetService("HttpService")
local StuffWeWantToSave = {}
function getPartData(part)
return( {part.Size,part.Position,part.BrickColor,part.Material} )
end
local model = workspace.Building --change this to what the model is
local modelTable = model:Descendants()
for i,v in pairs(modelTable) do
if v:IsA("Part") or v:IsA("WedgePart") then
table.insert(StuffWeWantToSave, HttpService:JSONEncode(getPartData(modelTable[v])))
end
end
Decoding a string into a whole model
This will probably occur when the server is loading a player's data.
local HttpService = game:GetService("HttpService")
local SavedStuff = game:GetService("DataStoreService"):GetDataStore("blabla") --I don't know how you save your data, so you'll need to adjust this and the rest of the scripts (as long as you've saved the string somewhere in the player's DataStore)
function createBrick(tab)
local brick = Instance.new("Part")
brick.Parent = <insert parent here>
brick.Size = tab[1]
brick.Position= tab[2]
brick.BrickColor= tab[3]
brick.Material= tab[4]
end
local model = Instance.new("Model") --if you already have 'bases' for the players to load their stuff in, remove this instance.new
model.Parent = workspace
for i,v in pairs(SavedStuff) do
if v[1] ~= nil then
CreateBrick(v)
end
end
FilteringEnabled
If your game uses filteringenabled, make sure that only the server handles saving and loading data!! (you probably already knew that) If you want the player to save by clicking a gui button, make the gui button fire a RemoteFunction that sends their base's data to the server to convert it to a string.
BTW I'm not that good at scripting so I've probably made a mistake somehwere.. good luck though
Crabway's answer is correct in that the HttpService's JSONEncode and JSONDecode methods are the way to go about tackling this problem. As it says on the developer reference page for the DataStoreService, Data is ... saved as a string in data stores, regardless of its initial type. (https://developer.roblox.com/articles/Datastore-Errors.) This explains the error you received, as you cannot simply push a table to the data store; instead, you must first encode a table's data into a string using JSONEncode.
While I agree with much of Crabway's answer, I believe the function createBrick would not behave as intended. Consider the following trivial example:
httpService = game:GetService("HttpService")
t = {
hello = 1,
goodbye = 2
}
s = httpService:JSONEncode(t)
print(s)
> {"goodbye":2,"hello":1}
u = httpService:JSONDecode(s)
for k, v in pairs(u) do print(k, v) end
> hello 1
> goodbye 2
As you can see, the table returned by JSONDecode, like the original, uses strings as keys rather than numeric indices. Therefore, createBrick should be written something like this:
function createBrick(t)
local brick = Instance.new("Part")
brick.Size = t.Size
brick.Position = t.Position
brick.BrickColor = t.BrickColor
brick.Material = t.Material
-- FIXME: set any other necessary properties.
-- NOTE: try to set parent last for optimization reasons.
brick.Parent = t.Parent
return brick
end
As for encoding a model, calling GetChildren would produce a table of the model's children, which you could then loop through and encode the properties of everything within. Note that in Crabway's answer, he only accounts for Parts and WedgeParts. You should account for all parts using object:IsA("BasePart") and also check for unions with object:IsA("UnionOperation"). The following is a very basic example in which I do not store the encoded data; rather, I am just trying to show how to check the necessary cases.
function encodeModel(model)
local children = model:GetChildren()
for _, child in ipairs(children) do
if ((child:IsA("BasePart")) or (child:IsA("UnionOperation"))) then
-- FIXME: encode child
else if (child:IsA("Model")) then
-- FIXME: using recursion, loop through the sub-model's children.
end
end
return
end
For userdata, such as Vector3s or BrickColors, you will probably want to convert those to strings when you go to encode them with JSONEncode.
-- Example: part with "Brick red" BrickColor.
color = tostring(part.BrickColor)
print(string.format("%q", color))
> "Bright red"
I suggest what #Crabway said, use HttpService.
local httpService = game:GetService("HttpService")
print(httpService:JSONEncode({a = "b", b = "c"}) -- {"a":"b","b":"c"}
But if you have any UserData values such as Vector3s, CFrames, Color3s, BrickColors and Enum items, then use this library by Defaultio. It's actually pretty nice.
local library = require(workspace:WaitForChild("JSONWithUserdata"))
library:Encode({Vector3.new(0, 0, 0)})
If you want a little documentation, then look at the first comment in the script:
-- Defaultio
--[[
This module adds support for encoding userdata values to JSON strings.
It also supports lists which skip indices, such as {[1] = "a", [2] = "b", [4] = "c"}
Userdata support is implemented by replacing userdata types with a new table, with keys _T and _V:
_T = userdata type enum (index in the supportedUserdataTypes list)
_V = a value or table representing the value
Follow the examples bellow to add suppport for additional userdata types.
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Usage example:
local myTable = {CFrame.new(), BrickColor.Random(), 4, "String", Enum.Material.CorrodedMetal}
local jsonModule = require(PATH_TO_MODULE)
local jsonString = jsonModule:Encode(myTable)
local decodedTable = jsonModule:Decode(jsonString)
--]]

What is wrong with the below statement(C#3.0 / Lambda)

what is wrong in the below
Enumerable.Range(0, objEntityCode.Count - 1).Select(i =>
{
options.Attributes[i] = new EntityCodeKey
{
EntityCode = objEntityCode[i].EntityCodes
, OrganizationCode = Constants.ORGANIZATION_CODE };
})
.ToArray();
Throwing error The type arguments for method 'System.Linq.Enumerable.Select(System.Collections.Generic.IEnumerable, System.Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
But this works
Enumerable.Range(0, objEntityCode.Count - 1).ToList().ForEach(i =>
{
options.Attributes[i] = new EntityCodeKey
{
EntityCode = objEntityCode[i].EntityCodes
, OrganizationCode = Constants.ORGANIZATION_CODE
};
}
);
Using C#3.0.
Purpose: I am learning LINQ / LAMBDA and trying to do the same program in different way.
Thanks.
As I stated in a comment - your lambda expression doesn't return anything. Therefore it can't be used in a projection. Select is meant to transform a sequence of items of one type into a sequence of items of another type. You're not doing any transforming in your lambda expression - you've just got an assignment.
Now you could do this:
Enumerable.Range(0, objEntityCode.Count - 1).Select(i =>
{
return options.Attributes[i] = new EntityCodeKey
{
EntityCode = objEntityCode[i].EntityCodes,
OrganizationCode = Constants.ORGANIZATION_CODE
};
})
.ToArray();
I wouldn't recommend it though. I appreciate you're currently learning about LINQ and lambda expressions, but it's worth learning when not to use them too - and this looks like a situation where you really, really shouldn't use them.
Look at what you have inside the Select and ForEach method calls:
options.Attributes[i] = new EntityCodeKey
{
EntityCode = objEntityCode[i].EntityCodes
, OrganizationCode = Constants.ORGANIZATION_CODE
};
This is essentially an Action<int> -- that is, code that does something but doesn't return something. This is why it makes sense within ForEach but not Select -- Select expects (in this case) a Func<int, T> -- code that returns something (of some type T). Since you are simply assigning Attributes[i] to a new EntityCodeKey, this code does not fall under the umbrella of what you would normally find within a Select call.
Note that technically, the above code actually would return something -- namely, the value stored in options.Attributes[i] -- if you removed the semicolon from the end. Why? Two reasons:
A single-line lambda expression (not terminating in a semi-colon) returns whatever it evaluates to. This is why something like person => person.Name can actually be interpreted as a Func<Person, string>.
An assignment operation evaluates to the assigned value. This is why you can write x = y = z -- because y = z actually evalutes to the newly assigned value of y.
So it's true: your code, sans semi-colon, would actually evaluate to options.Attributes[i]. But writing the code in this way would be, in my opinion anyway, pretty confusing.