Программирование на языке 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

Параметры с разным видом доступа править

Все параметры задаваемые в функциях можно разделить на два вида по способу доступа к ним:

  1. Параметры получаемые как значение (by-­value parameter)
  2. Параметры получаемые как название значения, (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)

Ответы на домашнее задание править