سؤال

في سعيي لمعرفة المزيد من F#، حاولت تنفيذ "مولد تراكم" كما وصفه بول غراهام هنا. أفضل حل لي حتى الآن هو كتابته ديناميكيًا تمامًا:

open System

let acc (init:obj) : obj->obj=
  let state = ref init
  fun (x:obj) ->
    if (!state).GetType() = typeof<Int32>
       && x.GetType() = typeof<Int32> then
      state := (Convert.ToInt32(!state) + Convert.ToInt32(x)) :> obj
    else
      state := (Convert.ToDouble(!state) + Convert.ToDouble(x)) :> obj
    !state

do
  let x : obj -> obj = acc 1  // the type annotation is necessary here
  (x 5) |> ignore
  printfn "%A" (x 2)   // prints "8"
  printfn "%A" (x 2.3) // prints "10.3"

لدي ثلاثة أسئلة:

  • إذا قمت بإزالة التعليق التوضيحي لـ x, ، فشل الرمز في التجميع لأن برنامج المترجم int -> obj ل x - على الرغم من acc مشروح لإرجاع obj->obj. لماذا هذا وهل يمكنني تجنب ذلك؟
  • أي أفكار لتحسين هذه النسخة المكتوبة ديناميكيا؟
  • هل من الممكن تنفيذ ذلك بأنواع ثابتة مناسبة؟ ربما مع قيود الأعضاء؟ (من الممكن في هاسكل ، ولكن ليس في Ocaml ، AFAIK)
هل كانت مفيدة؟

المحلول

في سعيي لمعرفة المزيد من F#، حاولت تنفيذ "مولد تراكم" كما وصفه بول غراهام هنا.

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

يمكنك تنفيذ برج رقمي في F# إما باستخدام نوع الاتحاد (مثل type number = Int of int | Float of float) أو عن طريق الملاكمة كل شيء. يستخدم الحل التالي النهج الأخير:

let add (x: obj) (y: obj) =
  match x, y with
  | (:? int as m), (:? int as n) -> box(m+n)
  | (:? int as n), (:? float as x)
  | (:? float as x), (:? int as n) -> box(x + float n)
  | (:? float as x), (:? float as y) -> box(x + y)
  | _ -> failwith "Run-time type error"

let acc x =
  let x = ref x
  fun (y: obj) ->
    x := add !x y
    !x

let x : obj -> _ = acc(box 1)
do x(box 5)
do acc(box 3)
do printfn "%A" (x(box 2.3))

ومع ذلك ، فإن الأبراج الرقمية عديمة الفائدة تقريبًا في العالم الحقيقي. ما لم تكن حريصًا جدًا ، فإن محاولة التعلم من هذه الأنواع من التحديات المحمولة ستؤذيك أكثر مما تنفع. يجب أن تترك تسأل نفسك لماذا لا نريد برجًا رقميًا ، ولا نريد أن نرغب في الترويج لوقت التشغيل؟

لماذا لم نكتب فقط:

let x = 1
let x = x + 5
ignore(3)
let x = float x + 2.3

نحن نعرف نوع x في كل خطوة. يتم تخزين كل رقم غير محصور. نحن نعلم أن هذا الرمز لن ينتج أبدًا خطأ في نوع وقت التشغيل ...

نصائح أخرى

وأنا أتفق مع جون على أن هذا مثال اصطناعي تمامًا وأنه ليس نقطة انطلاق جيدة لتعلم F#. ومع ذلك ، يمكنك استخدام قيود الأعضاء الثابتة للحصول على قرب معقول دون قوالب ديناميكية وانعكاس. إذا قمت بمناسبةها كـ inline وإضافة تحويل كل من المعلمات باستخدام float:

let inline acc x = 
  let x = ref (float x)
  fun y ->
    x := (float y) + !x
    !x

ستحصل على وظيفة مع النوع التالي:

val inline acc :
   ^a -> ( ^b -> float)
    when  ^a : (static member op_Explicit :  ^a -> float) and
          ^b : (static member op_Explicit :  ^b -> float)

تأخذ الوظيفة أي وسيطتين يمكن تحويلهما بشكل صريح إلى تعويم. القيد الوحيد مقارنةً بإصدار LISP (على ما أظن) هو أنه يعيد دائمًا العائم (باعتباره أكثر أنواع الرقمية العالمية المتاحة). يمكنك كتابة شيء مثل:

> acc 1 2;;            // For two integers, it returns float
val it : float = 3.0
> acc 1 2.1;;          // integer + float
val it : float = 3.1
> acc 1 "31";;         // It even works with strings!
val it : float = 32.0

من المؤكد أنه لا يمكن تنفيذ هذا مع أنواع ثابتة مناسبة. أنت تقول أنك تستطيع في هاسكل ، لكنني لا أصدقك.

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

module MyTest

type Numeric =
  | NInt of int
  | NFloat of float

  member this.Add(other : Numeric) : Numeric =
    match this with
      | NInt x ->
        match other with
          | NInt y -> NInt (x + y)
          | NFloat y -> NInt (x + (int y))
      | NFloat x ->
        match other with
          | NInt y -> NFloat (x + (float y))
          | NFloat y -> NFloat (x + y)

  override this.ToString() =
    match this with
      | NInt x -> x.ToString()
      | NFloat x -> x.ToString()

let foo (n : Numeric) =
  let acc = ref n
  fun i ->
    acc := (!acc).Add(i)
    !acc

let f = foo (NFloat 1.1)
(2 |> NInt |> f).ToString() |> printfn "%s"
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top