How can a time function exist in functional programming?

后端 未结 15 453
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.

    0 讨论(0)
  • 2020-12-04 04:52

    I am surprised that none of the answers or comments mention coalgebras or coinduction. Usually, coinduction is mentioned when reasoning about infinite data structures, but it is also applicable to an endless stream of observations, such as a time register on a CPU. A coalgebra models hidden state; and coinduction models observing that state. (Normal induction models constructing state.)

    This is a hot topic in Reactive Functional Programming. If you're interested in this sort of stuff, read this: http://digitalcommons.ohsu.edu/csetech/91/ (28 pp.)

    • Kieburtz, Richard B., "Reactive functional programming" (1997). CSETech. Paper 91 (link)
    0 讨论(0)
  • 2020-12-04 04:52

    Yes! You are correct! Now() or CurrentTime() or any method signature of such flavour is not exhibiting referential transparency in one way. But by instruction to the compiler it is parameterized by a system clock input.

    By output, Now() might look like not following referential transparency. But actual behaviour of the system clock and the function on top of it is adheres to referential transparency.

    0 讨论(0)
  • 2020-12-04 04:58

    Yes and no.

    Different functional programming languages solve them differently.

    In Haskell (a very pure one) all this stuff has to happen in something called the I/O Monad - see here.

    You can think of it as getting another input (and output) into your function (the world-state) or easier as a place where "impureness" like getting the changing time happens.

    Other languages like F# just have some impureness built in and so you can have a function that returns different values for the same input - just like normal imperative languages.

    As Jeffrey Burka mentioned in his comment: Here is the nice introduction to the I/O Monad straight from the Haskell wiki.

    0 讨论(0)
  • 2020-12-04 04:58

    Yes, it's possible for a pure function to return the time, if it's given that time as a parameter. Different time argument, different time result. Then form other functions of time as well and combine them with a simple vocabulary of function(-of-time)-transforming (higher-order) functions. Since the approach is stateless, time here can be continuous (resolution-independent) rather than discrete, greatly boosting modularity. This intuition is the basis of Functional Reactive Programming (FRP).

    0 讨论(0)
  • 2020-12-04 04:59

    Your question conflates two related measures of a computer language: functional/imperative and pure/impure.

    A functional language defines relationships between inputs and outputs of functions, and an imperative language describes specific operations in a specific order to perform.

    A pure language does not create or depend on side effects, and an impure language uses them throughout.

    One-hundred percent pure programs are basically useless. They may perform an interesting calculation, but because they cannot have side effects they have no input or output so you would never know what they calculated.

    To be useful at all, a program has to be at least a smidge impure. One way to make a pure program useful is to put it inside a thin impure wrapper. Like this untested Haskell program:

    -- this is a pure function, written in functional style.
    fib 0 = 0
    fib 1 = 1
    fib n = fib (n-1) + fib (n-2)
    
    -- This is an impure wrapper around the pure function, written in imperative style
    -- It depends on inputs and produces outputs.
    main = do
        putStrLn "Please enter the input parameter"
        inputStr <- readLine
        putStrLn "Starting time:"
        getCurrentTime >>= print
        let inputInt = read inputStr    -- this line is pure
        let result = fib inputInt       -- this is also pure
        putStrLn "Result:"
        print result
        putStrLn "Ending time:"
        getCurrentTime >>= print
    
    0 讨论(0)
提交回复
热议问题