40

OOとFPのパラダイムを統合するためにScalaとF#が採用したアプローチの主な違いは何ですか?

編集

各アプローチの相対的な長所と短所は何ですか?サブタイピングがサポートされているにもかかわらず、F#が関数の引数の型を推測できる場合、なぜScalaを使用できないのでしょうか。

4

7 に答える 7

43

私はF#を見て、低レベルのチュートリアルを行っているので、それについての知識は非常に限られています。しかし、そのスタイルは本質的に機能的であり、OOはアドオンのようなものであり、真のOOよりもはるかに多くのADT+モジュールシステムであることが私には明らかでした。私が得た感覚は、その中のすべてのメソッドが静的であるかのように最もよく説明できます(Java静的のように)。

たとえば、パイプ演算子(|>)を使用するコードを参照してください。F#のウィキペディアエントリからこのスニペットを取得します。

[1 .. 10]
|> List.map     fib

(* equivalent without the pipe operator *)
List.map fib [1 .. 10]

この関数mapはリストインスタンスのメソッドではありません。代わりに、Listパラメータの1つとしてリストインスタンスを受け取るモジュールの静的メソッドのように機能します。

一方、Scalaは完全にOOです。まず、そのコードに相当するScalaから始めましょう。

List(1 to 10) map fib

// Without operator notation or implicits:
List.apply(Predef.intWrapper(1).to(10)).map(fib)

ここにmap、のインスタンスのメソッドがありますListintWrapperonPredefapplyonなどの静的な方法は、Listはるかに一般的ではありません。次に、fib上記のような機能があります。ここでfibは、はのメソッドではありませんintが、静的メソッドでもありません。代わりに、これはオブジェクトです。F#とScalaの2番目の主な違いです。

ウィキペディアのF#実装と、同等のScala実装について考えてみましょう。

// F#, from the wiki
let rec fib n =
    match n with
    | 0 | 1 -> n
    | _ -> fib (n - 1) + fib (n - 2)

// Scala equivalent
def fib(n: Int): Int = n match {
  case 0 | 1 => n
  case _ => fib(n - 1) + fib(n - 2)
}

上記のScala実装はメソッドですが、Scalaはそれを関数に変換してに渡すことができmapます。以下では、関数がScalaでどのように機能するかを示すために、代わりに関数を返すメソッドになるように変更します。

// F#, returning a lambda, as suggested in the comments
let rec fib = function 
    | 0 | 1 as n -> n 
    | n -> fib (n - 1) + fib (n - 2)

// Scala method returning a function
def fib: Int => Int = {
  case n @ (0 | 1) => n
  case n => fib(n - 1) + fib(n - 2)
}

// Same thing without syntactic sugar:
def fib = new Function1[Int, Int] {
  def apply(param0: Int): Int = param0 match {
    case n @ (0 | 1) => n
    case n => fib.apply(n - 1) + fib.apply(n - 2)
  }
}

したがって、Scalaでは、すべての関数はFunctionX、と呼ばれるメソッドを定義するトレイトを実装するオブジェクトですapply。ここと上記のリスト作成に示されているように、.applyは省略できます。これにより、関数呼び出しはメソッド呼び出しのように見えます。

結局、Scalaのすべてはオブジェクトであり、クラスのインスタンスであり、そのようなオブジェクトはすべてクラスに属し、すべてのコードはメソッドに属し、何らかの方法で実行されます。match上記の例でも、以前はメソッドでしたが、かなり前にいくつかの問題を回避するためにキーワードに変換されました。

それで、それの機能的な部分はどうですか?F#は、関数型言語の最も伝統的なファミリの1つに属しています。関数型言語にとって重要だと考える機能はいくつかありませんが、実際には、F#はいわばデフォルトで関数です。

一方、Scalaは、言語の個別の部分として提供するのではなく、機能モデルとOOモデルを統合することを目的として作成されました。それが成功した程度は、関数型プログラミングであるとみなすものによって異なります。マーティン・オーダスキーが焦点を当てたもののいくつかを次に示します。

  • 関数は値です。それらもオブジェクトです-すべての値はScalaのオブジェクトであるため-しかし、関数は操作可能な値であるという概念は重要であり、そのルーツは元のLisp実装にまでさかのぼります。

  • 不変のデータ型の強力なサポート。関数型プログラミングは常にプログラムの副作用を減らすことに関心があり、その関数は真の数学関数として分析することができます。そのため、Scalaは物事を不変にすることを容易にしましたが、FPの純粋主義者がそれを批判する2つのことはしませんでした。

    • 可変性を難しくしませんでした。
    • 可変性を静的に追跡できるエフェクトシステムは提供していません。
  • 代数的データ型のサポート。代数的データ型(ADTと呼ばれ、紛らわしいことに抽象データ型の略でもあります)は関数型プログラミングで非常に一般的であり、OO言語でビジターパターンを一般的に使用する状況で最も役立ちます。

    他のすべてと同様に、ScalaのADTはクラスとメソッドとして実装されており、いくつかの構文糖衣を使用して簡単に使用できます。ただし、Scalaは、F#(またはその他の関数型言語)よりもはるかに冗長です。たとえば、|caseステートメントのF#の代わりに、を使用しますcase

  • 非厳密性のサポート。非厳密性とは、オンデマンドでの計算のみを意味します。これはHaskellの本質的な側面であり、副作用システムと緊密に統合されています。ただし、Scalaでは、非厳密なサポートは非​​常に臆病で初期段階です。利用可能で使用されていますが、制限された方法で使用されます。

    たとえば、Scalaの非厳密リストである、は、HaskellがサポートしているようStreamな真に非厳密なリストをサポートしていません。foldRightさらに、非厳密性のいくつかの利点は、オプションではなく、言語のデフォルトである場合にのみ得られます。

  • リスト内包のサポート。実際、Scalaはそれを理解のために呼んでいます。それは、実装方法がリストから完全に切り離されているためです。最も簡単に言えば、リスト内包表記はmap、例に示されている関数/メソッドと考えることができますが、通常、マップステートメントのネスト(flatMapScalaでのサポート)とフィルタリング(filterまたはwithFilter厳密性の要件に応じてScalaでのサポート)が期待されます。

    これは関数型言語では非常に一般的な操作であり、Pythonのin演算子のように構文が軽いことがよくあります。繰り返しになりますが、Scalaは通常よりもやや冗長です。

私の意見では、ScalaはFPとOOの組み合わせにおいて比類のないものです。これは、スペクトルのOO側からFP側に向かって発生しますが、これは珍しいことです。ほとんどの場合、OOに取り組んでいるFP言語を目にしますが、私にはそれに取り組んでいるように感じます。Scala上のFPは、おそらく関数型言語プログラマーにとっても同じように感じていると思います。

編集

他のいくつかの答えを読んで、私は別の重要なトピックがあることに気づきました:型推論。Lispは動的に型付けされた言語であり、それが関数型言語への期待をほぼ設定しました。最新の静的型付け関数型言語はすべて、強力な型推論システムを備えています。ほとんどの場合、Hindley-Milner 1アルゴリズムにより、型宣言は基本的にオプションになります。

Scalaは継承2をサポートしているため、ScalaはHindley-Milnerアルゴリズムを使用できません。そのため、Scalaははるかに強力でない型推論アルゴリズムを採用する必要があります-実際、Scalaの型推論は仕様で意図的に定義されておらず、継続的な改善の対象となっています(この改善は、次の2.8バージョンの最大の機能の1つですたとえば、Scala)。

ただし、最終的には、Scalaでは、メソッドを定義するときにすべてのパラメーターの型を宣言する必要があります。再帰などの状況では、メソッドの戻り型も宣言する必要があります。

ただし、Scalaの関数は、宣言する代わりに型を推測できることがよくあります。たとえば、ここでは型宣言は必要ありません。ここList(1, 2, 3) reduceLeft (_ + _)で、_ + _は実際には型の無名関数ですFunction2[Int, Int, Int]

同様に、変数の型宣言は不要なことがよくありますが、継承では必要になる場合があります。たとえば、Some(2)Noneは共通のスーパークラスを持っていますOptionが、実際には異なるサブクラスに属しています。したがって、通常var o: Option[Int] = None、正しいタイプが割り当てられていることを確認することを宣言します。

この限定された形式の型推論は、静的に型付けされたOO言語が通常提供するよりもはるかに優れており、Scalaに軽快感を与え、静的に型付けされたFP言語が通常提供するよりもはるかに悪く、Scalaに重さを感じさせます。:-)

ノート:

  1. 実際、ウィキペディアによると、このアルゴリズムは「アルゴリズムW」と呼ばれるDamasとMilnerに由来しています。

  2. マーティン・オーダスキーはここのコメントで次のように述べています:

    ScalaにHindley/Milner型推論がない理由は、オーバーロード(型クラスではなく、アドホックバリアント)、レコード選択、サブタイピングなどの機能と組み合わせることが非常に難しいためです。

    彼は続けて、それは実際には不可能ではないかもしれないと述べ、それはトレードオフに帰着しました。詳細については、そのリンクにアクセスしてください。より明確なステートメントを思いついた場合、または、何らかの形で何らかの論文を思いついた場合は、参照していただければ幸いです。

    不可能だと思っていたので、これを調べてくれたJonHarropに感謝します。まあ、多分そうです、そして私は適切なリンクを見つけることができませんでした。ただし、問題の原因は継承だけではないことに注意してください。

于 2010-05-25T21:53:30.570 に答える
14

F#は機能的です-OOはかなりうまく機能しますが、それでもデザインと哲学は機能的です。例:

  • Haskellスタイルの関数
  • 自動カリー化
  • 自動ジェネリック
  • 引数の型推論

F#を主にオブジェクト指向の方法で使用するのは比較的不器用なので、オブジェクト指向を関数型プログラミングに統合するという主な目標を説明できます。

Scalaは、柔軟性に重点を置いたマルチパラダイムです。現在最適なものに応じて、本物のFP、OOP、および手続き型スタイルから選択できます。それは本当にOOと関数型プログラミングを統合することです。

于 2010-05-25T14:30:09.037 に答える
13

2つ(または3つ)を比較するために使用できるポイントはかなりあります。まず、私が考えることができるいくつかの注目すべき点があります:

  • 構文構文
    的には、F#OCamlは関数型プログラミングの伝統(スペースで区切られ、より軽量)に基づいていますが、 Scalaはオブジェクト指向スタイルに基づいています(Scalaはより軽量になっています)。

  • OOとFPの統合​​F#Scala
    は どちらも、OOとFPを非常にスムーズに統合します(これら2つの間に矛盾がないためです!!)不変データ(機能面)を保持するクラスを宣言し、データの操作に関連するメンバーを提供できます。また、抽象化(オブジェクト指向の側面)のためにインターフェースを使用します。私はOCamlにあまり詳しくありませんが、OO側に重点を置いていると思います(F#と比較して)

  • F#でのプログラミングスタイルF#
    で使用される通常のプログラミングスタイル(.NETライブラリを作成する必要がなく、他の制限がない場合)はおそらくより機能的であり、必要な場合にのみOO機能を使用すると思います。 。これは、関数、モジュール、および代数的データ型を使用して機能をグループ化することを意味します。

  • Scalaのプログラミングスタイル
    Scalaでは、デフォルトのプログラミングスタイルは(組織内で)よりオブジェクト指向ですが、「標準」アプローチは突然変異を回避するコードを書くことであるため、関数型プログラムを作成します。

于 2010-05-25T20:14:44.230 に答える
13

OOとFPのパラダイムを統合するためにScalaとF#が採用したアプローチの主な違いは何ですか?

主な違いは、Scalaは(通常はFP側で)犠牲を払ってパラダイムをブレンドしようとするのに対し、F#(およびOCaml)は通常、パラダイム間に線を引き、プログラマーがタスクごとにパラダイムを選択できるようにすることです。

Scalaは、パラダイムを統一するために犠牲を払わなければなりませんでした。例えば:

  • ファーストクラス関数は、関数型言語(ML、Scheme、Haskell)の重要な機能です。すべての関数はF#のファーストクラスです。メンバー関数はScalaでは2番目のクラスです。

  • オーバーロードとサブタイプは型推論を妨げます。F#は、これらのOO機能を犠牲にして、これらの機能が使用されていないときに強力な型推論を提供するための大きなサブ言語を提供します(使用される場合は型注釈が必要です)。Scalaは、どこでも型推論が不十分であるという犠牲を払って一貫したOOを維持するために、これらの機能をどこにでもプッシュします。

これのもう1つの結果は、F#が試行錯誤されたアイデアに基づいているのに対し、Scalaはこの点で先駆者であるということです。これは、プロジェクトの背後にある動機付けに理想的です。F#は商用製品であり、Scalaはプログラミング言語研究です。

余談ですが、Scalaは、選択したVM(JVM)の制限により、実用的な理由で末尾呼び出しの最適化など、FPの他のコア機能も犠牲にしました。これにより、ScalaはFPよりもはるかに多くのOOPになります。Scalaを.NETに導入するプロジェクトがあり、CLRを使用して本物のTCOを実行することに注意してください。

各アプローチの相対的な長所と短所は何ですか?サブタイピングがサポートされているにもかかわらず、F#が関数の引数の型を推測できる場合、なぜScalaを使用できないのでしょうか。

型推論は、オーバーロードやサブタイプなどのOO中心の機能とは相容れません。F#は、オーバーロードに関して一貫性よりも型推論を選択しました。Scalaは、型推論よりもユビキタスなオーバーロードとサブタイプを選択しました。これにより、F#はOCamlに、ScalaはC#に似たものになります。特に、ScalaはC#ほど機能的なプログラミング言語ではありません。

もちろん、どちらが優れているかは完全に主観的ですが、私は個人的に、一般的な場合の強力な型推論から得られる非常に簡潔で明快なものを好みます。OCamlは素晴らしい言語ですが、1つの問題点は、プログラマーが+int、+.float、+/有理数などに使用する必要のある演算子のオーバーロードがないことでした。繰り返しになりますが、F#は、算術演算子だけでなく、などの算術関数でも、特に数値のコンテキストでオーバーロードするために型推論を犠牲にすることで、執着よりも実用主義を選択しますsin。F#言語の隅々まで、このような慎重に選択された実用的なトレードオフの結果です。結果として生じる不整合にもかかわらず、これによりF#がはるかに便利になると思います。

于 2010-06-04T21:35:20.083 に答える
7

プログラミング言語に関するこの記事から:

Scalaは、Javaの頑丈で表現力豊かな、厳密に優れた代替品です。Scalaは、WebサーバーやIRCクライアントの作成などのタスクに使用するプログラミング言語です。 オブジェクト指向システムが移植された関数型言語であったOCaml[またはF#]とは対照的に、Scalaはオブジェクト指向プログラミングと関数型プログラミングの真のハイブリッドのように感じます。(つまり、オブジェクト指向プログラマーは、Scalaの使用をすぐに開始でき、必要に応じて機能部分を選択できるはずです。)

私は、マーティン・オーダスキーが招待講演を行ったときに、POPL2006でScalaについて最初に知りました。当時、関数型プログラミングはオブジェクト指向プログラミングよりも厳密に優れていると思っていたので、関数型プログラミングとオブジェクト指向プログラミングを融合させた言語は必要ないと思いました。(それはおそらく、当時私が書いたのはコンパイラー、インタープリター、静的アナライザーだけだったからでしょう。)

Scalaの必要性は、私がyaplet用の長いポーリングされたAJAXをサポートするために、並行HTTPDを最初から作成するまで私には明らかになりませんでした。優れたマルチコアサポートを得るために、私はJavaで最初のバージョンを作成しました。言語としては、Javaはそれほど悪いとは思いませんし、よくできたオブジェクト指向プログラミングを楽しむことができます。しかし、関数型プログラマーとして、私がJavaでプログラミングするとき、関数型プログラミング機能(高階関数など)のサポートの欠如(または不必要に冗長な)は私を苦しめます。それで、私はScalaにチャンスを与えました。

ScalaはJVM上で実行されるので、既存のプロジェクトを徐々にScalaに移植することができました。また、Scalaは、それ自体のかなり大きなライブラリに加えて、Javaライブラリ全体にもアクセスできることを意味します。これは、Scalaで実際の作業を行うことができることを意味します。

Scalaを使い始めたとき、機能的な世界とオブジェクト指向の世界がいかに巧妙に融合しているかに感銘を受けました。特に、Scalaには強力なケースクラス/パターンマッチングシステムがあり、Standard ML、OCaml、Haskellでの私の経験から残っているペットのおしっこに対処しました。プログラマーは、オブジェクトのどのフィールドを一致させるかを決定できます(強制的に一致させるのではなく)それらのすべてで)、および可変アリティ引数が許可されます。実際、Scalaではプログラマー定義のパターンも使用できます。私は抽象構文ノードで動作する関数をたくさん書いています。構文の子だけで一致できるのはいいことですが、元のプログラムには注釈や行などのフィールドがあります。ケースクラスシステムでは、代数的データ型の定義を複数のファイル間または同じファイルの複数の部分に分割できます。これは非常に便利です。

Scalaは、トレイトと呼ばれるクラスのようなデバイスを介して、明確に定義された多重継承もサポートします。

Scalaは、かなりの程度のオーバーロードも可能にします。関数アプリケーションと配列更新でさえオーバーロードされる可能性があります。私の経験では、これは私のScalaプログラムをより直感的で簡潔にする傾向があります。

型クラスがHaskellでコードを保存するのと同じように、多くのコードを保存することが判明した1つの機能は、暗黙的です。タイプチェッカーのエラー回復フェーズのAPIとして暗黙的を想像することができます。つまり、型チェッカーがXを必要としているがYを取得した場合、YをXに変換する暗黙の関数がスコープ内にあるかどうかを確認します。見つかった場合は、暗黙を使用して「キャスト」します。これにより、Scalaのほぼすべてのタイプを拡張しているように見せることができ、DSLのより緊密な埋め込みが可能になります。

上記の抜粋から、OOとFPのパラダイムを統合するScalaのアプローチは、OCamlやF#のアプローチよりもはるかに優れていることが明らかです。

HTH。

よろしく、
エリック。

于 2010-05-26T07:02:07.363 に答える
5

F#の構文はOCamlから取得されましたが、F#のオブジェクトモデルは.NETから取得されました。これにより、F#は関数型プログラミング言語の特徴である簡潔で簡潔な構文を実現すると同時に、F#がオブジェクトモデルを介して既存の.NET言語および.NETライブラリと非常にスムーズに相互運用できるようになります。

Scalaは、F#がCLRで実行するのと同様のジョブをJVMで実行します。ただし、ScalaはよりJavaに似た構文を採用することを選択しました。これは、オブジェクト指向プログラマーによる採用に役立つかもしれませんが、関数型プログラマーにとっては少し重く感じることがあります。そのオブジェクトモデルは、Javaとのシームレスな相互運用を可能にするJavaに似ていますが、特性のサポートなど、いくつかの興味深い違いがあります。

于 2010-05-25T16:37:46.170 に答える
3

関数型プログラミングが関数を使ったプログラミングを意味する場合、Scalaはそれを少し曲げます。Scalaでは、私が正しく理解していれば、関数ではなくメソッドを使用してプログラミングしていることになります。

メソッドの背後にあるクラス(およびそのクラスのオブジェクト)が重要でない場合、Scalaはそれが単なる関数であるかのように見せかけます。おそらく、Scala言語の弁護士は、この区別(区別である場合でも)とその結果について詳しく説明することができます。

于 2010-05-25T20:49:50.313 に答える