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?
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?
Respostas:
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 FunctionN
na 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 há 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 def
declaração - tudo sobre a, def
exceto seu corpo.
Valor declarações e definições e declarações variáveis e definições são val
e var
declaraçõ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 def
declaraçã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 FunctionX
caracterí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 apply
mé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 AnyRef
resposta, 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) => R
assinaturas 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 m
tipo 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 AbstractFunction1
classe 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 f
acima possa necessariamente especificar o tipo de List
recebimento ( List[Int]
no exemplo), m
pode 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.
val f = m
pelo 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 this
interior do apply
método não se refere ao AnyRef
objeto, mas ao objeto em cujo método o val f = m _
é avaliado (o externo this
, por assim dizer) ), uma vez que this
está entre os valores capturados pelo fechamento (como por exemplo, return
conforme indicado abaixo).
Uma grande diferença prática entre um método e uma função é o que return
significa. return
sempre 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
for (a <- List(1, 2, 3)) { return ... }
? Isso é retirado do açúcar até o fechamento.
return
devolver um valor da função, e alguma forma de escape
ou break
ou continue
para voltar a partir de métodos.
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
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
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>
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 Function1
instâ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
No Scala 2.13, diferentemente das funções, os métodos podem receber / retornar
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
Praticamente, um programador Scala precisa conhecer apenas as três regras a seguir para usar funções e métodos corretamente:
def
e 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.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.