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
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 with104
, 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:
evaluate the input expression if it isn't already a function;
generate a new oi
value;
apply the function to the oi
value to form a new input expression;
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.