سؤال

ويمكن أن أفعل ذلك بسهولة في C ++ (ملاحظة: لم أكن اختبار هذا من أجل صحة - انها فقط لتوضيح ما أحاول القيام به):

   const int BadParam = -1;
   const int Success = 0;

   int MyFunc(int param)
   {
      if(param < 0)
      {
         return BadParam;
      }

      //normal processing

      return Success;
   }

ولكن لا أستطيع معرفة كيفية الخروج من الروتين في F # وقت مبكر. ما أريد القيام به هو للخروج من وظيفة على مدخلات سيئة ولكن يستمر إذا دخل على ما يرام. أنا في عداد المفقودين بعض الممتلكات الأساسية للF # أو أنا تقترب من المشكلة بطريقة خاطئة منذ أنا مجرد تعلم FP؟ هو failwith الخيار الوحيد هنا؟

وهذا هو ما كنت قد حصلت حتى الآن وأنه يجمع طيب:

   #light

   module test1

       (* Define how many arguments we're expecting *)
       let maxArgs = 2;;
       (* The indices of the various arguments on the command line *)
       type ProgArguments =
           | SearchString = 0
           | FileSpec = 1;;

       (* Various errorlevels which the app can return and what they indicate *)
       type ProgReturn =
           | Success = 0
           | WrongNumberOfArgumentsPassed = 1;;

       [<EntryPoint>]
       let main (args:string[]) =

           printfn "args.Length is %d" args.Length

           let ProgExitCode = if args.Length <> maxArgs then
                                   printfn "Two arguments must be passed"
                                   int ProgReturn.WrongNumberOfArgumentsPassed
                                   (* Want to exit "main" here but how? *)
                               else
                                   int ProgReturn.Success

           let searchstring, filespec  = args.[int ProgArguments.SearchString],args.[int ProgArguments.FileSpec];

           printfn "searchstring is %s" searchstring
           printfn "filespec is %s" filespec

           ProgExitCode;;

هل هناك طريقة FP التعامل مع هذا النوع من الشيء؟

هل كانت مفيدة؟

المحلول

في رأيي، وتعبيرات المباراة هي F # التناظرية من المبكر الخروج للنداء الظروف الخاطئة والتعامل مع كل منها على حدة. على سبيل المثال الخاص بك، كنت أكتب:

 [<EntryPoint>]
 let main (args:string[]) =
     printfn "args.Length is %d" args.Length
     match args with
     | [| searchstring; filespace |] -> 
       // much code here ...
       int Success
     | _ -> printfn "Two arguments must be passed"
       int WrongNumberOfArgumentsPassed

وهذا يفصل حالة الخطأ بشكل جيد. بشكل عام، إذا كنت بحاجة للخروج من منتصف شيء، وظائف انقسام ومن ثم وضعت هذه القضية خطأ في match. هناك حقا لا حدود لمدى ينبغي أن تكون وظائف صغيرة في لغة وظيفية.

وبوصفها جانبا، استخدامك لنقابات تتعرض للتمييز على أنها مجموعات من الثوابت صحيحة هو غريب قليلا. إذا كنت مثل هذا المصطلح، أن تدرك أن لا تحتاج إلى تضمين اسم نوع عند الإشارة إليهم.

نصائح أخرى

في F #، كل شيء على ما تتكون من تعبيرات (في حين أنه في العديد من اللغات الأخرى، لبنة الرئيسي هو بيان). ليس هناك طريقة للخروج من وظيفة في وقت مبكر، ولكن في كثير من الأحيان هذه ليست هناك حاجة. في C، لديك كتل if/else حيث تتم فروع تتكون من البيانات. في F #، هناك تعبير if/else، حيث يقيم كل فرع إلى قيمة من نوع ما، وقيمة التعبير if/else كله هو قيمة فرع واحد أو آخر.

وحتى هذا C ++:

int func(int param) {
  if (param<0)
    return BadParam;
  return Success;
}

ويبدو أن هذا في F #:

let func param =
  if (param<0) then
    BadParam
  else
    Success

والتعليمات البرمجية الخاصة بك على الطريق الصحيح، ولكن يمكنك ريفاكتور ذلك، وضع أكثر من المنطق في فرع else، مع منطق "العودة المبكرة" في فرع if.

وأولا وقبل كل شيء، والبعض الآخر قد أشار بالفعل، انها ليست "وF # الطريق" (وأيضا، لا FP الطريقة، حقا). منذ كنت لا تتعامل مع البيانات، ولكن التعابير فقط، ليس هناك حقا أي شيء للخروج من. بشكل عام، وهذا يتم التعامل من قبل سلسلة متداخلة من البيانات if..then..else.

وقال ذلك، أستطيع أن أرى بالتأكيد حيث هناك ما يكفي من النقاط للخروج المحتملة التي سلسلة if..then..else طويلة يمكن أن يكون غير قابل للقراءة جدا - وخاصة ذلك عند التعامل مع بعض API الخارجي الذي هو مكتوب في العودة رموز الخطأ بدلا من رمي الاستثناءات على الفشل (ويقول Win32 وAPI، أو بعض مكونات COM)، لذلك كنت حقا بحاجة الى أن قانون معالجة الأخطاء. إذا كان الأمر كذلك، يبدو أن طريقة للقيام بذلك في F # على وجه الخصوص سيكون لكتابة <لأ href = "http://msdn.microsoft.com/en-us/library/dd233182(VS.100).aspx" يختلط = "noreferrer"> سير العمل للحصول على ذلك. وهنا أول احملوا في ذلك:

type BlockFlow<'a> =
    | Return of 'a
    | Continue

type Block() = 
    member this.Zero() = Continue
    member this.Return(x) = Return x
    member this.Delay(f) = f
    member this.Run(f) = 
        match f() with
        | Return x -> x
        | Continue -> failwith "No value returned from block"
    member this.Combine(st, f) =
        match st with
        | Return x -> st
        | Continue -> f()
    member this.While(cf, df) =
        if cf() then
            match df() with
            | Return x -> Return x
            | Continue -> this.While(cf, df)
        else
            Continue
    member this.For(xs : seq<_>, f) =
        use en = xs.GetEnumerator()
        let rec loop () = 
            if en.MoveNext() then
                match f(en.Current) with
                | Return x -> Return x
                | Continue -> loop ()
            else
                Continue
        loop ()
    member this.Using(x, f) = use x' = x in f(x')

let block = Block() 

وعينة الاستخدام:

open System
open System.IO

let n =
    block {
        printfn "Type 'foo' to terminate with 123"
        let s1 = Console.ReadLine()
        if s1 = "foo" then return 123

        printfn "Type 'bar' to terminate with 456"
        let s2 = Console.ReadLine()
        if s2 = "bar" then return 456

        printfn "Copying input, type 'end' to stop, or a number to terminate with that number"
        let s = ref ""
        while (!s <> "end") do
            s := Console.ReadLine()
            let (parsed, n) = Int32.TryParse(!s)
            if parsed then           
                printfn "Dumping numbers from 1 to %d to output.txt" n
                use f = File.CreateText("output.txt") in
                    for i = 1 to n do
                        f.WriteLine(i)
                return n
            printfn "%s" s
    }

printfn "Terminated with: %d" n

وكما ترون، ويعرف على نحو فعال عن ثوابت في مثل هذه الطريقة التي، في أقرب وقت واجه return، والباقي من كتلة ليست حتى تقييمها. إذا تدفق كتلة "قبالة نهاية" دون return، سوف تحصل على استثناء وقت التشغيل (وأنا لا أرى أي طريقة لفرض هذا في الترجمة من الوقت حتى الآن).

وهذا يأتي مع بعض القيود. أولا وقبل كل شيء، وسير العمل في الحقيقة ليست كاملة - فإنه يتيح لك استخدام let، use، if، while وfor الداخل، ولكن لا try..with أو try..finally. ويمكن القيام به - تحتاج إلى تنفيذ Block.TryWith وBlock.TryFinally - ولكن أنا لا يمكن العثور على مستندات لهم حتى الآن، ولذلك فإن هذا سوف تحتاج قليلا من التخمين والمزيد من الوقت. وأود أن أعود إليها لاحقا عندما يكون لدي المزيد من الوقت، وإضافتها.

وثانيا، لأن سير العمل ونسبة السكر في الحقيقة مجرد النحوي لسلسلة من المكالمات وظيفة وlambdas - وعلى وجه الخصوص، كل ما تبذلونه متاحة في lambdas - لا يمكنك استخدام let mutable داخل العمل. وهذا هو السبب لقد استعملت ref و! في نموذج التعليمات البرمجية أعلاه، الذي هو الحل للأغراض العامة.

وأخيرا، هناك جزاء أداء حتمية لجميع المكالمات امدا. يفترض، F # هو أفضل في تحسين مثل هذه الأمور من، ويقول C # (الذي يترك فقط كل شيء كما هو في IL)، ويمكن أن مضمنة الاشياء على مستوى IL والقيام الحيل الأخرى؛ ولكن أنا لا أعرف الكثير عن ذلك، وبالتالي فإن أداء إصابة المحدد، إن وجدت، يمكن تحديد إلا من خلال التنميط.

وخيار مماثل لبافيل، ولكن دون الحاجة سير العمل البناء الخاصة، هو فقط لوضع كتلة التعليمات البرمجية الخاصة بك في غضون التعبير seq، ويكون ذلك رسائل الخطأ yield. ثم مباشرة بعد التعبير، كنت مجرد دعوة FirstOrDefault للحصول على أول رسالة خطأ (أو فارغة).

ومنذ تعبير تسلسل يقيم بتكاسل، وهذا يعني أنها سوف تشرع إلا على نقطة الخطأ الأول (على افتراض انك لا يدعون أي شيء ولكن FirstOrDefault على تسلسل). واذا لم يكن هناك خطأ ثم تشغيله ببساطة حتى النهاية. حتى إذا كنت تفعل ذلك بهذه الطريقة عليك أن تكون قادرا على التفكير من yield تماما مثل العودة المبكرة.

let x = 3.
let y = 0.

let errs = seq {
  if x = 0. then yield "X is Zero"
  printfn "inv x=%f" (1./x)
  if y = 0. then yield "Y is Zero"
  printfn "inv y=%f" (1./y)
  let diff = x - y
  if diff = 0. then yield "Y equals X"
  printfn "inv diff=%f" (1./diff)
}

let firstErr = System.Linq.Enumerable.FirstOrDefault errs

if firstErr = null then
  printfn "All Checks Passed"
else
  printfn "Error %s" firstErr

وهذه الوظيفة فيبوناتشي متكررة اثنين من نقاط الخروج:

let rec fib n =
  if n < 2 then 1 else fib (n-2) + fib(n-1);;
                ^      ^
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top