<jupyter_start><jupyter_text>Lecture 2  Functions Let's get a bit mathematical here. A __function__ is anything that takes in an input or a set of inputs , and then provides a single value. Single value is something to keep a note of , because any function that provides more than one output is not considered a pure function. For example, f(x) = 4x^2 + 5x + 7In here , as we can see ,  that f(x) is a function of x , where all the values which can be inserted into x i.e. input for the functions is called the __domain__ of the function . Like here, all the real numbers __R__ is the domain of the function. Similarly , this funciton , for one particular value of x will provide only one particular output, thus it is a pure function . A pure quaratic function, where the graph of the function would be a parabola on the +ve y-axis , with an intercept of 4. Now, all the values which the function will produce will be called the range of the function , which in this case is all the +ve numbers till infinity.The  same concept applies into the computer science, term __function__ . It is anything , that takes in a value and then provides a particular value for a given input or a set of inputs. Now the paradigm of programming with only the use of pure functions is called __functional programming__ .Pure functions are any functions , which - - For any given input provide only one particular output- Are not dependent on the outer scope of the environment context, i.e. the global variables. Functions are defined using a reserved keyword __def__ , which ironically stands for __define__, i.e. define a function. A normal function is defined as -def __name-of-the-function__(arguments,if,any):     your process or code to do some task<jupyter_code>def add_ten():
    print(12 + 10)
add_ten()<jupyter_output>22<jupyter_text>if you have not used the __return__ keyword , python will not stop the command from running and keep the function intact.<jupyter_code>def add_ten():
    print(12 + 10)
    print('Hey I am still runnning')
add_ten()
def add_ten():
    return 10 + 12
    print('Hey I am not running')
add_ten()<jupyter_output><empty_output><jupyter_text>arguments are just variables which are limited to the functions context. i.e. those variables can only be accessed inside of the function .<jupyter_code>age = 18

def eligible_to_vote():
    return 10 + age

eligible_to_vote()
age = 20

def eligible_to_vote(candidates_age):
    print(candidates_age)
    return 10 + candidates_age

eligible_to_vote(age)
print(age)
print(candidates_age)<jupyter_output><empty_output><jupyter_text>If you still have the __chull__ to make the argument globally accessible , you can use the __global__ keyword.<jupyter_code>age = 20

def eligible_to_vote(candidates_age):
    print(candidates_age)
    return 10 + candidates_age

eligible_to_vote(age)
print(age)
print('\n')
print(candidates_age)
age = 20 ## global scope ka var

def eligible_to_vote(candidates_age):
    x = 10 ## local scope ka var
    return x + candidates_age

eligible_to_vote(age)
print(x)<jupyter_output>13<jupyter_text>here we are not able to print x since , it is not defined outside of the function, therefore it has a local scope .<jupyter_code>age = 20

def eligible_to_vote(candidates_age):
    global x
    x = 13
    return 10 + candidates_age

eligible_to_vote(age)
print(x)<jupyter_output>13<jupyter_text>A function can take as many arguments as possible. but many of the time, a function is not aware of the number of arguments, thus we use the __*arg__ keyword<jupyter_code>def function_with_too_many_arguments(*arg):
    print(arg)
    
function_with_too_many_arguments(12)
function_with_too_many_arguments(12,23)
function_with_too_many_arguments(12,32, 'hello', (12,34), [1,2,3,4,5,6])<jupyter_output>(12, 32, 'hello', (12, 34), [1, 2, 3, 4, 5, 6])<jupyter_text>Using the __*arg__ keyword ,  you can pass as many arguments as you wish. Now every argument should not have a mandatory regulation. Thus we use some arguments with default values.<jupyter_code>def func_with_default(number):
    return number
func_with_default()<jupyter_output><empty_output><jupyter_text>This is because , we have called the function without any arguments, which are compulsory to be provided in order to run .<jupyter_code>func_with_default(0)
def func_with_default(number = 10):
    return number
func_with_default()
func_with_default(200)<jupyter_output><empty_output><jupyter_text>This is used, when some argument is not compulsory to be provided by the user. Thus it accepts a default value.  Now , if we have a similar case of unknown arguments , with their keys, then we use the __**kwarg__ keyword , which stands for, keyword argument.<jupyter_code>def func_with_default(**kwargs):
    print(kwargs)
func_with_default()
func_with_default(arg1 = None)
func_with_default(arg1 = 10, arg2 = 20, arg3 = 30)<jupyter_output>{'arg1': 10, 'arg2': 20, 'arg3': 30}<jupyter_text>A condition to make a function pure is that  , it depends on a single input, and local vars. Thus to make a function pure, we use a method called __currying__ . Currying is nothing but , a simple method to break many arguments into one single function.<jupyter_code>def add_two_values(x,y): ## impure function
    return x + y
add_two_values(10,20)
## currying used function.
def add_two_values(x):
    def second_value(y):
        return x + y
    return second_value
add_two_values(10)(10)
val_one = add_two_values(10)
val_one
val_one(10)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07098765432098765, 'fraction_numerical': 0.021241830065359478, 'mean_word_length': 5.00763358778626, 'pattern_counts': {'":': 0, '<': 36, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '310206', 'license': 'MIT', 'n_tokens_mistral': 1756, 'n_tokens_neox': 1642, 'n_words': 691, 'path': 'Py_tut_2.ipynb', 'repo_name': 'justdvnsh/py_tuts', 'size': '5508'}