Jump to content

Output terminals behave differently in dynamic and static VIs


Recommended Posts

Hi,

I've noticed a thing in LabVOOP that is quite annoying. If you have a static method VI, you can pass an object parameter to that method and get modified object of the same class out from one of the output terminals. However, with dynamic VIs you do not get the same behaviour. Dynamic VI methods casts object parameters to the more generic terminal type whereas static VI methods do not cast. See the image below.

It's annoying, since if you need to use dynamic VIs to which you pass parameters that are also objects, you need to cast these objects back to original type.

post-4014-1159251222.png?width=400

I assume this feature is implemented because it may be technically impossible to keep track if all decendent class dynamic VIs also really pass the wire trough the method. If the wire doesn't go from input terminal to output terminal directly or indirectly, one cannot of course preserve type.

What I suggest is an addition of a new terminal type similar to dynamic dispatch terminal. This terminal also would require that the wire really continues from input terminal to output terminal so that the type can be passed trough the method VI. However unlike dynamic dispatch terminal, this terminal would not be used for dynamic selection of which method instance to call.

I attach also the example project from which the above image is captured.

Download File:post-4014-1159251306.zip

p.s. I know there are people also on this forum who claim that in OOP one shouldn't create operations external to the class similar to the negate operation in the attached example. However this is not true. There are many more advanced design patterns which use these outsourced methods to add more flexibility and modularity to the software design. Strict encapsulation is often too limited to the requirements of software architecture and this can be in some cases overcome using this kinds of functionality outsourcing. Of course negate is not a good example of these situations, but it is easy to understand.

Link to comment
I assume this feature is implemented because it may be technically impossible to keep track if all decendent class dynamic VIs also really pass the wire trough the method. If the wire doesn't go from input terminal to output terminal directly or indirectly, one cannot of course preserve type.

Bingo.

What I suggest is an addition of a new terminal type similar to dynamic dispatch terminal. This terminal also would require that the wire really continues from input terminal to output terminal so that the type can be passed trough the method VI. However unlike dynamic dispatch terminal, this terminal would not be used for dynamic selection of which method instance to call.

Your thoughts accord with mine, sir. In fact, all of the type propagation information needed to support this function is available today in the compiler.

The problem is the user interface. We wouldn't be able to just popup on a terminal and say "This terminal is...>> XYZ" because what we would need is a way to specify which terminal, possibly a fairly complex mapping. And if we implement the simple form, you'd probably next want us to support the case of the MinMax.vi -- given two class inputs that produce two class outputs, the top one being the greater and the bottom one being the lesser, you'd want the outputs to become the nearest common parent to two input types. So to really handle this you need a fairly complex map.

And then there's the enforcement on the block diagram, with some very arcane error reports, such as, "Although data from X and Y both propagate to Z, the runtime data type of Y is not preserved at the Event Structure tunnel." The ones we have for dynamic output terminals are confusing enough. With the dynamic in/out, I was able to implement the background color of the wire to indicate whether or not the data was successfully propagating to the output FPTerm. With multiple sets of propagations on the diagram, I'd have to have some annotation on the wire probably involving a numeric code of which FPTerms were involved in setting the data on the wire.

I tried pulling this together a couple years ago during LabVOOP development. It's a continuing problem. At this point I'm actually considering something more akin to an "Advanced Conpane Configuration" dialog which would allow you to fully define the mapping of input terms to output terms, specify 5 different behaviors for unwired input terminals and about 3 other really useful behaviors that a simple "click on conpane click on control" cannot specify.

I'm starting to think there exists a rather elegant solution to the type propagation problem with the addition of a single new primitive to the language. I've been drawing sketches of a "Preserve Runtime Type" prim, which has a look/feel identical to the "To More Specific" node, but instead of casting to the wire data type, it casts to the type of the data on the wire at runtime. It's a little weird to explain why such a node is necessary, but the short description is that with it you could wrap a dynamic dispatch in a static VI and then do the runtime type cast on the output of the dynamic subVI call to preserve the type... then make the static VI be your public interface. (Sorry, I don't have the sketches in a .png form to post right now.)

Link to comment
I'm starting to think there exists a rather elegant solution to the type propagation problem with the addition of a single new primitive to the language. I've been drawing sketches of a "Preserve Runtime Type" prim, which has a look/feel identical to the "To More Specific" node, but instead of casting to the wire data type, it casts to the type of the data on the wire at runtime. It's a little weird to explain why such a node is necessary, but the short description is that with it you could wrap a dynamic dispatch in a static VI and then do the runtime type cast on the output of the dynamic subVI call to preserve the type... then make the static VI be your public interface. (Sorry, I don't have the sketches in a .png form to post right now.)

About your implementation suggestion, I must say that, I don't really like it. I think that static VIs should not be used as wrappers to dynamic VIs so that one can overcome limitations of the language. I already need to use static VIs as wrappers to dynamic VIs due to the fact that I want to use polymorphic VIs in my classes and I must say that it is kind of frustrating to create always two methods instead of one. I think it would also be hard to explain to users that well, in our language we have decided to implement this feature so that you create two methods, one embedded inside an other, to do this very simple task. Simple tasks should be made simple to achive.

Now that we are talking about types, perhaps I suggest something much more general. Something I've been really wanting to see in LabVIEW but have been "too little picky" to request :D

Polymorpic types

First of all, I'd like to see polymorphic types. What I mean with this is the following. One should be able to define front panel terminals that are of multiple types simultanously. These terminals would accept all these types of data. Wire propagating from such a terminal could be connected to any node that would accept at least the same types, but could also accept some more types.

One can implement such a polymorphic types in a bit similar fashion as enums or arrays are implemented. A polymorphic type controller would be an "type-array" each element of which would be a type specifier. One could also drop another polymorphic type-array into such array so that the resulting polymorphism would include all the items of the array itself and all its subarrays and their subarrays and so on. The "type-array" would be structurally a tree and the polymorphic type of such a tree would be defined by flattening the tree into a set of all the nodes in the tree.

Type classes

Types should form hierarchies, that is type classes. For example Integer and Floating would be subtypes of general class Number. Furthermore I32 and I16 would be subtypes of Integer type. You could define that your terminal is of type Integer and would therefore accept both I32 and I16 inputs without coercion.

The type classes shold in the beginning be built-in only. That is, user couldn't add new members to built-in type classes. Later on however, users should be able to extend type classes by defining own type that belong to certain type class. Type classes are exactly specified by requiring that certain built-in operations are defined for the type. For example a type of class number must have addition, substraction and multiplication defined. Type of class Floating should in addition have division operation defined.

Type classes, polymorphic types and type propagation

Type propagation of polymorphic types and type classes is somewhat similar issue to type propagation of LVOOP classes. When you wire two DBLs to your "+" operation, you want the output to be DBL. As LabVIEW is now a object-oriented programming, the type should also propagate trough dynamically dispatched methods. So the problematics is exactly the same as with type propagation of LabVIEW OOP classes. Which ever way you decide to implement the OOP type propagation, please make sure it would also be compatible of propagation of other types. I really wouldn't like to be casting all my types all the time.

Roadmap

Now that we have defined polymorphic types and type-classes, we still need to add wires and extend types so that wires can propagate not only VI references but block-diagrams themselves. We also need totally stateless VIs, real recursion (without opening a VI reference first), local and global variables that can only be written once (i.e. they are also stateless), perhaps lazy evaluation and some kind of lamda calculus, type pattern matching similar to Haskell counterpart and we end up in a fully featured graphical functional programming language. Monads are not needed, since empty wires or something like error cluster can define the execution order for I/O and user interface operations. Loops can be used in dataflow version of functional programming language, as they do not require variables unlike loops in text based programming languages.

This kind of general purpose programming language would probably be the most advanced, easiest to use programming language in the world. It would be naturally multithreading and allow writing highly modular code. I think combining dataflow programming language and functional programming language results in a something really amazing.

It's a pity that I know National Instruments will never ever do this kind of general purpose programming language. Perhaps I do it myself. Anybody else interested in joining my... quite challenging effort :P

Link to comment
  • 3 weeks later...

Hi Aristos,

You Aristos probably ment something like this could work? In the image below there is a static VI that calls a dynamic VI and the return value of the dynamic VI is casted back to static VI. Currentyl it doesn't work but do you think this exact diagram would work in the future releases of LabVIEW? If so, then I leave it as it is and hope it will work correctly some day. Until then I'll type cast the return value of the static VI manually in the calling block diagram.

post-4014-1160933347.png?width=400

Then a second thing. Would it be possible to extend this automatic type casting behaviour of static VIs to include object arrays. Especially the following cases:

  • Array of objects class A as an input would cast the type of an output to an array of objects of class A. (Array -> Array)
  • Array of objects class A as an input would cast the type of an output to an objects of class A. (Array -> Object, Index Array)
  • An object of class A as an input would cast the type of an output to an array of objects of class A. (Object -> Array, Get All)
  • All the same things Variants i.e. if there is a variant as an input and variant as an output and the type of the data in the variant stays the same then the calling block diagram would automatically see correctly typecasted output instead of variant output.

Link to comment
Bingo.

Your thoughts accord with mine, sir. In fact, all of the type propagation information needed to support this function is available today in the compiler.

The problem is the user interface. We wouldn't be able to just popup on a terminal and say "This terminal is...>> XYZ" because what we would need is a way to specify which terminal, possibly a fairly complex mapping. And if we implement the simple form, you'd probably next want us to support the case of the MinMax.vi -- given two class inputs that produce two class outputs, the top one being the greater and the bottom one being the lesser, you'd want the outputs to become the nearest common parent to two input types. So to really handle this you need a fairly complex map.

Aren't these problems are caused by the fact that you do not store type information in the control and indicators of LVOOP objects on the front panel? If the user would specify what type the "object in/out" should be all problems would be solved. If I understood things correctly you adjust at edit/view time the type of object to the way it is wired. This is unlike strict typedefining in all other OO languages and also unlike the LabVIEW behaviour we had up to now.

I filed a bug complaining that an indicator was automatically incorrectly adjusting to what I had wired, while it was _me_ who had made the mistake. I had expected a compiler error, but LabVIEW "solved" the type for me - incorrectly. It is not what you would expect from a compiler. But the bug was dismissed stating it was intended behaviour. :(

With this behaviour you need to solve all the kind of problems that you mention. Which you would not have had if you had made the control/indicator keep this object type. Of course sticking to OO rules, so a Bear can be wired to an Animal control, but a Car cannot.

You would not have any dynamic call problems then either, as the type is defined in the connector pane constant.

Can you tell us some more about why you choose this auto adjust to typedef behaviour ?

I tried pulling this together a couple years ago during LabVOOP development. It's a continuing problem. At this point I'm actually considering something more akin to an "Advanced Conpane Configuration" dialog which would allow you to fully define the mapping of input terms to output terms, specify 5 different behaviors for unwired input terminals and about 3 other really useful behaviors that a simple "click on conpane click on control" cannot specify.

I'm starting to think there exists a rather elegant solution to the type propagation problem with the addition of a single new primitive to the language.

Argh ! :headbang: Please just make the object control and indicator typed ! This sounds terrible.

Joris

Link to comment
They are typed. Strongly typed. We're not talking about the type of the VI. We're talking about the type of the call to the VI.

Well I've seen things change type suddenly when I rewired something... Scary. It feels like you're not in control. Which was true appearently. :unsure:

OK then. If I read your remark then I think: You should know what is coming out of a call... An object of the type of the indicator. So appearently that is true, but not with static calls.

So with dynamic VIs, the type is still defined in the connector pane constant. And you want to cast that back to the type you put in... yes that makes sense. :thumbup:

But then my question is, why don't you do it the same way on static calls ? It is not very "version robust" if the type that comes out at a VI-call can change when you change the contents of the VI without changing the type of the indicator. It conflicts with the class-independence you are trying to achieve. A calling VI may become broken while the connector pane is not changed and the called VI is also still executable... Having all the type propagation info is a snapshot info only, it does not make it more "version robust".

You know, I'm not really into this automatic stuff... I'd rather think about my code and downcast manually. The downcast is a normal object downcast with as type the wires that goes in. I would think that is very acceptable in your code. It consistently adheres to the defition of the called method. And you see what is happening in the diagram, no hidden config windows.

But if you're sure the system you have is predictable and "version robust", then you have my vote.

Joris

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.