web-dev-qa-db-ja.com

高レベルのプログラミングの観点から見ると、C#とF#の間の「異なるパラダイム」の障壁はどこで実際に発生しますか?

どちらも異なるプログラミングパラダイムを使用していることは承知していますが、高いレベルの観点からは、構文の違いは別として、ほとんどの基本的なタスクは同様の方法で達成できるようです。

Haskellなどの関数型プログラミング言語に触れたことがあるのですが、基本的なタスクのコードを書くことは(最初は)難しく、イライラし、まったく異なる考え方が必要だったためです。

たとえば、次の例では、再帰構文を使用するのに時間がかかるようになりました。

loop :: Int -> IO ()
loop n = if 0 == n then return () else loop (n-1)

F#ループが認識され、ほとんどすぐに理解できる場合:

let list1 = [ 1; 5; 100; 450; 788 ]
for i in list1 do
   printfn "%d" i

C#プログラマーがF#の学習を始めるときは、思考パターンを完全に再考することをお勧めします(これはHaskellには必ず必要でした)が、実際のタスクを実行するための条件、ループ、データセットなどを扱ういくつかのF#プログラムを書きました。 「パラダイムの違い」の障壁が実際にどこで発生するのでしょうか。

6
FBryant87

F#はオブジェクト指向プログラミングをサポートしていますが、OOPは実際には慣用的なF#ではありません。OOPはデータと動作のバンドルを促進し、関数型プログラミングはそれらを分離することを促進します。

_System.Collections.Generic.List<T>_クラスと_Microsoft.FSharp.Collections.List<'T>_クラスの違いを見てください。

通常(OOP)クラスには、List<T>.Add(T)などのメソッドと拡張メソッドの大きなリストがあります。対照的に、F#リストにはいくつかのプロパティしかありませんが、関連するすべての関数はListモジュールにグループ化されています。

おなじみのOOPの例を見てみましょう:

_public interface IAnimal
{
    void Speak();
}

public class Duck : IAnimal
{
    public void Speak()
    {
        Console.WriteLine("Quack!");
    }
}

public class Cat : IAnimal 
{
    public void Speak()
    {
        Console.WriteLine("Meow!");
    }
}
_

あなたはそれをF#(非慣用的)に翻訳できます:

_type IAnimal = 
    abstract member Speak : unit -> unit

type Duck = 
    interface IAnimal with
        member this.Speak () -> printfn "Quack!"

type Cat = 
    interface IAnimal with
        member this.Speak () -> printfn "Meow!"
_

コードは短くなっていますが、実際には違いはありません。このようなコードを書いている場合は、C#開発者のように考えていることになります:)より慣用的な翻訳を次に示します。

_type Animal = 
    | Duck
    | Cat

let speak animal = 
    match animal with 
    | Duck -> printfn "Quack!"
    | Cat -> printfn "Meow!"
_

speakの代替バージョンは次のとおりです。

_let speak = function
    | Duck -> printfn "Quack!"
    | Cat -> printfn "Meow!"
_

この時点では、動作(speakまたはSpeak())をデータ(この場合は、AnimalまたはIAnimalのタイプのみ)から分離しているため、コードはC#バージョンと大きく異なります。もう1つの違いは、コンパイラー(および型システム)がC#コンパイラー/型システムよりも親密に理解できる方法で、データ(動物の種類)の可能な状態を表現していることです。リストの最初のアイテムを取得する関数を考えてみましょう:

_public T GetHead(List<T> list)
{
    return list[0];
} 
_

対:

_let getHead = function
    | head :: tail -> head
_

どちらも慣用リストを取り、その最初の項目を返します(_::_はリスト構造化演算子です)。

これらの両方をコンパイルする場合、C#バージョンはエラーや警告なしでコンパイルされ、F#バージョンはすべてのケースを考慮していないことを警告します(空のリストはどうですか?)。 F#コンパイラは、考えられるすべての入力を考慮していないことを認識し、それに応じて警告します。

これは、言語間の他の違いの1つです。プログラマー(慣用的なコードがある場合)として、C#/ OOPが実行時までキャッチしないエラーやバグを検出するために、コンパイラーにもっと頼ることができます。

7
Wesley Wiser

「異なるパラダイム」の障壁が実際にどこで発生するのでしょうか。

「障壁」はほとんどありません。慣用的ではないF#を自由に作成できます。C#を古くからプログラミングでき、オブジェクト指向プログラミングを実際に実践するのではないのと同じです。

私が実際にあなたの手を強制するのを見ることができる唯一のケースは、他の開発者と協力することです。あなたは言語とともに彼らのスタイルを見るでしょう、そしてそれを使うことに適応しなければならないでしょう。さらに重要なことに、これらは機能的なコードベースでループを作成することを意味しません。

9
Telastyn

F#はハイブリッド言語です。これは主に関数型言語ですが、C#で実行できることのほとんどすべてを実行できます。したがって、どこでも、可変変数、可変コレクション、副作用のあるメソッド、継承階層を使用できます。しかし、これを行うと、F#は非常に優れた命令型言語ではなく、C#を使用し続けた方がよいでしょう。

一方、慣用的なF#を記述したい場合は、不変変数、不変コレクション、純関数、判別共用体、およびパターンマッチングの使用方法を学ぶ必要があります。

要約すると、F#が提供する機能を実際に利用したい場合は、実際にコードを再考する必要がありますが、おそらくHaskellに必要なほど根本的なものではありません。

5
svick

両方の言語(C#およびF#)の使用経験と、見つけた「バンプ」からコメントします。 (私は障壁がないことに同意します。しかし、道路には間違いなく「隆起」があります)。

私は最初にf#を使用してスクリプトを作成するためにF#を使用しました。これは、C#ではそれができず、いくつかの機能的なパラダイムについて学習していたためです。 F#は、手元のタスクが関数型プログラミング(本質的にはマップ/リデュース)に適していたため、非常に優れた簡潔な仕事をしました。

最大の問題は、オブジェクトを「忘れる」ことであり、ループや条件を使用するためにhaveを使用しないことに気づきました。私はこの「スクリプト」スペースで多くの時間を費やしました。 (私はいくつかの古いF#を振り返って、過去にどのように「強制」しようとしていたかを見ました)。

私は今、F#で最初の大きな作業であるDSLを実行しています。これは、このタスクにより適した言語です。私はまだC#を使用しています-これは私の「システム」言語です。動き回るオブジェクトと可変性は、私が持っているメンタルモデルに適しています。

私はそれが全体の切り替えをするよりも仕事に最も適していると言っていいでしょう。大きなメリットは、さまざまな問題を解決する方法について異なる考え方をさせることです。 F#を習得して以来、私のC#は大幅に改善されました。

4
CodeBeard