Functions are named, reusable expressions. They may be parameterized and they
may return a value, but neither of these features are required. These features are useful for ensuring maximum
re-usability and composability.
Input-less Function
At its most basic, a Scala function is a named wrapper for an expression
def <identifier> = <expression>
With A Return Type
The return type of functions, as with values and variables, are present even if they are not explicitly defined but unlike values and variables, functions are easier to read with explicit types.
def <identifier>: <type> = <expression>
Function
The body of these functions are essentially expressions or expression blocks, where the final line becomes the return value of the expression and thus the function.
def <identifier>(<identifier>: <type>[, ... ]): <type> = <expression>
Procedures
A procedure is a function that doesn’t have a return value. Any function that ends with a statement, such as a println() call, is also a procedure.
e. g. def log(d: Double) = println(f"Got value $d%.2f")
With Empty Parentheses
An alternate way to define and invoke an input-less function (that which has no input parameters) is with empty parentheses
def <identifier>()[: <type>] = <expression>
Invocation With Expression Blocks
When invoking functions using a single parameter, An expression block surrounded with curly braces to send the parameter instead of surrounding the value with parentheses.
<function identifier> <expression block>
Recursive Functions
A recursive function is one that may invoke itself, preferably with some type of parameter or external condition that will be checked to avoid an infinite loop of function invocation.
e. g. def power(x: Int, n: Int): Long = { if (n >= 1) x * power(x, n-1) else 1 }
Tail Recursion
One problem with recursive functions is running into the dreaded "Stack Overflow" error, where invoking a recursive function too many times eventually uses up all of the allocated stack space. The Scala compiler can optimize some recursive functions with tail recursion so that recursive calls do not use additional stack space.
e. g. @annotation.tailrec def power(x: Int, n: Int): Long = { if (n >= 1) x * power(x, n-1) else 1 }
Nested Functions
Functions are named, parameterized expression blocks and expression blocks are nestable, so it should be no great surprise that functions are themselves nestable.
e. g. def max(a: Int, b: Int, c: Int) = { def max(x: Int, y: Int) = if (x > y) x else y max(a, max(b,
c)) }
Calling Functions With Named Parameters
The convention for calling functions is that the parameters are specified in the order they are originally defined. However in Scala we can call parameters by name, making it possible to specify them out of order
<function name>(<parameter> = <value>)
VarArg Parameters
A function parameter that can match zero or more arguments from the caller. Scala also supports vararg parameters, so you can define a function with a variable number of input arguments.
e. g. def sum(items: Int*): Int = { var total = 0; for (i <- items) total += i; total } ).
If we call sum(10, 20, 30)
the result of this method is 60.
Parameters With Default Values
Any languages, a common solution is to provide multiple versions of the same function with the same name but different lists of input parameters. Scala provides a cleaner solution for this problem: specifying default values for any parameter, making the use of that parameter optional for callers
def <identifier>(<identifier>: <type> = <value>): <type>
Parameter Groups
Scala provides the option to break parameters into groups of parameters, each separated with their own parentheses
e. g. def max(x: Int)(y: Int) = if (x > y) x else y
Type Parameters
We can also pass type parameters which dictates the types used for the value parameters or for the return value
def <function-name>[type-name](parameter-name>: <type-name>): <type-name>
Function Types and Values
The type of a function is a simple grouping of its input types and return value type, arranged with an arrow indicating the direction from input types to output type.
e. g. def max(a: Int, b: Int) = if (a > b) a else b
val maximize: (Int, Int) => Int = max
maximize(50, 30)
Result is 50
val maximize: (Int, Int) => Int = max
maximize(50, 30)
Result is 50
Higher-Order Functions
We have already defined values with function types. If we define a function that takes a function type for one of its parameters then we have a higher-order function. The same goes if you define a function with a function type as its return value type.
e. g. def safeStringOp(s: String, f: String => String) = { if (s != null) f(s) else s }
def reverser(s: String) = s.reverse
safeStringOp(null, reverser)
result : String = null
safeStringOp("Jain", reverser)
result: String = niaJ
def reverser(s: String) = s.reverse
safeStringOp(null, reverser)
result : String = null
safeStringOp("Jain", reverser)
result: String = niaJ
Function Literals
A function literal, also known as an anonymous function, is to function types as a String literal (e.g. "Hi") is to the String type.
([<identifier>: <type>, ... ]) => <expression>
e.g. "Hi"
e.g. "Hi"
Placeholder Syntax
Placeholder syntax replaces named parameters in function literals with wild-cards. The wild-cards_
represent a single input parameter, in order, and so may only be used once in the function
body. By removing the name of input parameters and referring to them with wild cards, a function literal can
be reduced down to its core logic.
e. g. def safeStringOp(s: String, f: String => String) = { if (s != null) f(s) else s }
safeStringOp(null, _.reverse)
result : String = null
safeStringOp("Jain", _.reverse)
result: String = niaJ
safeStringOp(null, _.reverse)
result : String = null
safeStringOp("Jain", _.reverse)
result: String = niaJ
Post a Comment
Post a Comment