Diferença entre método e função no Scala


254

Eu li Funções do Scala (parte de Outra turnê do Scala ). Nesse post, ele declarou:

Métodos e funções não são a mesma coisa

Mas ele não explicou nada sobre isso. O que ele estava tentando dizer?



3
Eu acho que você pode obter algo de Qual é a diferença entre um método e uma função
jinglining

Uma pergunta de acompanhamento com boas respostas: Funções vs métodos em Scala
Josias Yoder

Respostas:


238

Jim tem isso praticamente coberto em seu blog , mas estou postando um briefing aqui para referência.

Primeiro, vamos ver o que a especificação Scala nos diz. O Capítulo 3 (tipos) nos fala sobre Tipos de Função (3.2.9) e Tipos de Método (3.3.1). O Capítulo 4 (declarações básicas) fala de Declaração e Definições de Valor (4.1), Declaração e Definições Variáveis (4.2) e Declarações e Definições de Funções (4.6). O capítulo 6 (expressões) fala de funções anônimas (6.23) e valores de métodos (6.7). Curiosamente, os valores das funções são falados uma vez no 3.2.9, e nenhum outro lugar.

Um Tipo de Função é (aproximadamente) um tipo do formulário (T1, ..., Tn) => U , que é uma abreviação para a característica FunctionNna biblioteca padrão. Funções anônimas e valores de método têm tipos de função, e os tipos de função podem ser usados ​​como parte do valor, das declarações e definições de variáveis ​​e funções. De fato, ele pode fazer parte de um tipo de método.

Um Tipo de método é um tipo sem valor . Isso significa que não valor - nenhum objeto, nenhuma instância - com um tipo de método. Como mencionado acima, um valor de método realmente tem um tipo de função . Um tipo de método é uma defdeclaração - tudo sobre a, defexceto seu corpo.

Valor declarações e definições e declarações variáveis e definições são vale vardeclarações, incluindo tanto o tipo eo valor - que pode ser, respectivamente, tipo de função e anônimos funções ou valores Método . Observe que, na JVM, esses (valores de método) são implementados com o que Java chama de "métodos".

Uma declaração de função é uma defdeclaração, incluindo tipo e corpo . A parte do tipo é o Tipo de método e o corpo é uma expressão ou um bloco . Isso também é implementado na JVM com o que Java chama de "métodos".

Finalmente, uma função anônima é uma instância de um tipo de função (ou seja, uma instância da característica FunctionN), e um valor de método é a mesma coisa! A distinção é que um valor de método é criado a partir de métodos, postfixando um sublinhado ( m _é um valor de método correspondente à "declaração de função" ( def) m) ou por um processo chamado eta-expansão , que é como uma conversão automática do método funcionar.

Isso é o que dizem as especificações, então deixe-me colocar isso de frente: não usamos essa terminologia! Isso gera muita confusão entre a chamada "declaração de função" , que faz parte do programa (capítulo 4 - declarações básicas) e "função anônima" , que é uma expressão, e "tipo de função" , que é, bem um tipo - uma característica.

A terminologia abaixo, e usada por programadores Scala experientes, faz uma alteração na terminologia da especificação: em vez de dizer a declaração da função , dizemos método . Ou mesmo declaração de método. Além disso, notamos que declarações de valor e declarações de variáveis também são métodos para fins práticos.

Portanto, dada a mudança acima na terminologia, aqui está uma explicação prática da distinção.

Uma função é um objecto que inclui uma das FunctionXcaracterísticas, tais como Function0, Function1, Function2, etc. Pode-se incluindo PartialFunction, assim, que, na verdade, se estende Function1.

Vamos ver a assinatura do tipo para uma dessas características:

trait Function2[-T1, -T2, +R] extends AnyRef

Essa característica tem um método abstrato (também tem alguns métodos concretos):

def apply(v1: T1, v2: T2): R

E isso nos diz tudo o que há para saber sobre isso. Uma função possui um applymétodo que recebe N parâmetros dos tipos T1 , T2 , ..., TN e retorna algo do tipo R. É contra-variante nos parâmetros que recebe e co-variante no resultado.

Essa variação significa que a Function1[Seq[T], String]é um subtipo de Function1[List[T], AnyRef]. Ser um subtipo significa que pode ser usado no lugar dele. Pode-se ver facilmente que, se vou ligar f(List(1, 2, 3))e esperar uma AnyRefresposta, qualquer um dos dois tipos acima funcionaria.

Agora, qual é a semelhança de um método e uma função? Bem, se fé uma função e mé um método local para o escopo, ambos podem ser chamados assim:

val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))

Essas chamadas são realmente diferentes, porque a primeira é apenas um açúcar sintático. Scala a expande para:

val o1 = f.apply(List(1, 2, 3))

Que, é claro, é uma chamada de método ao objeto f. As funções também têm outros açúcares sintáticos a seu favor: literais de função (dois deles, na verdade) e (T1, T2) => Rassinaturas de tipo. Por exemplo:

val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
  case i: Int => "Int"
  case d: Double => "Double"
  case o => "Other"
}

Outra semelhança entre um método e uma função é que o primeiro pode ser facilmente convertido no último:

val f = m _

O Scala expandirá isso , assumindo que o mtipo seja (List[Int])AnyRef(Scala 2.7):

val f = new AnyRef with Function1[List[Int], AnyRef] {
  def apply(x$1: List[Int]) = this.m(x$1)
}

No Scala 2.8, ele realmente usa uma AbstractFunction1classe para reduzir o tamanho das classes.

Observe que não é possível converter o contrário - de uma função para um método.

Os métodos, no entanto, têm uma grande vantagem (bem, duas - eles podem ser um pouco mais rápidos): eles podem receber parâmetros de tipo . Por exemplo, embora facima possa necessariamente especificar o tipo de Listrecebimento ( List[Int]no exemplo), mpode parametrizar:

def m[T](l: List[T]): String = l mkString ""

Acho que isso cobre tudo, mas terei prazer em complementar isso com respostas a quaisquer perguntas que possam permanecer.


26
Esta explicação é muito clara. Bem feito. Infelizmente, o livro Odersky / Venners / Spoon e a especificação Scala usam as palavras "função" e "método" de maneira um tanto intercambiável. (Eles são mais propensos a dizer "função", onde "método" seria mais claro, mas às vezes acontece o contrário, por exemplo, a seção 6.7 da especificação, que abrange a conversão de métodos em funções, é denominada "Valores do método". .) Eu acho que o uso frouxo dessas palavras causou muita confusão quando as pessoas tentam aprender o idioma.
Seth Tisue

4
@Seth eu sei, eu sei - PinS foi o livro que me ensinou Scala. Aprendi melhor da maneira mais difícil, ou seja, a paulp me corrigiu.
Daniel C. Sobral

4
Ótima explicação! Eu tenho uma coisa a acrescentar: Quando você cita a expansão de val f = mpelo compilador, como val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }você deve apontar que o thisinterior do applymétodo não se refere ao AnyRefobjeto, mas ao objeto em cujo método o val f = m _é avaliado (o externo this , por assim dizer) ), uma vez que thisestá entre os valores capturados pelo fechamento (como por exemplo, returnconforme indicado abaixo).
precisa saber é o seguinte

1
@ DanielC.Sobral, qual é o livro do PinS que você mencionou? Também estou interessado em aprender Scala e não encontrei um livro com esse nome,
tldr 18/06/2013

5
@tldr Programação em Scala , por Odersky e tudo. É a abreviação comum para ele (que me disse que não gostou muito PiS por algum motivo :)!
Daniel C. Sobral

67

Uma grande diferença prática entre um método e uma função é o que returnsignifica. returnsempre retorna de um método. Por exemplo:

scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
       val f = () => { return "test" }
                       ^

Retornar de uma função definida em um método gera um retorno não local:

scala> def f: String = {                 
     |    val g = () => { return "test" }
     | g()                               
     | "not this"
     | }
f: String

scala> f
res4: String = test

Considerando que retornar de um método local somente retorna desse método.

scala> def f2: String = {         
     | def g(): String = { return "test" }
     | g()
     | "is this"
     | }
f2: String

scala> f2
res5: String = is this

9
Isso ocorre porque o retorno é capturado pelo fechamento.
Daniel C. Sobral

4
Não consigo pensar em uma única vez que eu gostaria de 'retornar' de uma função para um escopo não-local. Na verdade, posso ver isso como um grave problema de segurança, se uma função puder apenas decidir que deseja voltar mais à pilha. Parece um longjmp, apenas muito mais fácil de acidentalmente errar. Notei que o scalac não me deixa voltar de funções, no entanto. Isso significa que essa abominação foi removida do idioma?
Root'

2
@root - que tal voltar de dentro de um for (a <- List(1, 2, 3)) { return ... }? Isso é retirado do açúcar até o fechamento.
Ben Lings

Hmm ... Bem, esse é um caso de uso razoável. Ainda tem o potencial de levar a problemas horríveis de difícil depuração, mas isso o coloca em um contexto mais sensato.
Root'

1
Honestamente, eu usaria sintaxe diferente. ter returndevolver um valor da função, e alguma forma de escapeou breakou continuepara voltar a partir de métodos.
Ryan The Leach

38

função Uma função pode ser chamada com uma lista de argumentos para produzir um resultado. Uma função possui uma lista de parâmetros, um corpo e um tipo de resultado. As funções que são membros de uma classe, característica ou objeto singleton são chamadas de métodos . Funções definidas dentro de outras funções são chamadas de funções locais. As funções com o tipo de resultado Unidade são chamadas procedimentos. Funções anônimas no código fonte são chamadas literais de função. No tempo de execução, literais de função são instanciados em objetos chamados valores de função.

Programação no Scala Second Edition. Martin Odersky - Colher Lex - Bill Venners


1
Uma função pode pertencer a uma classe como def ou como val / var. Somente os def's são métodos.
19379 Josiah Yoder

29

Vamos dizer que você tem uma lista

scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

Definir um método

scala> def m1(i:Int)=i+2
m1: (i: Int)Int

Definir uma função

scala> (i:Int)=>i+2
res0: Int => Int = <function1>

scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)

Método de aceitação de argumento

scala> m1(2)
res3: Int = 4

Definindo Função com val

scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>

O argumento para funcionar é opcional

 scala> p(2)
    res4: Int = 4

scala> p
res5: Int => Int = <function1>

O argumento para o método é obrigatório

scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function

Verifique o tutorial a seguir, que explica a passagem de outras diferenças com exemplos, como outro exemplo de diff, com o método Vs Function, Using function as Variables, criando a função que retornou a função


13

As funções não suportam padrões de parâmetro. Métodos fazem. A conversão de um método para uma função perde os padrões dos parâmetros. (Scala 2.8.1)


5
Há razão para isso?
Corazza

7

Há um belo artigo aqui, do qual a maioria das minhas descrições são tiradas. Apenas uma pequena comparação de Funções e Métodos em relação ao meu entendimento. Espero que ajude:

Funções : Eles são basicamente um objeto. Mais precisamente, funções são objetos com um método apply; Portanto, eles são um pouco mais lentos que os métodos por causa de sua sobrecarga. É semelhante aos métodos estáticos no sentido de que eles são independentes de um objeto a ser invocado. Um exemplo simples de uma função é como abaixo:

val f1 = (x: Int) => x + x
f1(2)  // 4

A linha acima não é nada, exceto atribuir um objeto a outro, como objeto1 = objeto2. Na verdade, o objeto2 em nosso exemplo é uma função anônima e o lado esquerdo obtém o tipo de um objeto por causa disso. Portanto, agora f1 é um objeto (Função). A função anônima é na verdade uma instância da Função1 [Int, Int] que significa uma função com 1 parâmetro do tipo Int e valor de retorno do tipo Int. Chamar f1 sem os argumentos nos dará a assinatura da função anônima (Int => Int =)

Métodos : Eles não são objetos, mas atribuídos a uma instância de uma classe, ou seja, um objeto. Exatamente o mesmo que o método em java ou funções-membro em c ++ (como Raffi Khatchadourian apontou em um comentário a esta pergunta ) e etc. Um exemplo simples de método é o seguinte:

def m1(x: Int) = x + x
m1(2)  // 4

A linha acima não é uma atribuição simples de valor, mas uma definição de um método. Quando você chama esse método com o valor 2 como a segunda linha, x é substituído por 2 e o resultado será calculado e você obtém 4 como saída. Aqui você receberá um erro se simplesmente escrever m1 porque é um método e precisa do valor de entrada. Usando _, você pode atribuir um método a uma função como abaixo:

val f2 = m1 _  // Int => Int = <function1>

O que significa "atribuir um método a uma função"? Significa apenas que agora você tem um objeto que se comporta da mesma maneira que o método?
K. M

@KM: val f2 = m1 _ é equivalente a val f2 = nova Função1 [Int, Int] {def m1 (x: Int) = x + x};
sasuke

3

Aqui está um ótimo post de Rob Norris, que explica a diferença, aqui está um TL; DR

Métodos em Scala não são valores, mas funções. Você pode construir uma função que delega para um método via expansão-η (acionada pelo sublinhado à direita).

com a seguinte definição:

um método é algo definido com def e um valor é algo que você pode atribuir a um val

Em poucas palavras ( extrato do blog ):

Quando definimos um método, vemos que não podemos atribuí-lo a a val.

scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int

scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
       val f = add1

Observe também o tipo de add1, que não parece normal; você não pode declarar uma variável do tipo (n: Int)Int. Métodos não são valores.

No entanto, adicionando o operador postfix de expansão η (η é pronunciado “eta”), podemos transformar o método em um valor de função. Anote o tipo de f.

scala> val f = add1 _
f: Int => Int = <function1>

scala> f(3)
res0: Int = 4

O efeito de _é executar o equivalente ao seguinte: construímos uma Function1instância que delega ao nosso método.

scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>

scala> g(3)
res18: Int = 4

1

No Scala 2.13, diferentemente das funções, os métodos podem receber / retornar

  • parâmetros de tipo (métodos polimórficos)
  • parâmetros implícitos
  • tipos dependentes

No entanto, essas restrições são levantadas em dotty (Scala 3) pelos tipos de função polimórficos # 4672 , por exemplo, a versão 0.23.0-RC1 dotty habilita a seguinte sintaxe

Parâmetros de tipo

def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))

Parâmetros implícitos ( parâmetros de contexto )

def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero

Tipos dependentes

class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet

Para mais exemplos, consulte tests / run / polymorphic-functions.scala


0

Praticamente, um programador Scala precisa conhecer apenas as três regras a seguir para usar funções e métodos corretamente:

  • Os métodos definidos por defe literais de função definidos por =>são funções. Está definido na página 143, capítulo 8 do livro de programação em Scala, 4ª edição.
  • Valores de função são objetos que podem ser passados ​​como quaisquer valores. Literais de função e funções parcialmente aplicadas são valores de função.
  • Você pode deixar o sublinhado de uma função parcialmente aplicada se um valor de função for necessário em um ponto do código. Por exemplo:someNumber.foreach(println)

Após quatro edições da Programação em Scala, ainda é um problema para as pessoas diferenciarem os dois conceitos importantes: função e valor da função, porque todas as edições não dão uma explicação clara. A especificação da linguagem é muito complicada. Eu descobri que as regras acima são simples e precisas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.