Diferenças entre essas três maneiras de definir uma função em Scala

92

Dadas três maneiras de expressar a mesma função f(a) := a + 1:

val f1 = (a:Int) => a + 1
def f2 = (a:Int) => a + 1
def f3:(Int => Int) = a => a + 1

Como essas definições diferem? O REPL não indica quaisquer diferenças óbvias:

scala> f1
res38: (Int) => Int = <function1>
scala> f2
res39: (Int) => Int = <function1>
scala> f3
res40: (Int) => Int = <function1>
qrest
fonte
11
Você deve observar que no segundo bloco acima, a avaliação f1no REPL mostra o valor vinculado estaticamente f1ao avaliar f2e f3mostra o resultado da chamada desses métodos. Em particular, uma nova Function1[Int, Int]instância é produzida toda vez que f2ou f3é invocada, enquanto f1é a mesma Function1[Int, Int]para sempre.
Randall Schulz,
@RandallSchulz dado que a versão val não requer uma nova instância de função, por que alguém usaria def neste caso?
virtualeyes
2
@virtualeyes A única situação de que me lembro em que se vê defs produzindo valores FunctionN [...] é na biblioteca do analisador combinador. Não é muito comum escrever métodos que geram funções e virtualmente nunca alguém usaria um def para produzir muitas cópias de uma função semântica / funcionalmente imutável.
Randall Schulz

Respostas:

112

f1 é uma função que pega um inteiro e retorna um inteiro.

f2é um método com aridade zero que retorna uma função que recebe um inteiro e retorna um inteiro. (Quando você digita f2em REPL mais tarde, torna-se uma chamada para o método f2.)

f3é o mesmo que f2. Você simplesmente não está empregando inferência de tipo aqui.

faltando faktor
fonte
6
Por que f1é um functione f2é um method?
Freewind de
17
@Freewind, uma função é um objeto com um método chamado apply. Um método, bem, é um método.
missingfaktor
Resposta incrível. Pergunta: você diz que f2 tem aridade zero, mas não é unário? en.wikipedia.org/wiki/Arity "Uma função nula não leva argumentos. Uma função unária leva um argumento." Apenas curioso!
Matthew Cornell
5
@MatthewCornell, por f2si só não aceita argumentos. O objeto de função que ele retorna sim.
missingfaktor
122

Dentro de uma classe, valé avaliado na inicialização, enquanto defé avaliado apenas quando, e todas as vezes , a função é chamada. No código abaixo, você verá que x é avaliado na primeira vez que o objeto é usado, mas não novamente quando o membro x é acessado. Em contraste, y não é avaliado quando o objeto é instanciado, mas é avaliado toda vez que o membro é acessado.

  class A(a: Int) {
    val x = { println("x is set to something"); a }
    def y = { println("y is set to something"); a }
  }

  // Prints: x is set to something
  val a = new A(1)

  // Prints: "1"
  println(a.x)

  // Prints: "1"                               
  println(a.x)

  // Prints: "y is set to something" and "1"                                  
  println(a.y)

  // Prints: "y is set to something" and "1"                                                                                   
  println(a.y)
Jack
fonte
@JacobusR isso é verdade apenas dentro de uma classe?
Andrew Cassidy
por exemplo: escala> var b = 5 b: Int = 5 escala> val a: (Int => Int) = x => x + ba: Int => Int = <função1> escala> a (5) res48: Int = 10 escala> b = 6 b: Int = 6 escala> a (5) res49: Int = 11 Eu esperava que a (5) retornasse 10 e o valor de b fosse embutido
Andrew Cassidy
@AndrewCassidy a função aé imutável e avaliada na inicialização, mas bpermanece um valor mutável. Portanto, a referência a bé definida durante a inicialização, mas o valor armazenado por bpermanece mutável. Para se divertir, agora você pode criar um novo val b = 123. Depois disso, você a(5)sempre dará 11, pois bagora é um valor completamente novo.
Jack
@JacobusR obrigado ... isso faz sentido. Isso coincide com a definição de "escopo léxico", uma vez que a função a carrega uma referência ao "var b" original. Acho que o que me deixou confuso é que dizer: var b = 5; val c = b; b = 6; age de forma diferente. Acho que não devo esperar que uma definição de função que contenha referências ao escopo "léxico" original se comporte da mesma maneira que um Int.
Andrew Cassidy
3

Executar uma definição como def x = e não avaliará a expressão e . Em vez disso, e é avaliado sempre que x é usado. Alternativamente, Scala oferece uma definição de valor val x = e , que avalia o lado direito e como parte da avaliação da definição. Se x for usado subsequentemente, ele será imediatamente substituído pelo valor pré-calculado de e , de modo que a expressão não precise ser avaliada novamente.

Scala por exemplo de Martin Odersky

Alexandre
fonte