Fable: دعم Python لـ Fable

تم إنشاؤها على ٤ يناير ٢٠٢١  ·  54تعليقات  ·  مصدر: fable-compiler/Fable

وصف

هذه القضية عبارة عن مناقشة حول دعم Python لـ Fable. إن Babel AST المستخدم حاليًا بواسطة Fable قريب بدرجة كافية لإنشاء كود Python. خاصة الآن أن الفصول مدعومة.

تم عمل نقطة حماية لإثبات أن هذا ممكن. ومع ذلك ، هناك حاجة إلى الكثير من العمل من أجل:

  1. أضف دعمًا لائقًا لـ Python
  2. اجعل Fable أكثر حيادية لـ JS ودعم عدة لغات مستهدفة.

تتيح مكتبة Expression البرمجة الوظيفية في Python المستوحاة من F #. يوفر التطبيق الخاص به لأنواع البيانات مثل option ، result ، seq ، map ، list (تسمى FrozenList) ، صندوق البريد المعالج ، ... وبالتالي يجب أن يكون مناسبًا كمكافئ Python لمكتبة Fable. في Python ، يمكنك استخدام Expression لعمل كود F # 'ish. في Fable ، تقوم بإنشاء كود Python باستخدام Expression.

استخدم حالات:

  • F # قيد التشغيل في دفتر Jupyter مع نواة Python ، أي مشابه لما يتم مع Hy و Calysto Hy على سبيل المثال.
  • F # البرمجة النصية باستخدام Python لتقليل الاحتكاك
  • دعم البيئات المدمجة بسهولة أكبر مثل micro: bit و Rasberry PI .
  • نموذج مشاركة البيانات بين F # و Python code

أشياء يجب مناقشتها واتخاذ قرار بشأنها:

  • هل يجب أن تكون Python لـ F # أكثر Pythonic من .NET؟ يوجد بالفعل دعم .NET لـ Jupyter والذي يجب ألا نحاول استبداله أو التنافس معه. ربما يجب أن تستهدف Fable for Python Python بدلاً من .NET (إن أمكن) وأن تكون أكثر ملاءمة لمطوري Python الذين يتطلعون إلى F #.
  • هل يجب أن نحاول أن نكون متوافقين مع Babel AST أم يجب أن نتشعب في Python AST الخاص بنا. ربما يكون هذا أمرًا لا مفر منه ، لكن بابل تعطينا نقطة انطلاق جيدة.
  • هل يجب علينا استخدام Fable (كما هو الحال مع Peeble ) أو هل يجب أن نبقى داخل Fable ولدينا Fable يدعم لغات متعددة. يمنحك امتلاك الريبو الخاص بنا الحرية والسرعة (من خلال عدم الحاجة إلى الاهتمام بـ JS) ، لكننا سنخاطر بالتخلف عن الركب والتخلي عننا بمرور الوقت. شعوري هو أننا يجب أن نصبح جزءًا من Fable.
  • تختلف أنواع Python الأساسية أيضًا عن F #. على سبيل المثال ، طول int عشوائي. هل يجب علينا محاكاة أنواع .NET (الجهاز) لتكون متوافقة أم يجب أن نكشف عن نوع Python int أنه F # int؟

تثبيت POC

الكود المصدري لـ POC موجود حاليًا هنا:

  1. قم بتثبيت أحدث إصدار من Python 3.9 من https://www.python.org أو brew install [email protected] على نظام Mac. لاحظ أن لغة python قد تكون متاحة على نظامك مثل python أو python3 أو كليهما.
  2. قم بنسخ كل من الريبو والتبديل إلى الفرع python .
  3. أنشئ مكتبة خرافية لـ Python: dotnet fsi build.fsx library-py
  4. لإجراء الاختبارات: dotnet fsi build.fsx test-py
  5. في Fable ، قم بتحرير QuickTest.fs لبعض كود F # البسيط. ثم قم بتشغيل dotnet fsi build.fsx quicktest من الدليل العلوي.

يمكنك الآن تحرير كلاً من كود Fable و Expression وسيعاد ترجمة رمز المثال عند الحاجة. لاحظ أنك بحاجة إلى حفظ إضافي لـ QuickTest.fs لبدء إعادة التحويل البرمجي بعد تعديل كود Fable. سيكون ملف python متاحًا كـ quicktest.py . العرض التوضيحي الجميل هو عرض كل من F # و JS و Python في vscode في نفس الوقت. ثم سترى كيف يتم تحويل الكود عند حفظ ملف F #:

Screenshot 2021-01-15 at 13 18 09

يمكنك تشغيل كود Python الذي تم إنشاؤه في المحطة:

$ python3 quicktest.py

الروابط

بعض الروابط ذات الصلة:

لا تتردد في المساهمة بالمناقشات والتعليقات والأفكار والكود 😍

التعليق الأكثر فائدة

هذا عمل رائع @ dbrattli! 👏 👏 👏 كانت فكرتي الأصلية مع Fable هي توفير طريقة لترجمة F # بسهولة إلى لغات أخرى. للأفضل أو للأسوأ ، تطورت Fable مع JS باعتبارها محور تركيزها الرئيسي ، لذا سيتطلب الأمر بعض العمل لجعلها أكثر حيادية للغة لكنني سأكون منفتحًا على ذلك. بعض التعليقات على أسئلتك:

  • نفس الريبو أم مختلف؟ سأؤيد الاحتفاظ بأكبر قدر ممكن من الكود المشترك. من المحتمل أن يؤدي استخدام مستودعات مختلفة إلى جعلها تتباعد بسرعة كبيرة (من الصعب بالفعل الحفاظ على تزامن الفرع مع دعم الاقتباس) ولكننا نحتاج أيضًا إلى جعل الكود معياريًا بما يكفي حتى لا يكون لدينا شروط في كل مكان اعتمادًا على اللغة الهدف. سيكون الحل الوسيط هو محاولة استخراج أكبر قدر ممكن من التعليمات البرمجية إلى "نواة" خرافة (لا يمكننا استخدام Core 😉) ومن ثم الحصول على عمليات إعادة شراء مختلفة لتطبيقات اللغة.

  • Babel AST: تم استخدام هذا لأننا في الأصل كنا نعتمد على Babel لطباعة الكود. في عالم مثالي ، بعد أن أصبح لدينا الآن طابعتنا ، سيكون من الممكن إزالتها. ولكن هناك الكثير من العمل الذي يحدث في خطوة Fable-to-Babel لذا سيكون الأمر صعبًا. للسبب نفسه ، سيكون من الأفضل إعادة استخدام أكبر قدر ممكن من التعليمات البرمجية من هذه الخطوة ، وربما جعل Babel AST شيئًا أكثر عمومية للغات غير الوظيفية. بعض الأمثلة:

    • الانتقال من AST القائم على التعبير إلى التعبير / العبارة AST. بالنسبة لـ JS ، يشمل ذلك نقل الإعلانات المتغيرة إلى أعلى الوظيفة عند الضرورة لتجنب الكثير من IIFEs. أعتقد أنه سيكون شيئًا مشابهًا لبايثون.
    • تحويل مطابقة النمط (DecisionTree) إلى عبارات if / switch.
    • تحسين الاتصال الذيل. يتم ذلك باستخدام حلقات JS المسمى. في بايثون ، سنحتاج إلى استخدام أسلوب آخر للتأكد من أننا لا نقطع عن حلقة متداخلة.
    • تحويل الواردات إلى معرفات فريدة (يمكن القيام بذلك مباشرة في Fable AST إذا لزم الأمر).
  • جعل F # أكثر Pythonic؟ مع Fable ، حاولت دائمًا إيجاد توازن حيث يتم التضحية بدلالات .NET أحيانًا لتجنب النفقات العامة. على سبيل المثال ، نستخدم رقم JS فقط بدلاً من وجود نوع محدد لـ ints (على الرغم من أننا نفعل ذلك لفترات طويلة) ، فإن هذا ينطبق على Python int أفترض. ومع ذلك ، تم إجراء العديد من المساهمات لاحترام دلالات .NET في نقاط محددة لتجنب النتائج غير المتوقعة ، على سبيل المثال في الأقسام الصحيحة أو التحويلات الرقمية الصريحة.
    على أي حال ، عندما تقول "أكثر بيثونية" ، فأنت تقصد نمط الكود أو واجهات برمجة التطبيقات الأصلية؟ لا تحاول Fable دعم معظم .NET BCL ولكن في النهاية نحتاج إلى دعم الوظيفة / المشغلين المشتركين لأن هذا هو ما اعتاد المطورون عليه (حتى الوافدين الجدد ، لأنهم سيجدون في البرامج التعليمية). من الجيد إعطاء خيار استخدام واجهات برمجة التطبيقات الأصلية في أي حال ( Fable.Extras هي خطوة جيدة بهذا المعنى). على سبيل المثال ، عادةً ما يقوم JS.console.log بتنسيق العناصر بشكل أفضل من printfn ، لكننا نحتاج إلى دعم الأخير لأنه أكثر ما يستخدمه المطورون.

ال 54 كومينتر

هذا عمل رائع @ dbrattli! 👏 👏 👏 كانت فكرتي الأصلية مع Fable هي توفير طريقة لترجمة F # بسهولة إلى لغات أخرى. للأفضل أو للأسوأ ، تطورت Fable مع JS باعتبارها محور تركيزها الرئيسي ، لذا سيتطلب الأمر بعض العمل لجعلها أكثر حيادية للغة لكنني سأكون منفتحًا على ذلك. بعض التعليقات على أسئلتك:

  • نفس الريبو أم مختلف؟ سأؤيد الاحتفاظ بأكبر قدر ممكن من الكود المشترك. من المحتمل أن يؤدي استخدام مستودعات مختلفة إلى جعلها تتباعد بسرعة كبيرة (من الصعب بالفعل الحفاظ على تزامن الفرع مع دعم الاقتباس) ولكننا نحتاج أيضًا إلى جعل الكود معياريًا بما يكفي حتى لا يكون لدينا شروط في كل مكان اعتمادًا على اللغة الهدف. سيكون الحل الوسيط هو محاولة استخراج أكبر قدر ممكن من التعليمات البرمجية إلى "نواة" خرافة (لا يمكننا استخدام Core 😉) ومن ثم الحصول على عمليات إعادة شراء مختلفة لتطبيقات اللغة.

  • Babel AST: تم استخدام هذا لأننا في الأصل كنا نعتمد على Babel لطباعة الكود. في عالم مثالي ، بعد أن أصبح لدينا الآن طابعتنا ، سيكون من الممكن إزالتها. ولكن هناك الكثير من العمل الذي يحدث في خطوة Fable-to-Babel لذا سيكون الأمر صعبًا. للسبب نفسه ، سيكون من الأفضل إعادة استخدام أكبر قدر ممكن من التعليمات البرمجية من هذه الخطوة ، وربما جعل Babel AST شيئًا أكثر عمومية للغات غير الوظيفية. بعض الأمثلة:

    • الانتقال من AST القائم على التعبير إلى التعبير / العبارة AST. بالنسبة لـ JS ، يشمل ذلك نقل الإعلانات المتغيرة إلى أعلى الوظيفة عند الضرورة لتجنب الكثير من IIFEs. أعتقد أنه سيكون شيئًا مشابهًا لبايثون.
    • تحويل مطابقة النمط (DecisionTree) إلى عبارات if / switch.
    • تحسين الاتصال الذيل. يتم ذلك باستخدام حلقات JS المسمى. في بايثون ، سنحتاج إلى استخدام أسلوب آخر للتأكد من أننا لا نقطع عن حلقة متداخلة.
    • تحويل الواردات إلى معرفات فريدة (يمكن القيام بذلك مباشرة في Fable AST إذا لزم الأمر).
  • جعل F # أكثر Pythonic؟ مع Fable ، حاولت دائمًا إيجاد توازن حيث يتم التضحية بدلالات .NET أحيانًا لتجنب النفقات العامة. على سبيل المثال ، نستخدم رقم JS فقط بدلاً من وجود نوع محدد لـ ints (على الرغم من أننا نفعل ذلك لفترات طويلة) ، فإن هذا ينطبق على Python int أفترض. ومع ذلك ، تم إجراء العديد من المساهمات لاحترام دلالات .NET في نقاط محددة لتجنب النتائج غير المتوقعة ، على سبيل المثال في الأقسام الصحيحة أو التحويلات الرقمية الصريحة.
    على أي حال ، عندما تقول "أكثر بيثونية" ، فأنت تقصد نمط الكود أو واجهات برمجة التطبيقات الأصلية؟ لا تحاول Fable دعم معظم .NET BCL ولكن في النهاية نحتاج إلى دعم الوظيفة / المشغلين المشتركين لأن هذا هو ما اعتاد المطورون عليه (حتى الوافدين الجدد ، لأنهم سيجدون في البرامج التعليمية). من الجيد إعطاء خيار استخدام واجهات برمجة التطبيقات الأصلية في أي حال ( Fable.Extras هي خطوة جيدة بهذا المعنى). على سبيل المثال ، عادةً ما يقوم JS.console.log بتنسيق العناصر بشكل أفضل من printfn ، لكننا نحتاج إلى دعم الأخير لأنه أكثر ما يستخدمه المطورون.

أحب هذه الفكرة. أنا أتطلع بشكل خاص لاستخدام مكتبات Python في F #. من الناحية المثالية ، يجب أن يكون لدينا خيار استهلاك إما مكتبات Python أو SciSharp - فهي تميل إلى امتلاك واجهة برمجة تطبيقات دقيقة تقريبًا مثل Python (يتم تشغيل نفس رمز F # في Python أو سياق .NET!)

يمكن تجميع كود F # مع Fable إلى Python ثم استخدامه داخل نواة Notebooks (Jupyter أو .NET Interactive) مما يجعل سير عمل التحليلات مرفقًا حقيقيًا يحركه المجال (يمكنني اعتباره كله "ميزة قاتلة").

.NET Interactive قابل للتوسعة للغاية ، تشغيل Fable داخل F # Kernel سهل جدًا (لدي PoC أعمل).

شيء آخر هو أنني أتوقع أن MS ستكشف شيئًا مشابهًا قريبًا لـ .NET بالكامل (يعتمد التنبؤ فقط على الأنشطة والأهداف معالجة .NET Interactive لذا قد أكون مخطئًا). حتى لو فعلوا ذلك ، ما زلنا نريد Fable الحصول عليه بشكل منفصل من أجل الحصول على ترسانة JS / F # / Python كاملة في عالم وظيفي. نهج MS سيكون أشبه بنكهة Blazor (على ما أعتقد).

شكرًا ، هذه تعليقات رائعة alfonsogarciacaro ، PawelStadnicki . ما قصدته هو ما إذا كنا نريد السماح للمبرمج بالحصول على شعور "Python" من خلال الكشف عن المزيد من مكتبة Python القياسية بدلاً من محاولة إعادة تطبيق .NET (ثم ربما ينبغي عليهم استخدام .NET بدلاً من ذلك).

أعتقد أنه يمكننا الحصول على الأفضل من كلا العالمين ، وجعل المستخدم يقرر ما يجب استيراده أم لا (على سبيل المثال ، استخدم datetime من Python أو DateTime من .NET). لذلك لا داعي لاتخاذ قرار ، ولكن ما عليك سوى تنفيذ ما نريده أكثر (حسب الطلب). لكنني أظن أن نفس المشكلة ذات صلة بـ Fable JS. أنت تعلم أنك تقوم بتنفيذ تطبيق ويب ، ومن المحتمل ألا تتوقع أن يتوفر .NET بالكامل. تفضل الوصول بشكل أفضل إلى نظام JS البيئي.

لذلك لا داعي لاتخاذ قرار مقدمًا. دعنا نشمر عن الأكمام ونبدأ 😄 سيكون من الرائع أن تكون جزءًا من Fable ولدينا هذه المسألة لطرح الأسئلة على طول الطريق ، على سبيل المثال حول التحولات وما إلى ذلك. هناك الكثير لنتعلمه.

ملاحظة: لدى Expression btw أيضًا مصممًا لنداء الذيل يمكننا استخدامه (كل من المزامنة وغير المتزامنة). فيما يلي مثال على الاستخدام في النشاط الحيوي.

يجعلني أفكر في # 1601.

أتساءل عما إذا كانت هناك طريقة جيدة لجعل Fable قويًا بما يكفي بحيث يمكن القيام به على أنه "مكونات إضافية" لجهة خارجية (فكر في LLVM ولكنه خاص بـ F #). احتمالات لا نهاية لها.

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

ماذا قال alfonsogarciacaro . جزء من هذا التحدي هو ترحيل المزيد من تطبيق Fable BCL إلى F # بحيث لا يلزم إعادة كتابته لكل لغة ، مع الحفاظ على الأداء الأصلي المقبول للكود الذي تم إنشاؤه ، وهو أمر غير تافه حتى مع JavaScript.

مثير للإعجاب. لم أكن أدرك أن أجزاء مهمة من تطبيق Fable BCL تمت كتابتها (على الأرجح) بلغة Javascript. افترضت أنه سيكون أكثر من F # بالفعل. لا أشك في وجود سبب وجيه لذلك - لماذا اتضح أنه أسهل بهذه الطريقة؟

jwosty الإجابة (ليست قصيرة جدًا) هي: لأسباب تتعلق بالأداء ،

يحتوي أيضًا .NET BCL على واجهة API كبيرة جدًا ، بالإضافة إلى FSharp.Core ، لذلك من الصعب الحفاظ على هذا الجهد بدون فريق أكبر (على الرغم من أن alfonsogarciacaro تمكن بطريقة ما من

أعتقد أن ما أحاول قوله هو أن المساهمات (والأفكار) موضع ترحيب وتقدير للغاية.

الإجابة (ليست قصيرة جدًا) هي: لأسباب تتعلق بالأداء ، ولتجسير الفرق بين أنواع F # مقابل الأنواع التي يدعمها المتصفح أصلاً ، ولتحسين تكامل الشفرة التي تم إنشاؤها في نظام JavaScript البيئي.

أستطيع أن أرى ذلك.

أعتقد أن ما أحاول قوله هو أن المساهمات (والأفكار) موضع ترحيب وتقدير للغاية.

بالتأكيد! أفهم مقدار الجهد المبذول في الحفاظ على مشاريع كبيرة ومهمة مثل هذا ، وأنا أقدر حقًا العرق الدموي والدموع التي سكبتموها عليها. بالتأكيد أبقي عيناي مفتوحتين للأماكن للمساهمة ، حيث كانت Fable مؤخرًا مفيدة جدًا جدًا بالنسبة لي وأحب أن أرد الجميل.

يمكنني استخدام القليل من المساعدة. أستخدم حاليًا مشروع QuickTest للتطوير (على سبيل المثال dotnet fsi build.fsx quicktest ). كيف أطبع Babel AST أثناء التطوير؟ ( تحديث : وجدت ASTViewer ).

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

let fn args cont  =
    cont args

let b = fn 10 (fun a ->
    a + 1
)

سوف تحتاج إلى التحول إلى:

let fn args cont  =
    cont args

let cont a =
    a + 1
let b = fn 10 cont

كيف يجب أن أفكر عند استبدال تعبير استدعاء واحد بعبارات متعددة لأنه عند تحويل استدعاء الوظيفة بـ args أحتاج إلى استبداله بمستوى أعلى ، أو استخدام نوع من بيان الكتلة ، مثل if true then ... أي أفكار؟

dbrattli راجع أيضًا هذه الطابعة ، التي تطبع بعض الخصائص أيضًا.

لم أكن أدرك أن أجزاء مهمة من تطبيق Fable BCL تمت كتابتها (على الأرجح) بلغة Javascript. افترضت أنه سيكون أكثر من F # بالفعل. لا أشك في وجود سبب وجيه لذلك - لماذا اتضح أنه أسهل بهذه الطريقة؟

نعم ، كما قال ncave ، كان السبب الرئيسي هو إنشاء المزيد من JS القياسية. على سبيل المثال ، في Funscript ، تمت كتابة جميع بدائل FSharp.Core بلغة F # ، ولكن مع Fable أردت الدمج مع معايير JS عندما يكون ذلك ممكنًا (على سبيل المثال ، باستخدام مكررات JS بدلاً من .NET IEnumerable) والتي جلبت العديد من مشاكل الدجاج والبيض. كان من الأسهل كتابتها في JS / Typescript في البداية. في البداية كان لدي أيضًا فكرة نشر مكتبة الخرافات كحزمة منفصلة يمكن استخدامها في مشاريع JS ، لكنني تجاهلتها بسرعة.

في Fable 2 ، بدأنا جهدًا لنقل بعض الوحدات إلى F #. لقد كان هذا مفيدًا جدًا لأنه يزيد من تجربة الاستخدام التجريبي ويسمح لنا بإعادة استخدام التحسينات في FSharp.Core بسهولة (كما هو الحال مع الخرائط والمجموعات مؤخرًا). ومع ذلك ، لا تزال هناك بعض الاختراقات هنا وهناك للتغلب على مشكلات الدجاج والبيض. ودعنا لا نتحدث عن الوحش الكبير الذي يمثل وحدة البدائل :)

أيضًا كما تقول ncave ، تعد صيانة هذه المكتبة مهمة ضخمة ، ولهذا السبب أنا متردد دائمًا في زيادة الدعم لـ BCL لتقليل سطح الصيانة ، لكنني كنت محظوظًا جدًا لوجود العديد من المساهمات العظيمة للمكتبة منذ البداية.

dbrattli حول طباعة AST ، لقد مرت فترة من الوقت لم أستخدم فيها أداة ASTViewer في Fable repo. أفضل الخيارات هي تلك التي أشار إليها ncave أو متخيل Fantomas وهو ما أستخدمه مؤخرًا عندما أحتاج إلى فحص F # AST (فقط تأكد من تحديد Show Typed AST ). للأسف ليس لدينا طابعات محددة لـ Fable أو Bable AST حتى الآن. في حالة Fable AST ، حيث إنها مجرد نقابات وتسجل عمل بسيط printfn "%A" ، على الرغم من أن معلومات الموقع صاخبة بعض الشيء.

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

شكرًا على المعلومات الرائعة alfonsogarciacaro ، كانت عملية PoC الأولى الخاصة بي تقوم فقط بتغيير Babel.fs / Fable2Babel.fs ، BabelPrinter.fs إلخ. لقد بدأت الآن من نقطة الصفر باستخدام Python AST منفصل مثل Python.fs ، PythonPrinter.fs . سأحاول بعد ذلك إضافة Babel2Python.fs للتحويل من Babel AST إلى Python AST لأن ذلك قد يكون أسهل من التحويل من Fable AST (ولكن ممكن أيضًا مع Fable2Python.fs إذا انتهى التحويل من Babel صعبة). بهذه الطريقة لن أتطرق إلى قاعدة الشفرة الحالية كثيرًا ، وسيكون لدي تحويل خاص بي حيث سأحاول إعادة كتابة lambda.

منطقي. تتمثل إحدى الصعوبات في أن Babel AST ليس مصنوعًا من DU ، لذا يصعب اجتيازه بمطابقة النمط. ربما يمكن أن يساعد # 2158؟

alfonsogarciacaro أعتقد أنني تمكنت من إصلاح إعادة كتابة وظائف الأسهم والتعبيرات الوظيفية بطريقة أعتقد (آمل) أنها قد تنجح بالفعل. الفكرة هي أن كل تعبير ( TransformAsExpr ) يُرجع أيضًا قائمة من العبارات (التي يجب أن تكون متسلسلة وتمريرها على طول الطريق حتى آخر قائمة من العبارات (مستوى العبارة الأخيرة). ثم كل سيتم رفع العبارات التي تم إرجاعها (func-def) وكتابتها أمام العبارات الأخرى. لذا فإن السهم - أو تعبير الوظيفة - سيعيد ببساطة name-expression, [statement ] حيث تتم إعادة كتابة تعبير السهم / الوظيفة إلى البيان وعاد مع تعبير الاسم. وهذا يعني أن:

module QuickTest

let fn args cont  =
    cont args

let b = fn 10 (fun a ->
    printfn "test"
    a + 1
)

يولد JS التالية:

import { printf, toConsole } from "./.fable/fable-library.3.1.1/String.js";

export function fn(args, cont) {
    return cont(args);
}

export const b = fn(10, (a) => {
    toConsole(printf("test"));
    return (a + 1) | 0;
});

والذي بدوره ينتج Python التالية:

from expression.fable.string import (printf, toConsole)

def fn(args, cont):
    return cont(args)


def lifted_5094(a):
    toConsole(printf("test"))
    return (a + 1) | 0


b = fn(10, lifted_5094)

اعتقدت أنني قد أواجه مشكلات في التعامل مع عمليات الإغلاق ، ولكن في معظم الحالات (كلها؟) يتم رفع الإغلاق أيضًا ، على سبيل المثال:

module QuickTest

let add(a, b, cont) =
    cont(a + b)

let square(x, cont) =
    cont(x * x)

let sqrt(x, cont) =
    cont(sqrt(x))

let pythagoras(a, b, cont) =
    square(a, (fun aa ->
        printfn "1"
        square(b, (fun bb ->
            printfn "2"
            add(aa, bb, (fun aabb ->
                printfn "3"
                sqrt(aabb, (fun result ->
                    cont(result)
                ))
            ))
        ))
    ))

ستتم إعادة كتابته إلى:

from expression.fable.string import (printf, toConsole)

def add(a, b, cont):
    return cont(a + b)


def square(x, cont):
    return cont(x * x)


def sqrt(x, cont):
    return cont(math.sqrt(x))


def pythagoras(a, b, cont):
    def lifted_1569(aa):
        toConsole(printf("1"))
        def lifted_790(bb):
            toConsole(printf("2"))
            def lifted_6359(aabb):
                toConsole(printf("3"))
                return sqrt(aabb, lambda result: cont(result))

            return add(aa, bb, lifted_6359)

        return square(b, lifted_790)

    return square(a, lifted_1569)

على أي حال ، إنها بداية جيدة 😄

فعلت نفس الشيء مع PHP ، (وهي تدير CrazyFarmers على BGA ) لذا ربما يمكننا دمج الجهد هنا؟

https://github.com/thinkbeforecoding/peeble

ncave هل لديك أية نصائح حول استخدام Fable للاستخدام التفاعلي مثل أجهزة الكمبيوتر المحمولة Jupyter؟ تكمن المشكلة في أن Jupyter سيعطيك جزءًا من التعليمات البرمجية (خلية) وتحتاج النواة إلى تجميع هذا الجزء مع أي تصريحات محلية موجودة من العبارات السابقة.

Fable ليس هو الأمثل لهذا ، لكنني أنشأت PoC حيث أحتفظ بقاموس مرتب من العبارات / التصريحات السابقة (اسمح ، اكتب ، افتح) وأنشئ برنامج F # صالحًا باستخدامها بالترتيب مع آخر جزء من الكود. تتم إزالة أي تصريحات في جزء الكود من Dict قبل التنفيذ وإضافتها بعد ذلك. يبدو أن هذا يعمل ، لكني أتساءل عما إذا كان هناك ربما طريقة أفضل؟

المصدر: https://github.com/dbrattli/Fable.Jupyter/blob/main/fable/kernel.py#L85

ثم لدي Fable cli يعمل في الخلفية والذي يعيد ترجمة كلما تم تحديث Fable.fs.

dotnet watch -p src/Fable.Cli run -- watch --cwd /Users/dbrattli/Developer/GitHub/Fable.Jupyter --exclude Fable.Core --forcePkgs --python

dbrattli تبدو معقولة ، إذا كانت الأجزاء صغيرة. بالنسبة للخلايا الأكبر حجمًا ، ربما يؤدي استخدام ملف منفصل لكل خلية (جزء) إلى تسريع الأمور ، حيث يستخدم Fable بعض التخزين المؤقت لتخطي العمل غير الضروري على الملفات التي لم تتغير.

فقط لمعلوماتك ، إذا كنت بحاجة إلى مزيد من التحكم ، فيمكنك أيضًا استخدام Fable كمكتبة ، وليس فقط CLI (انظر fable-standalone ، ومثال الاستخدام).

1) مشكلة لدي هي أن Fable AST لا يحتوي على Throw / Raise. لذلك سيتم تحويل هذا إلى تعبير Emit يصعب عليّ تحليله. أو على الأقل يبدو غير ضروري عندما يكون لدى Babel بيان رمى. هل هناك سبب وجيه لعدم استخدامه ، أم يجب علي محاولة إصلاحه؟ تضمين التغريدة

let divide1 x y =
   try
      Some (x / y)
   with
      | :? System.DivideByZeroException -> printfn "Division by zero!"; None

let result1 = divide1 100 0

يولد:

export function divide1(x, y) {
    try {
        return ~(~(x / y));
    }
    catch (matchValue) {
        throw matchValue;
    }
}

export const result1 = divide1(100, 0);

حيث throw matchValue هو EmitExpression .

2) هل يجب إضافة ThisExpr إلى Fable AST ، لذلك يمكنني تحديد ما إذا كان يتم استخدام this ككلمة رئيسية هذه. لا يمكنني التأكد من تعيين Fable.IdentExpr على this إذا كانت هذه الكلمة الرئيسية أم لا لأن هذه الكلمات تحتاج إلى ترجمتها إلى self في Python.
https://github.com/fable-compiler/Fable/blob/nagareyama/src/Fable.Transforms/Fable2Babel.fs#L792

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

حول "هذا" ، إنه أمر صعب. أحتاج إلى التحقق من ذلك مرة أخرى لأنه في F # AST يتم تمثيل هذا بشكل مختلف في المنشئات أو الطرق (هناك بعض التعليقات حول هذا منتشرة من خلال الكود). توجد خاصية IsThisArgument للمعرفات في Fable AST ، لكن دعني أتحقق مما إذا كانت هذه هي الوسيطة الأولى للأعضاء المنفصلين فقط أو ما إذا كانت تعمل أيضًا مع "هذا فعليًا".

alfonsogarciacaro يمكنني استخدام القليل من المساعدة في كيفية التعامل مع التعليمات البرمجية المجمعة "الداخلية" مقابل "المستخدم" ، على سبيل المثال ، يحتوي الاستثناء على .message ، ولكن كيف يمكنني ترجمة هذا؟ هل يمكنني التأكد من أن الشفرة التي قام المستخدم بجمعها لن تحتوي أبدًا على .message وستنتهي دائمًا بدلاً من ذلك مثل Test__Message_229D3F39(x, e) لذلك لن يتدخلوا أبدًا؟ أو هل يجب أن أقوم بلف استثناء تم التقاطه داخل كائن JsException سبيل المثال وإضافة خاصية .message فقط للتأكد؟ كيف تقوم الخرافة بهذا من F # إلى Babel؟ هل تتعقب الأنواع ، أم؟

على سبيل المثال: كيف يصبح ex.Message.StartsWith في F # ex.message.indexOf في JS؟ سأحتاج إلى ترجمة هذا إلى str(ex).index في Python (أو لف الكائنات).

في الواقع لدينا مشكلة مع استثناءات F # (كما في: استثناء Foo من int * int) لم أقم بإصلاحها قبل إصدار Fable 3 System.Exception تمت ترجمته على أنه خطأ JS لذلك لديهم حقل رسالة. لكن استثناءات F # لا تنبع من خطأ JS لأسباب تتعلق بالأداء (أعتقد الآن أن محاولة الوصول إلى .Message فشلت للتو). هناك نقاش حول هذا في مكان ما. بالنسبة لقضيتك وللاستثناء العام ، أعتقد أنه يمكنك افتراض أن Fable يصل الآن إلى خصائص .message و .stack.

كيف بالمناسبة نريد أن يكون اختيار اللغة في Fable؟ اليوم يمكننا تحديد على سبيل المثال TypeScript باستخدام --typescript . لذلك يمكننا تحديد Python باستخدام --python . ولكن هل يجب أن نتوقف عن توليد .js إذن؟ ربما يجب أن يكون JS الذي تم إنشاؤه غير صالح إذا تم استخدام Emit على سبيل المثال. لكن في بعض الأحيان نريد أن نرى JS. هل يجب أن يكون لدينا خيار --javascript ، أم يجب أن نطلب من المستخدم التشغيل بدون --python للحصول على JavaScript؟ تضمين التغريدة

ينشئdbrattli --typescript .ts ، لذا يمكن لـ --python إنشاء ملفات .py ، إذا كنت تفضل ذلك.
لكن من الناحية الفنية ، كلهم ​​حصريون بشكل متبادل ، لذا هل يجب علينا بدلاً من ذلك تقديم مفتاح جديد --language ، بقيم JavaScript/TypeScript/Python ؟

ncave نعم ، أعتقد أنها فكرة جيدة شيئًا مثل [-lang|--language {"JavaScript"|"TypeScript"|"Python"}] حيث يمكن أن يكون JavaScript افتراضيًا. لقد بدأت بالفعل في إضافة خاصية لغة للمترجم ، لذا يمكنني محاولة إصلاح خيارات سطر الأوامر أيضًا؟ https://github.com/fable-compiler/Fable/pull/2345/files#diff -9cb94477ca17c7556e6f79d71ed20b71740376f7f3b00ee0ac3fdd7e519ac577R12

بعض الوضع. دعم Python يتحسن. توجد الآن العديد من ميزات اللغة. المهمة الضخمة المتبقية هي نقل مكتبة الخرافات. لتسهيل هذا الأمر ، تم نقل مكتبة الخرافات الخاصة بـ Python إلى الريبو ، حتى نتمكن من تحويل ملفات مكتبة الخرافات العديدة .fs وإعادة استخدامها إلى Python. ومع ذلك ، فإن العديد منها يحتوي على كود منبعث من JS يحتاج إلى اعتراضه والتعامل معه "يدويًا". يتم حاليًا إعداد اختبار لـ Python (استنادًا إلى pytest) مع اجتياز 43 اختبارًا ، مما يسهل كثيرًا تطوير وإضافة ميزات جديدة (ويظهر ما ينجح).

أنشئ مكتبة خرافية لـ Python: dotnet fsi build.fsx library-py
قم بتشغيل اختبارات Python: dotnet fsi build.fsx test-py

تضمين التغريدة
من الناحية المثالية ، يجب أن نحاول تحويل المزيد من fable-library إلى F #. List و Seq موجودان بالفعل ، يستخدم Array عددًا صغيرًا من الطرق الأصلية ، ربما يمكن فصلها.

فيما يتعلق بموضوع مختلف قليلاً ، في رأيك ، ما هي الفوائد الرئيسية لتطبيق رمز لغة جديد من Babel AST ، بدلاً من Fable AST مباشرةً؟

ncave Python و JavaScript قريبان جدًا (imo) لذا من السهل نسبيًا إعادة كتابة Babel AST. لقد قمت بنقل القليل من JS إلى Python في وقت سابق ، على سبيل المثال RxJS إلى RxPY. ونستفيد أيضًا من جميع الإصلاحات (الأخطاء) التي يتم إجراؤها "في المنبع". ومع ذلك ، يمكننا إعادة استخدام معظم Fable2Babel.fs والتحويل مباشرة إلى Python باستخدام Fable2Python.fs الذي يجمع / ينهار Fable2Babel.fs و Babel2Python.fs . لقد فكرت في ذلك في وقت سابق اليوم. بعد ذلك يصبح الأمر أشبه بالشوكة ومن المرجح أن يتم تطبيق إصلاحات الأخطاء على كلا المكانين. في الوقت الحالي ، أعتقد أنه من الجيد تحويل Babel AST ، لكن في النهاية قد نرغب في تحويل Fable AST مباشرة.

هذا شيء عظيم dbrattli! شكرا جزيلا على كل عملك. في الواقع ، ما كان يدور في خاطري هو وضع معظم كود Fable في مكتبة وإطلاق التطبيقات المختلفة كأدوات dotnet مستقلة: fable (-js) ، fable-py (أو أي اسم آخر). ربما يمنحنا ذلك مزيدًا من الحرية في دورات الإصدار ، لكنني موافق تمامًا أيضًا على وجود أداة دوت نت واحدة يمكنها تجميع اللغتين. يمكن أن يكون ذلك أبسط ، خاصة في البداية.

حول مكتبة fable ، نعم ، إذا كان ذلك ممكنًا ، بدلاً من إعادة كتابة ملفات .ts الحالية في python ، سيكون من الجيد نقلها إلى F # بدلاً من ذلك. يجب أن نحاول عزل الأجزاء باستخدام Emit وتكييفها مع Python إما باستخدام #if :

#if FABLE_COMPILER_PYTHON
    [<Emit("print($0)")>]
    let log(x: obj) = ()

 // Other native methods ...
#else
    [<Emit("console.log($0)")>]
    let log(x: obj) = ()

    // ...
#endif

أو يمكننا إضافة سمة Emit جديدة "متعددة اللغات":

type EmitLangAttribute(macros: string[]) =
    inherit Attribute()

[<EmitLang([|"js:console.log($0)"; "py:print($0)"|])>]
let log(x: obj) = ()

شكرًا alfonsogarciacaro و ncave ، هذه أفكار رائعة. سأجربهم لمعرفة ما ينجح. أرى فائدة ترجمة مكتبة الخرافات إلى F # لذا سأحاول المساعدة هنا بمجرد أن يكون مترجم Python جيدًا بما يكفي للتعامل مع الملفات هناك. من المفترض أن يساعد استخدام #if كثيرًا ، وربما يمكننا تجنب #if s عن طريق نقل عمليات الإرسال إلى ملفات مكتبة لغة معينة (نظرًا لأن لدي ملف .fsproj منفصل لـ Python.

alfonsogarciacaro لماذا لا يكون

بالنسبة إلى سنتي 2 ، أحب الحمل المعرفي المنخفض الحالي لامتلاك سمة Emit واحدة تنبعث منها ببساطة. Imo ما يفعله dbrattli الآن أمر منطقي (ملف مشروع مختلف لكل إصدار لغة fable-library ). يمكننا فصل اللغات المختلفة المنبعثة في ملفاتها الخاصة ، والتي تنفذ واجهات محددة جيدًا (أو وحدات) ليتم استدعاؤها لاحقًا عن تطبيق F # المشترك.

قد يكون من الأمثلة الجيدة تحويل Array.Helpers إلى واجهة (أو اتركها كوحدة نمطية فقط) يمكن تنفيذها لكل لغة هدف. ربما هذا الجهد لترحيل المزيد من fable-library إلى F # (بالإضافة إلى خيار المترجم --language ) يمكن أن ينتقل إلى علاقات عامة منفصلة عن هذا ، لذلك يمكن أن يكون أسرع ويمكن المساهمة فيه بسهولة أكبر .

هذه فكرة جيدة ncave : +1: في الواقع نحن نقوم بذلك بالفعل عند عزل الكود الأصلي في مشاريع Fable متعددة الأنظمة (.net و js). لتبسيط الأمور ، ربما سأضيف ملفًا جديدًا Native.fs إلى Fable.Library ولديها وحدات فرعية هناك حسب الحاجة ، مثل:

namespace Fable.Library.Native            # or just Native

module Array = ..
module Map = ..

يجب أن ننتقل إلى هناك أي شيء يستخدم Emit أو قيمًا من Fable.Core.JS .

أعمل على عدم التزامن أحاول نقل Async.ts و AsyncBuilder.ts إلى F # ، أي وجود Async.fs و AsyncBuilder.fs . لكن الآن لدي مشكلة في ملف الاختبار الخاص بي برمز:

async { return () }
|> Async.StartImmediate

يولد كود Python (يجب أن يكون كود JS مشابهًا جدًا):

startImmediate(singleton.Delay(lambda _=None: singleton.Return()))

ومع ذلك ، لا يحتوي AsyncBuilder على أي طرق ولكن هناك AsyncBuilder__Delay_458C1ECD . لذلك أحصل على AttributeError: الكائن "AsyncBuilder" ليس له سمة "تأخير".

class AsyncBuilder:
    def __init__(self):
        namedtuple("object", [])()

def AsyncBuilder__ctor():
    return AsyncBuilder()

def AsyncBuilder__Delay_458C1ECD(x, generator):
    def lifted_17(ctx_1):
        def lifted_16(ctx):
            generator(None, ctx)

        protectedCont(lifted_16, ctx_1)

    return lifted_17
...

هل من تلميحات حول كيفية التعامل مع هذا؟ تضمين التغريدة هل هناك طريقة يمكنني من خلالها إنشاء فئات F # باستخدام طرق ، أو جعل اختباراتي تنشئ رمزًا يستخدم الرمز الثابت AsyncBuilder__Delay_458C1ECD بدلاً من .Delay() ؟

dbrattli عادةً ما يكون الرد على de-mangling هو استخدام واجهات للطرق التي لا تريد أن تتشوه.
في ملاحظة ذات صلة ، هل الدعم غير المتزامن إحدى تلك الميزات التي ربما تكون مناسبة بشكل أفضل للترجمة إلى لغة Python الأصلية asyncio ؟

ncave طيب ، هذه هي الطريقة التي تعمل بها 😄 شكرا على البصيرة. سأحاول وضع المُنشئ خلف الواجهة ومعرفة ما إذا كان مفيدًا (حتى لو لم تكن أدوات إنشاء F # واجهات ، فمن المحتمل أن تعمل بشكل جيد). بالنسبة إلى async ، كانت الخطة هي استخدام لغة Python asyncio الأصلية ( انظر هنا ) ، لكن واجهت مشاكل مع python cannot reuse already awaited coroutine ، لذلك أحتاج إلى تضييقها خلف وظيفة. على أي حال ، اكتشفت أنه يمكنني أيضًا استخدام أداة إنشاء task أو asyncio لأشياء منتظرة من لغة Python. جذبني تبسيط AsyncBuilder.ts لمحاولة نقله إلى F #.

يجب أن يعمل استخدام واجهة لتجنب التشويش كما يقول

  • السمة NoOverloadSuffix : تمنع Fable من إضافة لاحقة التحميل الزائد. من الواضح أن الأحمال الزائدة لن تعمل في هذه الحالة.

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/fable-library/Map.fs#L524 -L527

ثم يمكننا الرجوع إلى الطرق باستخدام Naming.buildNameWithoutSanitation :

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/Fable.Transforms/Replacements.fs#L1956 -L1963

  • ولكن بعد ذلك ، كتب ncave طريقة لإنشاء أسماء src/fable-library/System.Text.fs :

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/Fable.Transforms/Replacements.fs#L1314 -L1326

في هذه الحالة ، ما عليك سوى إضافة الفصل إلى القاموس replacedModules :

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/Fable.Transforms/Replacements.fs#L3075


لسوء الحظ ، لا يوجد نهج ثابت حتى الآن للربط بكود مكتبة الخرافات المكتوبة في F # من Replacements . ربما نستغل هذه الفرصة للاتفاق على واحدة الآن.

شكرًا على الرؤى الشيقة للغاية unit في بايثون؟ تبدو واجهتي على النحو التالي:

type IAsyncBuilder =
    abstract member Bind<'T, 'U> : IAsync<'T> * ('T -> IAsync<'U>) -> IAsync<'U>
    abstract member Combine<'T> : IAsync<unit> * IAsync<'T> -> IAsync<'T>
    abstract member Delay<'T> : (unit -> IAsync<'T>) -> IAsync<'T>
    abstract member Return<'T> : value: 'T -> IAsync<'T>
   ...

المشكلة هي أن Return يولد:

class AsyncBuilder:
    def Return(self, value):
        return protectedReturn(value)
    ....

يبدو هذا جيدًا وربما يكون مفيدًا لـ JS ، لكن في بايثون يجب أن تعطي حجة إذا كانت الوظيفة تأخذ حجة. وبالتالي ستحصل على خطأ إذا اتصلت x.Return() عندما يكون 'T هو unit . كان رد فعلي الأول هو زيادة الحمل:

abstract member Return : unit -> IAsync<unit>

لكن هذا له مشاكله الخاصة. الحل الحالي (الذي يبدو قبيحًا هو):

abstract member Return<'T> : [<ParamArray>] value: 'T [] -> IAsync<'T>

...

member this.Return<'T>([<ParamArray>] values: 'T []) : IAsync<'T> =
    if Array.isEmpty values then
        protectedReturn (unbox null)
    else
        protectedReturn values.[0]

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

class AsyncBuilder:
    def Return(self, value=None):
        return protectedReturn(value)
    ....

ما هي الطريقة الصحيحة للتعامل مع هذه المشكلة؟

IIRC ، بالنسبة للطرق التي تحتوي على توقيع unit -> X ، لا تحتوي الاستدعاءات على وسيطات (كما في F # AST) ولكن بالنسبة إلى lambdas أو في هذه الحالة ، فإن الوسائط العامة المملوءة بـ unit المكالمات / التطبيقات لها unit وسيطة transformCallArgs لخطوة Fable2Babel. ربما يمكننا إضافة شرط هناك وترك وسيطة الوحدة عندما تكون اللغة الهدف هي python:

https://github.com/fable-compiler/Fable/blob/caa715f1156be29c8dd9b866a03031a1852b3186/src/Fable.Transforms/Fable2Babel.fs#L1083 -L1086

مرحبًا ncave ، alfonsogarciacaro ، يمكنني استخدام بعض المدخلات في معالجة معاملات [<Inject>] في بايثون. كيف يتم ذلك في Phpthinkbeforecoding؟ على سبيل المثال ، وظائف مثل (في Array.fs ):

let map (f: 'T -> 'U) (source: 'T[]) ([<Inject>] cons: Cons<'U>): 'U[] =
    let len = source.Length
    let target = allocateArrayFromCons cons len
    for i = 0 to (len - 1) do
        target.[i] <- f source.[i]
    target

لا يُنشئ الكود الخاص بي المعلمة cons وهو مطلوب لـ Python.

map(fn ar)

هل هو اختياري لـ JS؟ كيف يمكنني اكتشاف السمة في الكود وجعلها اختيارية؟ على سبيل المثال

def map(f, source, cons=None):
    ...

Php أيضًا صريح في المعلمات الاختيارية. اضطررت إلى إضافة علامة IsOptional على نموذج الوسيطة.

هناك استخدامان لسمة Inject في Fable. أحدهما تجريبي بشكل أساسي (لمحاكاة أنواع الحروف بطريقة ما وأيضًا لتجنب الاضطرار إلى تضمين دالة لحل معلومات النوع العام) ولا يجب أن تهتم كثيرًا به.

الاستخدام الآخر داخلي في أساليب مكتبة الخرافات التي تحتاج إلى بعض المعلومات الإضافية التي تم تمريرها بواسطة وسيطة تم حلها في وقت الترجمة:

  • تحتاج الوظائف التي تنشئ مصفوفة إلى معرفة ما إذا كانت مصفوفة ديناميكية أو مصفوفة مكتوبة.
  • قم بتعيين مُنشئي الخرائط والمساعدين الآخرين الذين يحتاجون إلى الحصول على مقارنة لمعرفة كيفية مقارنة النوع (مماثل للوظائف التي تقوم ببعض الحسابات المتوسطة).

ومع ذلك ، لا يتم استدعاء هذه الوظائف بشكل مباشر ، لذلك "لا يمكن لـ Fable رؤية" السمة Inject . لهذا السبب ، نستخدم ملف ReplacementsInject.fs الذي يوضح الوظائف في المكتبة التي تتطلب الحقن. تعرف على كيفية استخدامه: https://github.com/fable-compiler/Fable/blob/522f6aad211102271538798aeb90f4aed1f77dd6/src/Fable.Transforms/Replacements.fs#L988-L1019

تم إنشاء هذا الملف تلقائيًا في البداية مع هذا البرنامج النصي الذي يكتشف الوظائف في Fable.Library لها الوسيط الأخير مزين بـ Inject . لكني أعتقد أننا توقفنا في مرحلة ما عن تحديث و IIRC آخر تحديثات لـ ReplacementInjects. الملف يدويا.

مع العلم بذلك ، ربما يمكنني التخلص من معلومات IsOptional ... سأتحقق منها

alfonsogarciacaro يبدو أن هناك مشكلة في injectArg للرمز مثل:

type Id = Id of string

let inline replaceById< ^t when ^t : (member Id : Id)> (newItem : ^t) (ar: ^t[]) =
    Array.map (fun (x: ^t) -> if (^t : (member Id : Id) newItem) = (^t : (member Id : Id) x) then newItem else x) ar

let ar = [| {|Id=Id"foo"; Name="Sarah"|}; {|Id=Id"bar"; Name="James"|} |]
replaceById {|Id=Id"ja"; Name="Voll"|} ar |> Seq.head |> fun x -> equal "Sarah" x.Name
replaceById {|Id=Id"foo"; Name="Anna"|} ar |> Seq.head |> fun x -> equal "Anna" x.Name

هنا لن يتم حقن المُنشئ Array.map . سيتم تحويل الكود إلى JS بدون أي وسيط محقون:

return map((x) => (equals(newItem.Id, x.Id) ? newItem : x), ar);

هذا الخلل؟ سيعمل هذا الرمز في JS (عن طريق الحظ؟) ولكن ليس مع Python.

اه اسف! نسيت هذا تمامًا ولكن لدينا "تحسين" حيث لا يتم حقن مُنشئ المصفوفة لمصفوفة JS "القياسية": https://github.com/fable-compiler/Fable/blob/4ecab5549ab6fcaf317ab9484143420671ded43b/src/Fable.Transforms/ Replacements.fs # L1005 -L1009

بعد ذلك ، ننتقل افتراضيًا إلى Array عندما لا يتم تمرير أي شيء: https://github.com/fable-compiler/Fable/blob/4ecab5549ab6fcaf317ab9484143420671ded43b/src/fable-library/Array.fs#L25 -L28

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

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

            | Types.arrayCons ->
                match genArg with
                | Number(numberKind,_) when com.Options.TypedArrays ->
                    args @ [getTypedArrayName com numberKind |> makeIdentExpr]
                | _ -> args @ [ Expr.Value(ValueKind.Null genArg, None) ]

بالنسبة لـ JS ، سيتم إنشاء:

map((x_3) => (equals(newItem_1.Id, x_3.Id) ? newItem_1 : x_3), ar, null))).Name);

ولبايثون:

def lifted_53(x_2):
    return newItem_1 if (equals(newItem_1["Id"], x_2["Id"])) else (x_2)

return map(lifted_53, ar, None)

هل شيء من هذا القبيل يكون حلاً مقبولاً؟

التي يجب أن تعمل. ربما يمكننا استخدام None بدلاً من ذلك. في مرحلة ما كنا نزيل None args في الموضع الأخير في Fable2Babel ، يبدو أننا نقوم بذلك الآن في FSharp2Fable ، لكن يمكننا إضافة تحقق إضافي هنا: https://github.com/fable-compiler/ Fable / blob / c54668b42b46c0538374b6bb2e283af41a6e5762 / src / Fable.Transforms / Fable2Babel.fs # L1082 -L1095

راجع للشغل ، آسف لأنني لم أتحقق من هذه العلاقات العامة بعمق 😅 ولكن إذا لم يتم كسر الاختبارات ، فيمكننا دمجها قريبًا إلى التالي مثل POC كما فعلنا مع PHP. هذا لأنني أخطط (يسمح الوقت) للقيام ببعض إعادة البناء لتسهيل استهداف لغات متعددة باستخدام Fable ، وربما سيكون من الأسهل إذا كانت Python موجودة بالفعل في فرع next لذلك. نتجنب أيضًا الاضطرار إلى الاحتفاظ بالعديد من الفروع المتباينة في تزامن (رئيسي ، تالي ، بيثون).

حسنًا alfonsogarciacaro ، Fable.Core.PY.fs بالانبعاثات المطلوبة. كانت إعادة الكتابة من Babel2Python -> Fable2Python أصعب مما كنت أتوقع ، لكنني أخيرًا اقتربت من العودة إلى المسار الصحيح مرة أخرى. سوف أقوم بتنظيف العلاقات العامة قليلاً وجعلها جاهزة للدمج.

alfonsogarciacaro لقد أصلحت جميع الاختبارات باستثناء اختبار واحد. ومع ذلك ، فهو مرتبط بنفس المشكلة القديمة التي واجهتها مع Babel أحتاج إلى طريقة لمعرفة ما إذا كان Fable.Get يُستخدم في نوع AnonymousRecord نظرًا لأنه يتحول إلى Python deb ، وأحتاج إلى استخدام رمز منخفض أي [] وصول وليس .dotted . المشكلة هي أن الجانب الأيسر يمكن أن يكون أي شيء ، أو كائن ، أو استدعاء وظيفة ، ... لذلك من الصعب معرفة ذلك. هل هناك طريقة أفضل. هل يجب أن يكون لدينا GetKind للسجلات المجهولة ، أم ما هي أفضل طريقة للقيام بذلك؟

أرى هذا في FSharp2Fable. كيف أعرف لاحقًا أن FieldGet تم إنشاؤه بواسطة AnonRecord؟

    // Getters and Setters
    | FSharpExprPatterns.AnonRecordGet(callee, calleeType, fieldIndex) ->
        let r = makeRangeFrom fsExpr
        let! callee = transformExpr com ctx callee
        let fieldName = calleeType.AnonRecordTypeDetails.SortedFieldNames.[fieldIndex]
        let typ = makeType ctx.GenericArgs fsExpr.Type
        return Fable.Get(callee, Fable.FieldGet(fieldName, false), typ, r)

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

type FieldGetInfo =
    { Name: string
      IsMutable: bool
      IsAnonymousRecord: bool }

type GetKind =
    | FieldGet of info: FieldGetInfo
    | ...

شكراalfonsogarciacaro. هذا مشغول! 🎉

الاصدار القادم:

testCase "Map.IsEmpty works" <| fun () ->
    let xs = Map.empty<int, int>
    xs.IsEmpty |> equal true
    let ys = Map [1,1; 2,2]
    ys.IsEmpty |> equal false

بالنسبة إلى JS ، يتم تجميع هذا إلى:

Testing_testCase("Map.isEmpty works", () => {
    Testing_equal(true, isEmpty_1(ofSeq([], {
        Compare: (x_1, y_1) => compare(x_1, y_1),
    })));
    Testing_equal(false, isEmpty_1(ofSeq([[1, 1]], {
        Compare: (x_2, y_2) => comparePrimitives(x_2, y_2),
    })));
})

لاحظ أنه يتم استدعاء ofSeq باستخدام وسيطتين. ومع ذلك ، فإن ofSeq يأخذ وسيطة واحدة فقط:

let ofSeq elements =
    Map<_, _>.Create elements
export function ofSeq(elements) {
    return FSharpMap_Create(elements);
}

لماذا تمت إضافة تعبير الكائن مع Compare عند استدعاء ofSeq ؟

حسنًا ، أنا بحاجة للنظر في هذا. يبدو أنه خطأ ، يجب أن يقبل ofSeq المقارنة (وظائف مشابهة مثل MapTree.ofSeq و Set.ofSeq do). الإعداد معقد بعض الشيء ، لذا ربما في مرحلة ما خرجت الأمور عن المزامنة: لدينا ملف باسم ReplacementsInject.fs يستخدمه Replacements للإشارة إلى الطرق التي تحتاج إلى حقن الوسيطة. يوجد نص برمجي في مكان ما لإنشاء هذا الملف تلقائيًا ولكن لم نستخدمه منذ فترة ولست متأكدًا مما إذا كان يعمل مع أحدث إصدار من FCS. سوف أتحقق ، شكرا للإشارة إلى ذلك!

أضفت إصلاحًا مؤقتًا. جاهز للمراجعة 🎉 https://github.com/fable-compiler/Fable/pull/2345

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات