Программирование на языке Scala/Синтаксис параметров функции
Подстановочный знак (Wildcard)
правитьВ анонимной функции, имеющей параметр, но не использующей его значение, можно использовать символ "_" в качестве подстановочного знака. Тем самым не придумывать название для параметра и подчеркнуть его неиспользование. Например:
"abc".foreach(char => println("Hello"))
// Значение char не используется, вместо него можно подставить _
"abc".foreach(_ => println("Hello"))
Заполнитель (Placeholder)
правитьИспользовать параметры функции по порядку их объявления, не давая им названий. Например:
val add: (Int, Int) => Int = (first, second) => first + second
// Аргументы в теле функции можно указать не по названию, а по порядку объявления. Первый заполнитель это первый параметр, второй заполнитель это второй параметр.
val add: (Int, Int) => Int = _ + _
// Альтернативная запись
val add = (_:Int) + (_:Int)
Сокращенную форму можно использовать, только если каждый параметр применяется в функции не более одного раза.
Частично применнёная функция
правитьЧастично применённая функция (partially applied function) - это функция, которая вызывается с меньшим числом аргументов, чем у неё определено параметров. Это создаёт новую функцию, которая является частным случаем оригинальной функции, зафиксировав значения некоторых параметров.
Заполнитель можно использовать при создании частично применнёной функции.
Пример:
val add2Param: (Int, Int) => Int = (param1, param2) => param1 + param2
// Создание частичных функций с фиксацией второго параметра.
val add1ParamVar1: Int => Int = param1 => add2Param(param1, 2) //Создание частичной функции с явным указанием параметров
val add1ParamVar2: Int => Int = add2Param(_, 2) //Создание частичной функции с заполнителем
//Результаты
val res = add2Param(5, 2) // res = 7
val res2 = add1ParamVar1(5) // res2 = 7
val res3 = add1ParamVar2(5) // res3 = 7
Создание частичных функций может быть полезным, когда вам нужно передать функцию в качестве аргумента, но вы хотите зафиксировать некоторые её параметры.
Аргумент функции - блок выражений
правитьВ качестве аргумента функции может выступать выражение или блок выражений. Например:
val add: (Int, Int) => Int = (first, second) => first + second
// Аргументами функции выступают значения
val res = add(2, 3)
// Аргументами функции выступают выражение и значение
val res2 = add(1 + 1, 3)
// Аргументами функции выступают блок выражений и значение
val res3 = add(
{
val a = 1 + 1
a * 1
},
3
)
Если функция имеет только один параметр, то можно упростить код, не указывая круглые скобки.
val inc: (Int) => Int = value => value + 1
// Аргументом функции выступает значение
val res = inc(2)
// Аргументом функции выступает выражение
val res2 = inc(1 + 1)
// Аргументом функции выступает блок выражений
val res3 = inc(
{
val a = 1 + 1
a * 1
}
)
// Аргументом функции выступает блок выражений, без указания круглых скобок
val res3 = inc{
val a = 1 + 1
a * 1
}
Такой синтаксис часто используют функции выcшего порядка, когда в качестве аргумента выступает функциональный блок выражений.
//Много скобок, используется блок выражений для задания тела функции.
"abc".foreach( char => {
val newChar = (char + 1).toChar
println(newChar )
}
)
// Меньше скобок, используется функциональный блок выражений
"abc".foreach{ char =>
val newChar = (char + 1).toChar
println(newChar )
}
В функциональном блоке не работает прием заполнитель (placeholder), так как входные параметры в функционально блоке всегда должны быть указаны.
Одиночный неуказанный параметр
правитьОдиночный параметр функции можно совсем нигде не указывать, если он применяется как аргумент другой функции с одним параметром и только один раз. Компилятор сам подставит параметр в нужное место.
val printFun: Char => Unit = char => println(char)
//Упрощенное выражение
val printFun: Char => Unit = println
Параметры с разным видом доступа
правитьВсе параметры задаваемые в функциях можно разделить на два вида по способу доступа к ним:
- Параметры получаемые как значение (by-value parameter)
- Параметры получаемые как название значения, (by-name parameter)
Параметр-значение
правитьАргумент параметра-значения функции будет вычислен перед передачей в функцию, далее в теле функции это значение параметра будет использоваться как неизменно-стабильное во всех выражениях.
Пример:
val check: Int => Boolean = (x: Int) => x == 0 || x == 1
// Использование
check((random() * 100).toInt)
В данном случае аргумент подставляемый в функцию в виде выражения (random() * 100).toInt
вычислиться единственный раз и в виде параметра x будет использован два раза в выражении x == 0 || x = 1
как конкретное значение.
Параметр-значение можно назвать строгим. Он вычисляется всегда в полном объеме перед вызовом функции.
Параметр-название
правитьАргумент параметра-названия функции не будет вычислен перед передачей в функцию. В функцию будет передано само выражение аргумента, далее в теле функции это выражение будет вычисляться перед каждым использованием его в выражениях функции. Параметр-название очень похож на функциональный параметр, по существу он является функциональным параметром у которого отсутствуют входные параметры. Отсутствие входных параметров позволяет использовать в качестве аргумента для параметра-названия нефункциональное выражение, но использовать параметр как функцию.
// Функция с функциональным параметром у которого нет входных параметров
val check2: (() => Int) => Boolean = x => x() == 0 && x() == 1
// Функция с параметром-названием
val check3: ( => Int) => Boolean = x => x == 0 && x == 1
// Использование
check2(() => (random() * 100).toInt)
check3((random() * 100).toInt)
В данных случаях параметр x воспринимается как название функции, и при каждом использовании параметра получаем его новое значение. В качестве аргумента параметра-названия не нужно использовать функцию. При использовании, не нужно указывать пустой список параметров.
Параметр-название, также как и функциональный параметр, можно назвать ленивыми (более точно - нестрогим) параметрами. Это означает, что параметр не вычисляется до тех пор пока он не будет действительно использован в цепочке вычислений. Это позволяет ему вычисляется с запозданием, по мере необходимости, что снижает объем вычислений при вызове функции. Возможно он вообще не будет вычислен никогда, если цепочка вычислений пойдет по пути, где этот параметр не используется. С другой стороны при использовании несколько раз параметра-названия, он будет вычислен тоже несколько раз.
Ленивость вычислений является одной из визитных карточек функционального подхода.
Параметр-название не следует путать с параметрами задаваемыми по названию. Параметры задаваемые по названию будут рассмотрены в будущем. В анонимных функциях такой возможности нет.
Домашнее задание
править1. В учебном проекте создать файл FunctionParams.sc (Scala-Worksheet)
2. Упростите код по возможности
"abc".flatMap(char => "dfg".map(ch => ch.toUpper))
3. Упростите код по возможности
"abc".flatMap((char: Char) => "dfg".flatMap(ch => ch.toUpper + ch.toString) + char.toString)
4. Упростите код по возможности
"abc".sortWith((ch1: Char, ch2: Char) => ch1 > ch2 )
5. Упростите код по возможности
"abc".sortWith((ch1: Char, ch2: Char) => ch1 > ch2 && ch1 < 'c' )
6. Восстановите упрощенный код
"abc".foldLeft(0)(_ + _)
7. Восстановите упрощенный код
"abc".foreach(println)
8. Упростите код по возможности
"abc".foreach((char: Char) => {print(char); println(char + 1)})
9. Встройте функцию fun в метод map
val fun: Char => Char =
(a: Char) => {
val b = a * 2
val c = b + 2
c.toChar
}
"abc".map(fun)
10. Предскажите, совпадут ли результаты функций с тем что они напечатают или нет.
// Функция - аргумент
val getIntValue = () => (random() * 100).toInt
val fun2: Int => Int = { int =>
println(int)
int
}
val fun3: (=> Int) => Int = { int =>
println(int)
int
}
val fun4: (() => Int) => Int = { int =>
println(int())
int()
}
// Вызов с функцией
fun2(getIntValue())
fun3(getIntValue())
fun4(getIntValue)
// Вызов с выражением
fun2((random() * 100).toInt)
fun3((random() * 100).toInt)