Jump to content

  • Log In with Google      Sign In   
  • Create Account

Chained functions in C

  • You cannot reply to this topic
6 replies to this topic

#1 TheComet   Members   -  Reputation: 1604

Like
1Likes
Like

Posted 12 March 2014 - 06:32 AM

(This is C btw, not C++)

 

I just came across the following. Regulator_t is a struct holding various parameters.

/* only one of these */
Regulator_t theRegulator;

int main()
{

    /* initialise regulator */
    set_coil_value( 0.0001f,                   /* 100uH coil */
    set_maximum_coil_current( 1.5f,            /* allow 1.5 amps */
    set_maximum_output_voltage( 200.0f,        /* cap output voltage to 200 volts */
    set_switching_voltage( 325.0f,         /* voltage from rectifier is 230*sqrt(2) = 325V */
    set_output_voltage_divider_ratio( 100.0f,  /* 1Meg to 10k is a ratio of 100 */
    initialise_regulator( theRegulator ))))));

    /* ---SNIP--- */
}

I'm not sure how to feel about this. Someone was obviously trying to be a smartass by defining all "set" functions with the pattern:

Regulator_t* set_whatever( float someValue, Regulator_t* regulator )
{
    regulator->someValue = someValue;
    /* do other important stuff */
    return regulator;
}

On one hand, it's a little confusing because the first section of code is executed "backwards", i.e. initialise_regulator is called first and then the chain is executed from the inside out.

 

On the other hand, it might actually be easier to read than doing it the traditional way:

initialise_regulator( theRegulator );
set_coil_value( theRegulator, 0.0001f );
set_maximum_coil_current( theRegulator, 1.5f );
/* etc */

Any thoughts?


Edited by TheComet, 12 March 2014 - 06:37 AM.

YOUR_OPINION >/dev/null


Sponsor:

#2 Bregma   Crossbones+   -  Reputation: 5133

Like
2Likes
Like

Posted 12 March 2014 - 06:52 AM

I think it's nonidiomatic, hard to read, and far too Klever™.
 
The idiomatic way to chain functions in a C-based language is to return a pointer or reference to the object.

initialise_regulator(&theRegulator)->set_coil_value(0.0001f)->set_maximum_coil_current(1.5f)->...;

That way, the order of operations and the textual ordering are the same.  In C proper, the traditional way accomplishes the same thing.
 
You write programs for human readers.  If you try to make it harder for them (and "them" is more likely "you" a few months later), you're just being an ass.


Edited by Bregma, 12 March 2014 - 06:55 AM.

Stephen M. Webb
Professional Free Software Developer

#3 Bacterius   Crossbones+   -  Reputation: 8890

Like
4Likes
Like

Posted 12 March 2014 - 06:58 AM

Yes, I guess it makes sense from a theoretical perspective as composition of functions, but I find it quite unreadable. I imagine he was inspired by method chaining in e.g. Java where methods mutate their instance and then return a reference to it, so you can write object.DoThis().ThenThat().ThisToo(), but due to the syntactic sugar it's actually executed left to right in that case. You could do the same thing in C using function pointers in the struct but as someone mentioned in your other thread this is hard to maintain, and you still need to pass "this" explicitly (excluding dirty hacks).

 

Would not recommend.

 

The idiomatic way to chain functions in a C-based language is to return a pointer to the object.

initialise_regulator(&theRegulator)->set_coil_value(0.0001f)->set_maximum_coil_current(1.5f)->...;

That way, the order of operations and the textual ordering are the same.

 

I'm pretty sure the function needs a pointer to the struct instance to be able to act on the data, so it gets tedious real fast if the variable has a longish name (though you can add newlines to tidy it up a bit, but then why don't you just use a proper initializer instead of abusing getters and setters for initialization purposes).


The slowsort algorithm is a perfect illustration of the multiply and surrender paradigm, which is perhaps the single most important paradigm in the development of reluctant algorithms. The basic multiply and surrender strategy consists in replacing the problem at hand by two or more subproblems, each slightly simpler than the original, and continue multiplying subproblems and subsubproblems recursively in this fashion as long as possible. At some point the subproblems will all become so simple that their solution can no longer be postponed, and we will have to surrender. Experience shows that, in most cases, by the time this point is reached the total work will be substantially higher than what could have been wasted by a more direct approach.

 

- Pessimal Algorithms and Simplexity Analysis


#4 Shippou   Members   -  Reputation: 1537

Like
0Likes
Like

Posted 12 March 2014 - 10:27 AM

I'm more interested in

    set_coil_value( 0.0001f,                   /* 100uH coil */
    set_maximum_coil_current( 1.5f,            /* allow 1.5 amps */
    set_maximum_output_voltage( 200.0f,        /* cap output voltage to 200 volts */
    set_switching_voltage( 325.0f,         /* voltage from rectifier is 230*sqrt(2) = 325V */
    set_output_voltage_divider_ratio( 100.0f,  /* 1Meg to 10k is a ratio of 100 */
    initialise_regulator( theRegulator ))))));

 Do you know what this code is attached to ?

 That looks like the settings for a transformer . *shrugs *

Seems much easier to use my HART communicator to soft set those values, than hard set them VIA code injection.


 Reactions To Technologies:
1. Anything that is in the world when you’re born is normal and ordinary and is just a natural part of the way the world works.
2. Anything that's invented between when you’re fifteen and thirty-five is new and exciting and revolutionary and you can probably get a career in it.
3. Anything invented after you're thirty-five is against the natural order of things.

- Douglas Adams 2002


 


#5 Buster2000   Members   -  Reputation: 1665

Like
0Likes
Like

Posted 12 March 2014 - 11:02 AM

looks like it was written by a Lisp programmer.



#6 TheComet   Members   -  Reputation: 1604

Like
0Likes
Like

Posted 12 March 2014 - 11:06 AM


I think it's nonidiomatic, hard to read, and far too Klever™


Yes, I guess it makes sense from a theoretical perspective as composition of functions, but I find it quite unreadable. I imagine he was inspired by method chaining in e.g. Java where methods mutate their instance and then return a reference to it, so you can write object.DoThis().ThenThat().ThisToo(), but due to the syntactic sugar it's actually executed left to right in that case. You could do the same thing in C using function pointers in the struct but as someone mentioned in your other thread this is hard to maintain, and you still need to pass "this" explicitly (excluding dirty hacks).



Would not recommend.

My thoughts exactly.




Bregma, on 12 Mar 2014 - 1:52 PM, said:

The idiomatic way to chain functions in a C-based language is to return a pointer or reference to the object.

initialise_regulator(&theRegulator)->set_coil_value(0.0001f)->set_maximum_coil_current(1.5f)->...;

That way, the order of operations and the textual ordering are the same. In C proper, the traditional way accomplishes the same thing.



I'm pretty sure the function needs a pointer to the struct instance to be able to act on the data, so it gets tedious real fast if the variable has a longish name (though you can add newlines to tidy it up a bit, but then why don't you just use a proper initializer instead of abusing getters and setters for initialization purposes).

 

As Bacterius pointed out, the struct would have to hold a reference to every function to make that work, and you'd additionally have to pass the instantiated variable along with it:

initialise_regulator(&theRegulator)->set_coil_value(&theRegulator, 0.0001f)->set_maximum_coil_current(&theRegulator, 1.5f)->...;

You may as well spray camel's diarrhea in my eyes.

 

I'm more interested in

    set_coil_value( 0.0001f,                   /* 100uH coil */
    set_maximum_coil_current( 1.5f,            /* allow 1.5 amps */
    set_maximum_output_voltage( 200.0f,        /* cap output voltage to 200 volts */
    set_switching_voltage( 325.0f,         /* voltage from rectifier is 230*sqrt(2) = 325V */
    set_output_voltage_divider_ratio( 100.0f,  /* 1Meg to 10k is a ratio of 100 */
    initialise_regulator( theRegulator ))))));

 Do you know what this code is attached to ?

 That looks like the settings for a transformer . *shrugs *

Seems much easier to use my HART communicator to soft set those values, than hard set them VIA code injection.

You're pretty much spot on. It's part of a high power buck regulator (step down transformer). The values are hard-coded simply because there is only one version of hardware, and dynamically programming them would introduce additional sources of potential errors, since false values have disastrous effects like the power MOSFETs blowing up.

 

Much of the control logic was offloaded to a micro controller to save space, components, and price . The micro controller is only 0.79$ and yet is essentially a miniature oscilloscope. It can do what 50$ of hardware could have done, only faster and more accurately. The only downside is now you're more error-prone to software bugs, so I'm heavily relying on unit testing.


YOUR_OPINION >/dev/null


#7 ferrous   Members   -  Reputation: 2013

Like
0Likes
Like

Posted 12 March 2014 - 04:00 PM

Is it doing some magic under the hood with those set functions?  It seems like a simple function like, "InitDefaults(reg*) would do the same thing and be a bit clearer.  Granted it obscures the default values out of it, but if you are truly hardcoding those values anyway, does that matter?  Or, to be clearer, a specific function like InitToHardwareXValues()?

 

Hell, to be honest, unless you have a lot of programmers touching that structure, I'd be tempted to just make those values public.


Edited by ferrous, 12 March 2014 - 04:00 PM.






PARTNERS