How can a time function exist in functional programming?

后端 未结 15 479
Happy的楠姐
Happy的楠姐 2020-12-04 04:26

I\'ve to admit that I don\'t know much about functional programming. I read about it from here and there, and so came to know that in functional programming, a function retu

15条回答
  •  遥遥无期
    2020-12-04 04:51

    How can a time function exist in functional programming?

    You've almost answered your own question - if it's a time function, then all it needs is the appropriate input. For a language like Standard ML it could look something like:

    val time_now    : () -> time
    fun time_now () = ...
    

    for a suitable definition of time, of course.

    I [...] know that in functional programming, a function returns the same output, for same input, no matter how many times the function is called.

    That is possible in Standard ML if you're careful...

    For example, consider this:

    f(x,y) = x*x + y;
    

    [...] As such, wherever you've written f(10,4), you can replace it with 104, without altering the value of the whole expression. This property is referred to as referential transparency [...]

    Aha! This calls for a change of language - let's try...Miranda(R)!

    As you've correctly surmised, something like:

    unit ::= Unit
    
    time_now :: unit -> time
    

    would only return the same time value, no matter where it was used - not exactly what we're looking for. We need to use a different input for each call to time_now:

    time_taken x        =  t1 $seq x $seq t2 $seq (x, tdiff)
                           where
                               t1      =  time_now u1
                               t2      =  time_now u2
                               tdiff   =  t2 $minus_time t1
                               u1:u2:_ =  ...               || what goes here?
    

    where:

    minus_time :: time -> time -> time
    

    and time are already defined elsewhere.

    (Note: while it is here, seq isn't actually sequential in all the languages that define it...)

    But what about:

    times_taken xs      =  map time_taken xs
    

    All that would happen is each element of the list would be paired with the same time (difference) value - again, not exactly what was intended.

    Favouring simplicity, we reuse the change made to time_now - use a different input for each call to time_taken:

    times_taken xs      =  map2 time_taken xs us
                           where
                               us =  ...                    || what about here?
    

    That implies:

    time_taken x u      =  t1 $seq x $seq t2 $seq (x, tdiff)
                           where
                               t1      =  time_now u1
                               t2      =  time_now u2
                               tdiff   =  t2 $minus_time t1
                               u1:u2:_ =  ...               || what goes here?
    

    which in turn implies:

    times_taken xs u    =  map2 time_taken xs us
                           where
                               us =  ...                    || what about here?
    

    so that times_taken can also be used far and wide.

    Now for the enigmatic u1, u2 and us - since we've added u as an extra parameter to time_taken and times_taken, let's make use of it with the help of a new definition e.g. parts:

    time_taken x u      =  t1 $seq x $seq t2 $seq (x, tdiff)
                           where
                               t1      =  time_now u1
                               t2      =  time_now u2
                               tdiff   =  t2 $minus_time t1
                               u1:u2:_ =  parts u
    
    times_taken xs u    =  map2 time_taken xs us
                           where
                               us =  parts u
    

    While we're at it, let's also name the type of all these u-values. Since time_now uses an outside source of information, what about:

    time_now :: outside_information -> time
    parts    :: outside_information -> [outside_information]
    

    ...yeah - on second thought, let's just use the initials:

    time_now :: oi -> time
    parts    :: oi -> [oi]
    

    Much better! This also allows us to provide time_taken and times_taken with their own type signatures:

    time_taken  ::   * -> oi -> (*, time)
    times_taken :: [*] -> oi -> [(*, time)]
    

    That just leaves parts and time_now - how will they use their respective oi arguments?

    Well, you may recall the requirement for each oi value to be unique for all this to work. But a regular definition:

    oi ::= ...
    

    would expose the constructors, which could then be reused at will...

    Now consider:

    what_time_taken     :: * -> oi -> (*, time)
    what_time_taken x u =  t1 $seq x $seq t2 $seq (x, tdiff)
                           where
                               t1      =  time_now u1
                               t2      =  time_now u
                               tdiff   =  t2 $minus_time t1
                               u1:u2:_ =  parts u
    

    Did you see it?

    In the local definition of t2, time_now has been applied to u, instead of (presumably) u2 - u is being used twice, contrary to the single-use property we're trying to maintain. This is Miranda(R), not Clean, so there are no uniqueness types which could be used to fend off such anomalies...

    Those two observations suggest oi needs to be predefined, like char or num - an implementation could then check if any oi value has already been used and react accordingly e.g. raising a runtime error to stop the offending program (think of how division-by-zero is dealt with now). The simplest way time_now and parts can access this runtime check is for both to also be predefined (like ord or div) - together with oi, they form an abstract data type provided by the implementation.

    With that out of the way, let's bring everything together:

    || oi ADT
    parts    :: oi -> [oi]
    time_now :: oi -> Time
    
    minus_time :: time -> time -> time  || defined elsewhere
    
    time_taken          :: * -> oi -> (*, time)
    time_taken x u      =  t1 $seq x $seq t2 $seq (x, tdiff)
                           where
                               t1      =  time_now u1
                               t2      =  time_now u2
                               tdiff   =  t2 $minus_time t1
                               u1:u2:_ =  parts u
    
    times_taken         :: [*] -> oi -> [(*, time)]
    times_taken xs u    =  map2 time_taken xs us
                           where
                               us =  parts u
    

    By now, you've probably already noticed how the use of parts, u, etc form a tree embedded across times_taken and times_taken, with its leaves in the applications of time_now. This suggests the existence of a single ancestral oi value e.g:

    start_here :: oi -> unit
    

    We already know having something like:

    start_up :: oi
    

    is useless because it will always have the same value...wait a moment - we're trying to bring a new oi value to start_now? That's so first-order!

    In Miranda(R), functions can be used like any other value of e.g. bool, char, or num i.e. functions are first-class values. Let's just bring start_here to a newly-made oi value inside the implementation, since it already chooses how to process its input based on type:

    • using quotes for printing char values;

    • using the decimal point (if needed) for printing num values.

    We just need to extend it to cater for functions using oi values; the implementation can then:

    1. evaluate the input expression if it isn't already a function;

    2. generate a new oi value;

    3. apply the function to the oi value to form a new input expression;

    4. which is then sent back for more processing, again based on type.

    To obtain a new oi value, the implementation can use a technique similar to what parts uses to generate new oi values.

    To conclude:

    How can a time function exist in functional programming?

    By always being applied to a unique input value wherever it's called.

提交回复
热议问题