=>、()=>、およびUnit =>の違いは何ですか


153

引数を取らず、値を返さない関数を表現しようとしています(知っている必要がある場合は、JavaScriptでsetTimeout関数をシミュレートしています)。

case class Scheduled(time : Int, callback :  => Unit)

「 `val 'パラメータは名前による呼び出しではない可能性があります」と言ってコンパイルしません

case class Scheduled(time : Int, callback :  () => Unit)  

コンパイルしますが、代わりに奇妙に呼び出す必要があります

Scheduled(40, { println("x") } )

私はこれをしなければなりません

Scheduled(40, { () => println("x") } )      

また、うまくいくのは

class Scheduled(time : Int, callback :  Unit => Unit)

しかし、あまり意味のない方法で呼び出されます

 Scheduled(40, { x : Unit => println("x") } )

(Unit型の変数は何でしょうか?)私は何をしたいもちろんこと、それは普通の関数だった場合、私はそれを呼び出すような方法することができたinvokeコンストラクタです。

 Scheduled(40, println("x") )

赤ちゃんに彼のボトルをあげよう!


3
名前によるパラメーターを持つケースクラスを使用する別の方法は、それらを2番目のパラメーターリスト(例:)に配置することcase class Scheduled(time: Int)(callback: => Unit)です。セカンダリパラメータリストは公開されておらず、生成されたequals/ hashCodeメソッドにも含まれていないため、これは機能します。
nilskp

名前によるパラメータと0のアリティ関数の違いに関するいくつかの興味深い側面は、この質問と回答にあります。それは私がこの質問を見つけたときに私が実際に探していたものです。
lex82

回答:


234

名前による呼び出し:=>タイプ

=> Type表記は、コール・バイ・名前の一つである、の略で多くの方法のパラメータが渡されます。それらに慣れていない場合は、今日のほとんどが値渡しと参照渡しであるにもかかわらず、ウィキペディアの記事を読むことをお勧めします。

つまり、渡されたものが関数内の値の名前に置き換えられます。たとえば、次の関数を使用します。

def f(x: => Int) = x * x

このように呼べば

var y = 0
f { y += 1; y }

次に、コードはこのように実行されます

{ y += 1; y } * { y += 1; y }

ただし、識別子名の競合が発生した場合に何が起こるかという点が浮上します。従来の名前による呼び出しでは、名前の競合を回避するために、キャプチャ回避置換と呼ばれるメカニズムが実行されます。ただし、Scalaでは、これは別の方法で実装され、同じ結果になります。パラメーター内の識別子名は、呼び出された関数の識別子を参照したり、シャドウしたりできません。

名前による呼び出しに関連する他のポイントがいくつかありますが、他の2つについては後で説明します。

0アリティ関数:()=>タイプ

構文() => Typeはのタイプを表しますFunction0。つまり、パラメータをとらずに何かを返す関数です。これは、たとえば、メソッドを呼び出すことと同じsize()です。パラメータをとらず、数値を返します。

しかし、興味深いのは、この構文が無名関数リテラルの構文と非常に似ていることです。これが混乱の原因です。例えば、

() => println("I'm an anonymous function")

アリティ0の無名関数リテラルで、

() => Unit

だから私たちは書くことができます:

val f: () => Unit = () => println("I'm an anonymous function")

ただし、タイプと値を混同しないようにすることが重要です。

ユニット=>タイプ

これは実際には単なるでありFunction1、最初のパラメータは型Unitです。それを書く他の方法は、(Unit) => TypeまたはでしょうFunction1[Unit, Type]。事は...これが人が望んでいるものである可能性は低いです。Unitタイプの主な目的は、そうしても意味がありません、に興味を持っていない値の1を表示している受け取るその値を。

たとえば、

def f(x: Unit) = ...

何ができるxでしょうか?値は1つだけなので、受け取る必要はありません。考えられる用途の1つは、次を返す関数のチェーンですUnit

val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g

のでandThenのみに定義されているFunction1、と我々はチェーン化されている関数が戻ってきているUnit、我々はタイプとしてそれらを定義しなければならなかったFunction1[Unit, Unit]チェーンそれらのことができるようにします。

混乱の原因

混乱の最初の原因は、0アリティ関数に存在するタイプとリテラルの類似性が名前による呼び出しにも存在すると考えることです。言い換えれば、

() => { println("Hi!") }

のリテラルである() => Unit場合、

{ println("Hi!") }

のリテラルになり=> Unitます。そうではない。これは、リテラルではなくコードのブロックです

混乱のもう1つの原因は、Unit型のがに書き込まれることです()。これは、アリティ0のパラメーターリストのように見えます(ただし、そうではありません)。


2年後、最初に反対票を投じなければならないかもしれません。誰かがクリスマスのcase =>構文について不思議に思っています、そして私はこの答えを標準的で完全なものとしてお勧めすることはできません!来る世界は何ですか?たぶん、マヤ人は一週間だけ離れていました。彼らはうるう年を正しく計算しましたか?夏時間?
som-snytt

@ som-snyttさて、質問はについて尋ねcase ... =>なかったので、言及しませんでした。悲しいですが本当。:-)
Daniel C. Sobral

1
@Daniel C. Sobralは、「これはリテラルではなくコードのブロックです」と説明していただけませんか。部。では、2つの正確な違いは何ですか?
nish1013 2013

2
@ nish1013「リテラル」は値です(一部の例1では、整数、文字'a'、文字列"abc"、または関数() => println("here"))。引数として渡したり、変数などに格納したりできます。「コードのブロック」は、ステートメントの構文上の区切りです。値ではなく、渡したりすることはできません。
ダニエルC.ソブラル2013

1
@Alexこれは(Unit) => Typevs と同じ違いです() => Type-最初のはFunction1[Unit, Type]、2番目のはFunction0[Type]です。
ダニエルC.ソブラル2014年

36
case class Scheduled(time : Int, callback :  => Unit)

case修飾子は、暗黙的になりvalコンストラクタに各引数のうちに。したがって、(誰かが指摘したように)削除するcase場合は、名前による呼び出しパラメーターを使用できます。コンパイラはおそらくとにかくそれを許可することができますが、val callbackにモーフィングする代わりに作成した場合、人々を驚かせるかもしれませんlazy val callback

あなたがcallback: () => Unit今に変更すると、あなたのケースはcall-by-nameパラメータではなく関数をとります。もちろん関数を格納できるval callbackので問題ありません。

必要なものを取得する最も簡単な方法(Scheduled(40, println("x") )call-by-nameパラメーターを使用してラムダを渡す場合)は、おそらくをスキップして、最初に取得できなかっcaseapplyを明示的に作成することです。

class Scheduled(val time: Int, val callback: () => Unit) {
    def doit = callback()
}

object Scheduled {
    def apply(time: Int, callback: => Unit) =
        new Scheduled(time, { () => callback })
}

使用中で:

scala> Scheduled(1234, println("x"))
res0: Scheduled = Scheduled@5eb10190

scala> Scheduled(1234, println("x")).doit
x

3
ケースクラスのままにして、デフォルトの適用をオーバーライドするだけではどうですか?また、コンパイラーはby-nameをレイジーvalに変換できません。これらは本質的に異なるセマンティクスを持ち、lazyは1回限りであり、by-nameはすべての参照を持っています
Viktor Klang

@ViktorKlangケースクラスのデフォルトのapplyメソッドをどのようにオーバーライドできますか?stackoverflow.com/questions/2660975/…–
Sawyer、

オブジェクトClassName {def apply(…):…=…}
Viktor Klang

4年後、私が選択した答えは、タイトルの質問にのみ答えたものであり、実際に持っていたもの(これは答えます)ではなかったことがわかりました。
Malvolio 2015

1

問題は、JavaScriptでSetTimeOut関数をシミュレートすることです。以前の回答に基づいて、次のコードを記述します。

class Scheduled(time: Int, cb: => Unit) {
  private def runCb = cb
}

object Scheduled {
  def apply(time: Int, cb: => Unit) = {
    val instance = new Scheduled(time, cb)
    Thread.sleep(time*1000)
    instance.runCb
  }
}

REPLでは、次のようになります。

scala> Scheduled(10, println("a")); Scheduled(1, println("b"))
a
b

シミュレーションはブロッキング関数であるため、シミュレーションはSetTimeOutとまったく同じようには動作しませんが、SetTimeOutはノンブロッキングです。


0

私はこのようにしています(適用を中断したくないだけです)。

case class Thing[A](..., lazy: () => A) {}
object Thing {
  def of[A](..., a: => A): Thing[A] = Thing(..., () => a)
}

それを呼ぶ

Thing.of(..., your_value)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.