Могу ли я передать произвольную функцию другой функции в Scala?

Я новичок в Scala, и возможность передавать функции другим функциям довольно удобна, но могу ли я передать произвольную ссылку на функцию другой функции? Арность указанного функционального параметра будет фиксированной (тем не менее, мне также любопытно, можете ли вы передать функцию с произвольной арностью). Я продолжаю спотыкаться на ошибках типа. Я пробовал использовать Any, но это не помогает.

Например, у меня есть код ниже:

class CodeRunner(val user_defined: (Int) => Unit) {
  def run(input: Int) = {
    user_defined(input)
  }
}

def arbitrary_code(input: Int) = { println("Running with input " + input) }

val d1 = new CodeRunner(arbitrary_code)

d1.run(4)

И я получаю:

Running with input 4

Теперь предположим, что вместо этого я хочу передать следующую функцию:

def arbitrary_code(input: String) = { println("Running with input " + input) }

Как я могу изменить свой класс CodeRunner для обработки обоих?


person Dan Barowy    schedule 14.06.2011    source источник
comment
Попахивает дженериками: scala-lang.org/node/113   -  person Robert Harvey    schedule 14.06.2011
comment
Дух. шлепает по голове Это кажется таким очевидным после того, как кто-то указывает на это. Спасибо!   -  person Dan Barowy    schedule 15.06.2011


Ответы (4)


Универсальные типы позволяют определить класс с типом-заполнителем, который указывается при создании экземпляра объекта. Компилятор счастлив, потому что он может убедиться, что все типобезопасно, и вы счастливы, потому что вы можете создать экземпляр объекта и передать произвольные типы для значения.

Чтобы использовать общий тип с вашим классом, вы можете изменить его следующим образом:

class CodeRunner[T] (val user_defined: (T) => Unit) {
  def run(input: T) = {
    user_defined(input)
  }
}

[T] после «класса CodeRunner» является важной частью — он определяет, что существует универсальный тип T (вы можете заменить T другой заглавной буквой и т. д.), который будет использоваться в определении класса.

Итак, если вы определяете метод:

def arbitrary_code(input: String) = { println("Running with input " + input) }

а затем передать его:

val d1 = new CodeRunner(arbitrary_code)

... затем компилятор говорит: «ага, для этого экземпляра CodeRunner общий тип T является строкой». И если вы вызываете

d1.run("string")

компилятор будет счастлив, но не позволит вам передать d1.run(4).

person Josh Marcus    schedule 14.06.2011

Как я могу изменить свой класс CodeRunner для обработки обоих?

Вы можете сделать произвольный тип параметром класса:

class CodeRunner[T](val user_defined: (T) => Unit) {
  def run(input: T) = {
    user_defined(input)
  }
}

def arbitrary_code(input: Int) = { println("Running with input " + input) }

val d1 = new CodeRunner(arbitrary_code)

d1.run(4)

def arbitrary_code2(input: String) = { println("Running with input " + input) }

val d2 = new CodeRunner(arbitrary_code2)

d2.run("hello")

Обратите внимание, что тип d2 — это CodeRunner[String], который нельзя присвоить d1, который равен CodeRunner[Int].

person Ben Jackson    schedule 14.06.2011

Чтобы передать произвольную функцию, вы, безусловно, можете использовать дженерики:

def run[T,U](f: T => U) = println(f)

Для произвольной арности это невозможно, потому что функция типа T => U является экземпляром Function1[U,T], а функция типа (T,U) => V является экземпляром Function2[T,U,V]. (Кроме того, я не смог найти ни одного полезного варианта использования). Однако есть умная концепция под названием «каррирование». Он заключается в преобразовании функции, которая принимает несколько аргументов и возвращает значение, в функцию, которая принимает один аргумент и возвращает другую функцию. Вот пример:

def nonCurriedAdd(x: Int, y: Int) = x + y
// nonCurriedAdd(4,6)
def curriedAdd(x: Int) = (y: Int) => x + y
// we can use some syntax sugar
def curriedAdd(x: Int)(y: Int) = x + y
// curriedAdd(4)(6)

Итак, теперь вы можете выполнить `d1.run(curriedAdd). Вы также можете преобразовать функцию без каррирования в каррированную, используя метод «каррирование»:

d1.run(nonCurriedAdd.curried)
person Aymen    schedule 15.06.2011

могу ли я передать произвольную ссылку на функцию другой функции? Арность указанного функционального параметра будет фиксированной.

Как всегда, напишите тип разрабатываемой вами функции, и все станет ясно.

Ваше слово «произвольный» предполагает, что аргументы функции работают для любого типа. То есть они являются полиморфными функциями (или общими функциями в некоторых языках).

Следующее должно быть довольно чисто переведено на Scala:

== The type of an "arbitrary" function of fixed arity
f :: a -> b -> c -> d

-- The type of a function that accepts such a
-- function as an argument, and does something with it:
g :: (a -> b -> c -> d) -> a -> b -> c -> d

-- And a function that implements something of that type
g f a b c = f a b c

Возможно, вы сможете придумать несколько других подобных функций более высокого порядка, которые берут функции фиксированной арности, но произвольного (т. е. полиморфного) типа и работают с ними.

Классические функции высшего порядка, например.

map :: (a -> b) -> [a] -> [b]

fold :: (a -> b -> b) -> b -> [a] -> b

И многие, многие другие.

person Don Stewart    schedule 14.06.2011