web-dev-qa-db-ja.com

if-thenステートメントの機能的な代替とは何ですか?

私はF#と関数型プログラミングを学び、関数型の方法で物事を実行しようとしています。ただし、C#で既に記述したコードを書き換える場合、単純なif-thenステートメント(何かを実行するだけで値を返さないもの)に行き詰まります。私はあなたがこれをF#で引っ張ることができることを知っています:

if expr then do ()

しかし、これはコーディングへの必須のアプローチだと思いましたか?たぶん私は関数型プログラミングについて十分に学習していませんが、私には機能的ではないようです。関数型のアプローチは、関数と式を作成することであり、単にステートメントを次々に実行することではないと考えました。

それで、私は何かを欠いているのですか、そして関数型の世界では完全に問題がないのですか?そうでない場合、そのようなステートメントに相当する機能は何ですか?どうすればif-thenを取得して機能させることができますか?

編集:私は間違った質問をしたかもしれません(申し訳ありませんが、関数型プログラミングはまだかなり新しいものです)。

if not <| System.String.IsNullOrWhiteSpace(data) then do
    let byteData = System.Text.Encoding.Unicode.GetBytes(data)
    req.ContentLength <- int64 byteData.Length
    let postStream : System.IO.Stream = req.GetRequestStream()
    postStream.Write(byteData, 0, byteData.Length)
    postStream.Flush()
    postStream.Dispose()

そのif-thenの本体では何も返されませんが、これをより機能的にする方法(それが可能な場合でも)はわかりません。命令型コードを最小限に抑えるための適切な手法がわかりません。 F#の性質を考えると、C#を直接転送することはかなり簡単ですが、機能させるのに問題があります。 C#でそのようなifステートメントに到達し、それをF#に転送しようとするたびに、コードをより機能的にする方法を考えられないことに落胆します。

47
Bob

これまでに言及されていない重要な点は、_if .. then .. else_とelseブランチなしの_if .. then_の違いです。

関数型言語のIf

ifの機能的な解釈は、ある値に評価される式であるということです。 _if c then e1 else e2_の値を評価するには、条件cを評価してから、条件に応じて_e1_または_e2_を評価します。これにより、_if .. then .. else_の結果が得られます。

_if c then e_しかない場合、cブランチがないため、falseelseの場合、評価の結果がどうなるかわかりません!以下は明らかに意味がありません。

_let num = if input > 0 then 10
_

F#では、_printf "hi"_のような副作用がある式は、unit型の特別な値を返します。型には単一の値(_()_として記述)しかないため、ifを記述して、1つのケースでのみ効果を発揮することができます。

_let u = if input > 0 then printf "hi" else ()
_

これは常にunitに評価されますが、trueブランチでは、副作用も実行します。 falseブランチでは、unit値を返すだけです。 F#では、else ()ビットを手動で書き込む必要はありませんが、概念的には、そこにあります。あなたは書ける:

_let u = if input > 0 then printfn "hi"
_

追加の例について

コードは私には完全にうまく見えます。 (多くの.NETライブラリのように)命令型のAPIを扱う必要がある場合、ifのような命令型機能をunit- returningブランチで使用するのが最良のオプションです。

_option<string>_を使用してデータを表すなど、stringnullまたは空の文字列と一緒に使用するのではなく、さまざまな調整を使用できます。そうすることで、Noneを使用して欠落データを表すことができ、その他は有効な入力になります。次に、値がある場合に特定の関数を呼び出す_Option.iter_など、オプションを操作するためにいくつかの高次関数を使用できます。

_maybeData |> Option.iter (fun data ->
    let byteData = System.Text.Encoding.Unicode.GetBytes(data)  
    req.ContentLength <- int64 byteData.Length  
    use postStream = req.GetRequestStream()  
    postStream.Write(byteData, 0, byteData.Length) )
_

これはそれほど必要不可欠ではありませんが、ifを自分で記述する必要がないため、宣言的です。ところで、useオブジェクトを自動で作成したい場合は、Disposeの使用をお勧めします。

35
Tomas Petricek

関数型の世界ではif-thenに問題はありません。

exprには副作用があり、戻り値を無視するため、実際の例は_let _ = expr_に似ています。より興味深い例は次のとおりです。

_if cond then expr
_

これは次と同等です:

_match cond with
| true -> expr
| false -> ()
_

パターンマッチングを使用する場合。

条件が単純な場合、または条件式が1つしかない場合、if-thenはパターンマッチングよりも読みやすくなります。さらに、関数型プログラミングのすべてが式であることに注意してください。したがって、_if cond then expr_は実際にはif cond then expr else ()のショートカットです。

If-then自体は必須ではありません。if-thenをステートメントとして使用することは、必須の考え方です。私の経験から、関数型プログラミングはプログラミング言語での具体的な制御フローよりも考え方の方が重要です。

編集:

コードは完全に読み取り可能です。いくつかのマイナーな点は、冗長なdoキーワード、型注釈、およびpostStream.Dispose()を取り除くことです(useキーワードを使用して):

_if not <| System.String.IsNullOrWhiteSpace(data) then
    let byteData = System.Text.Encoding.Unicode.GetBytes(data)
    req.ContentLength <- int64 byteData.Length
    use postStream = req.GetRequestStream()
    postStream.Write(byteData, 0, byteData.Length)
    postStream.Flush()
_
13
pad

必須ののはif式ではなく、if式に含まれるものです。例えば、 let abs num = if num < 0 then -num else numは、abs関数を記述する完全に機能的な方法です。引数を取り、その引数の変換を副作用なしで返します。しかし、「値を返すのではなく、何かを行うだけのコード」がある場合、thenは、純粋に機能的ではない何かを書いています。関数型プログラミングの目標は、そのように記述できるプログラムの部分を最小限に抑えることです。条件文の書き方は接線です。

10
Chuck

複雑なコードを書くには、ある時点で分岐する必要があります。これを実行する方法は非常に限られており、それらすべてにコードのセクションを介した論理フローが必要です。 if/then/elseの使用を避けたい場合は、loop/while/repeatを使用して不正を行うことは可能ですが、コードの保守と読み取りがはるかに合理的でなくなります。

関数型プログラミングは、ステートメントを次々に実行してはならないという意味ではありません。単に、変更可能な状態にしてはいけないという意味です。各関数は、呼び出されるたびに確実に同じように動作する必要があります。データの処理方法の違いは、関数を呼び出しているものから隠されているトリガーではなく、渡されたデータによって説明される必要があります。

たとえば、boolがtrueかfalseかによって異なる値を返す関数foo(int, bool)がある場合、foo()。それは完全に合法です。正当でないのは、プログラムで最初に呼び出されたかどうかによって異なる関数を返す関数foo(int)を持つことです。これは「ステートフル」機能であり、プログラムを保守する人にとって人生を困難にします。

8
Michael Martin

ifステートメントに戻り値があり、副作用がない場合は、機能していると見なされます。

次のものと同等のものを記述したいとします。

if(x > 3) n = 3; else n = x;

その代わりに、ifコマンドのreturnステートメントを使用します。

let n = (if x > 3 then 3 else x)

この架空のifは、副作用がないため突然機能します。値を返すだけです。一部の言語では項演算子であるかのように考えてください:int n = x>3?3:x;

6
Tim

命令型プログラミングから関数型(「すべてが式」である)プログラミングへの移行を支援する2つの観察があります。

  1. unitは値ですが、C#でvoidを返す式はステートメントとして扱われます。つまり、C#はステートメントと式を区別します。 F#ではすべてが式です。

  2. C#では値を無視できます。 F#ではできません。そのため、より高いレベルのタイプセーフが提供されます。この明示性により、F#プログラムの推論が容易になり、保証が強化されます。

3
Daniel

F#がわからないことをお詫びしますが、JavaScriptで可能な解決策の1つを次に示します。

function $if(param) {
    return new Condition(param)
}

function Condition(IF, THEN, ELSE) {
    this.call = function(seq) {
        if(this.lastCond != undefined) 
            return this.lastCond.call(
                sequence(
                    this.if, 
                    this.then, 
                    this.else, 
                    (this.elsif ? this.elsif.if : undefined),
                    seq || undefined
                )
            );
         else 
            return sequence(
                this.if, 
                this.then, 
                this.else, 
                (this.elsif ? this.elsif.if : undefined),
                seq || undefined
            )
    }


    this.if   = IF ? IF : f => { this.if = f; return this };
    this.then = THEN ? THEN : f => { this.then = f; return this };
    this.else = ELSE ? ELSE : f => { this.else = f; return this };
    this.elsif = f => {this.elsif = $if(f); this.elsif.lastCond = this; return this.elsif};
}

function sequence(IF, THEN, ELSE, ELSIF, FINALLY) {
    return function(val) {
        if( IF(val) ) 
            return THEN();

        else if( ELSIF && ELSIF(val) ) 
            return FINALLY(val);

        else if( ELSE ) 
            return ELSE();

        else 
            return undefined

    }
}}

$ if関数は、Conditionコンストラクターを使用して、if..then..else..elsif構成を持つオブジェクトを返します。 Condition.elsif()を呼び出したら、別のConditionオブジェクトを作成します-基本的に、sequence()を使用して再帰的にトラバースできるリンクリストを作成します

あなたはそれを次のように使うことができます:

var eq = val => x => x == val ? true : false;

$if( eq(128) ).then( doStuff ).else( doStuff )
.elsif( eq(255) ).then( doStuff ).else( doStuff ).call();

ただし、オブジェクトを使用することは、純粋に機能的なアプローチではないことを理解しています。したがって、その場合は、オブジェクトをまとめて忘れることができます。

sequence(f, f, f, f,
    sequence(f, f, f, f
        sequence(f, f, f)
    )
);

魔法が実際にsequence()関数にあることがわかります。 JavaScriptで特定の質問に答えようとはしません。しかし、重要な点は、if..thenステートメントの下で任意の関数を実行する関数を作成し、再帰を使用してその関数の複数をネストし、複雑な論理チェーンを作成することです。少なくともこの方法では、自分自身を繰り返す必要はありません;)

このコードはプロトタイプにすぎないので、概念実証として受け取り、バグを見つけた場合はお知らせください。

1
Duco