Fable: Поддержка Python для Fable

Созданный на 4 янв. 2021  ·  54Комментарии  ·  Источник: fable-compiler/Fable

Описание

В этом выпуске обсуждается поддержка Python для Fable. Вавилонский AST, используемый в настоящее время Fable, достаточно близок для генерации кода Python. Особенно сейчас, когда поддерживаются классы.

POC был создан, чтобы показать, что это возможно. Однако потребуется довольно много работы, чтобы:

  1. Добавить достойную поддержку Python
  2. Сделайте Fable более независимым от JS и поддерживайте несколько целевых языков.

Библиотека Expression обеспечивает функциональное программирование на Python, вдохновленное F #. Он обеспечивает собственную реализацию типов данных, таких как option , result , seq , map , list (так называемый FrozenList), почтовый ящик. процессор, ... Таким образом, он должен подходить как Python-эквивалент библиотеки Fable. В Python вы используете Expression для выполнения кода F #. В Fable вы генерируете код Python с помощью Expression.

Случаи применения:

  • F # работает в записной книжке Jupyter с ядром Python, то есть аналогично тому, что сделано, например, с Hy и Calysto Hy .
  • Создание сценариев F # с использованием Python для уменьшения трения
  • Более легко поддерживать встроенные среды, такие как micro: bit и Rasberry PI .
  • Совместное использование модели данных между кодом F # и Python

Что нужно обсудить и решить:

  • Должен ли Python для F # быть более питоническим, чем .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. Сборка fable-библиотеки для 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 в качестве основного направления, поэтому потребовалась бы некоторая работа, чтобы сделать его более независимым от языка, но я был бы открыт для этого. Некоторые комментарии к вашим вопросам:

  • То же или другое репо? Я бы поддержал как можно больше общего кода. Использование разных репозиториев, вероятно, приведет к тому, что они расходятся довольно быстро (уже сложно синхронизировать ветку с поддержкой цитат), но нам также необходимо сделать код достаточно модульным, чтобы у нас не везде были условия в зависимости от целевого языка. Промежуточным решением было бы попытаться извлечь как можно больше кода в Fable "Nucleus" (мы не можем использовать Core 😉), а затем иметь разные репозитории для языковых реализаций.

  • Babel AST: Это использовалось, потому что изначально мы полагались на Babel для печати кода. В идеальном мире, когда у нас есть принтер, его можно будет удалить. Но на этапе от Fable-to-Babel происходит много работы, поэтому это будет сложно. По той же причине было бы предпочтительнее повторно использовать как можно больше кода из этого шага, возможно, сделав Babel AST чем-то более общим для нефункциональных языков. Некоторые примеры:

    • Перейти от AST на основе выражений к AST с выражениями / операторами. Для JS это включает перемещение объявлений переменных в начало функции, когда это необходимо, чтобы избежать слишком большого количества IIFE. Думаю, с Python будет что-то подобное.
    • Преобразование сопоставления с образцом (DecisionTree) в операторы if / switch.
    • Оптимизация хвостовых звонков. Это делается с помощью петель, помеченных JS. В Python нам нужно было бы использовать другую технику, чтобы убедиться, что мы не выйдем из вложенного цикла.
    • Преобразование импорта в уникальные идентификаторы (при необходимости это можно сделать прямо в Fable AST).
  • Сделать F # более Pythonic? В Fable я всегда пытался найти баланс, при котором семантика .NET иногда приносится в жертву, чтобы избежать накладных расходов. Например, мы просто используем номер JS вместо того, чтобы иметь определенный тип для int (хотя мы делаем это для longs), это применимо к Python int я полагаю. Тем не менее, было сделано много вкладов, чтобы уважать семантику .NET в определенных точках, чтобы избежать неожиданных результатов, например, в целочисленных делениях или явных числовых преобразованиях.
    В любом случае, когда вы говорите «более питонический», вы имеете в виду стиль кода или собственные API? Fable не пытается поддерживать большую часть .NET BCL, но, в конце концов, нам нужно поддерживать общие функции / операторы, потому что это то, к чему привыкли разработчики (даже новички, потому что они найдут это в учебниках). В любом случае хорошо дать возможность использовать собственные API ( Fable.Extras - хороший ход в этом смысле). Например, JS.console.log обычно форматирует объекты лучше, чем printfn , но нам нужно поддерживать последнее, потому что это то, что разработчики используют больше всего.

Все 54 Комментарий

Это отличная работа @dbrattli! 👏 👏 👏 Моя первоначальная идея с Fable заключалась в том, чтобы предоставить способ простой компиляции F # для других языков. К лучшему или худшему, Fable эволюционировала с JS в качестве основного направления, поэтому потребовалась бы некоторая работа, чтобы сделать его более независимым от языка, но я был бы открыт для этого. Некоторые комментарии к вашим вопросам:

  • То же или другое репо? Я бы поддержал как можно больше общего кода. Использование разных репозиториев, вероятно, приведет к тому, что они расходятся довольно быстро (уже сложно синхронизировать ветку с поддержкой цитат), но нам также необходимо сделать код достаточно модульным, чтобы у нас не везде были условия в зависимости от целевого языка. Промежуточным решением было бы попытаться извлечь как можно больше кода в Fable "Nucleus" (мы не можем использовать Core 😉), а затем иметь разные репозитории для языковых реализаций.

  • Babel AST: Это использовалось, потому что изначально мы полагались на Babel для печати кода. В идеальном мире, когда у нас есть принтер, его можно будет удалить. Но на этапе от Fable-to-Babel происходит много работы, поэтому это будет сложно. По той же причине было бы предпочтительнее повторно использовать как можно больше кода из этого шага, возможно, сделав Babel AST чем-то более общим для нефункциональных языков. Некоторые примеры:

    • Перейти от AST на основе выражений к AST с выражениями / операторами. Для JS это включает перемещение объявлений переменных в начало функции, когда это необходимо, чтобы избежать слишком большого количества IIFE. Думаю, с Python будет что-то подобное.
    • Преобразование сопоставления с образцом (DecisionTree) в операторы if / switch.
    • Оптимизация хвостовых звонков. Это делается с помощью петель, помеченных JS. В Python нам нужно было бы использовать другую технику, чтобы убедиться, что мы не выйдем из вложенного цикла.
    • Преобразование импорта в уникальные идентификаторы (при необходимости это можно сделать прямо в Fable AST).
  • Сделать F # более Pythonic? В Fable я всегда пытался найти баланс, при котором семантика .NET иногда приносится в жертву, чтобы избежать накладных расходов. Например, мы просто используем номер JS вместо того, чтобы иметь определенный тип для int (хотя мы делаем это для longs), это применимо к Python int я полагаю. Тем не менее, было сделано много вкладов, чтобы уважать семантику .NET в определенных точках, чтобы избежать неожиданных результатов, например, в целочисленных делениях или явных числовых преобразованиях.
    В любом случае, когда вы говорите «более питонический», вы имеете в виду стиль кода или собственные API? Fable не пытается поддерживать большую часть .NET BCL, но, в конце концов, нам нужно поддерживать общие функции / операторы, потому что это то, к чему привыкли разработчики (даже новички, потому что они найдут это в учебниках). В любом случае хорошо дать возможность использовать собственные API ( Fable.Extras - хороший ход в этом смысле). Например, JS.console.log обычно форматирует объекты лучше, чем printfn , но нам нужно поддерживать последнее, потому что это то, что разработчики используют больше всего.

Мне нравится эта идея. Особенно я с нетерпением жду возможности использовать библиотеки Python в F #. В идеале у нас должна быть возможность использовать либо чистые библиотеки Python, либо SciSharp - они, как правило, имеют почти тот же API, что и Python (тот же код F # выполняется в контексте Python или .NET!)

Такой код F # можно скомпилировать с помощью Fable в Python, а затем использовать в ядрах Notebooks (Jupyter или .NET Interactive), что делает рабочий процесс аналитики реальным средством, управляемым предметной областью (я мог бы рассматривать все это как «смертельную возможность»).

.NET Interactive очень расширяема, запускать Fable внутри ядра F # очень просто (у меня есть работающий 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 и задавать вопросы по ходу дела, например, о преобразованиях и т. Д. Здесь есть чему поучиться.

PS: Expression btw также имеет декоратор хвостового вызова, который мы можем использовать (как синхронизацию, так и асинхронность). Вот пример использования в aioreactive.

Напоминает №1601.

Интересно, есть ли хороший способ сделать Fable достаточно мощным, чтобы это можно было делать как сторонние бэкэнд-плагины (думаю, LLVM, но специфичный для F #). Безграничные возможности.

Мы могли бы попытаться сделать Babel AST более универсальным (и типизированным), чтобы его можно было легко преобразовать / распечатать на C-подобных языках. Однако, если я чему-то научился при разработке Fable, так это то, что написать простой компилятор F # на другой язык (более или менее) легко, но он обеспечивает хороший опыт разработки и интеграцию, поэтому преимущества F # за вычетом трения с Зарубежные экосистемы весят больше, чем развиваться на родном языке довольно сложно.

Что сказал @alfonsogarciacaro . Часть этой задачи состоит в том, чтобы перенести большую часть реализации Fable BCL на F #, чтобы ее не нужно было переписывать для каждого языка, сохраняя при этом приемлемую собственную производительность для сгенерированного кода, что нетривиально даже для JavaScript.

Интересно. Я не осознавал, что значительная часть реализации Fable BCL была написана (предположительно) на Javascript. Я предполагал, что это уже будет больше F #. Не сомневаюсь, что для этого есть веская причина - почему так оказалось проще?

@jwosty (не такой короткий) ответ: по соображениям производительности и для преодоления разницы между типами F # и типами, изначально поддерживаемыми браузером, а также для лучшей интеграции сгенерированного кода в экосистему JavaScript.

Кроме того, .NET BCL имеет очень большую поверхность API поверх FSharp.Core , поэтому трудно поддерживать эту работу без более крупной команды (хотя @alfonsogarciacaro каким-то образом удается поддерживать и развивать его в основном самостоятельно, что удивительно и похвально).

Думаю, я пытаюсь сказать, что вклад (и идеи) очень приветствуются и ценятся.

Ответ (не такой краткий): по соображениям производительности и для преодоления разницы между типами F # и типами, изначально поддерживаемыми браузером, а также для лучшей интеграции сгенерированного кода в экосистему JavaScript.

Я могу видеть это.

Думаю, я пытаюсь сказать, что вклад (и идеи) очень приветствуются и ценятся.

Для уверенности! Я понимаю, сколько усилий уходит на поддержание таких больших и важных проектов, как этот, и я очень ценю кровавый пот и слезы, которые вы, ребята, вылили в это. Определенно я слежу за тем, где можно внести свой вклад, так как Fable в последнее время был для меня очень, очень полезен, и я хотел бы поделиться.

Мне нужна небольшая помощь. В настоящее время я использую проект QuickTest для разработки (т.е. dotnet fsi build.fsx quicktest ). Как распечатать Babel AST во время проявки? ( Обновление : я нашел ASTViewer ).

Моя текущая проблема заключается в том, что Python не поддерживает многострочные лямбда (функции стрелок), поэтому лямбда необходимо преобразовать и извлечь в именованную функцию и вместо этого вызвать эту функцию. Любые советы здесь будут оценены. Например:

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

Как я должен думать при замене одного выражения вызова несколькими операторами, поскольку при преобразовании вызова функции с помощью аргументов мне нужно заменить его на более высоком уровне или использовать какой-либо оператор блока, например if true then ... Есть идеи?

@dbrattli См. также этот принтер , который также печатает некоторые свойства.

Я не осознавал, что значительная часть реализации Fable BCL была написана (предположительно) на Javascript. Я предполагал, что это уже будет больше F #. Я не сомневаюсь, что для этого есть веская причина - почему так оказалось проще?

Да, как сказал @ncave , основная причина заключалась в создании более стандартного JS. Например, в Funscript все замены FSharp.Core были написаны на F #, но с Fable я хотел интегрироваться со стандартами JS, когда это возможно (например, используя итераторы JS вместо .NET IEnumerable), что доставляло много проблем с курицей и яйцом, поэтому он Поначалу было проще написать это на JS / Typescript. Вначале у меня также была идея опубликовать fable-library как отдельный пакет, который можно было бы использовать в JS-проектах, но я быстро отказался от нее.

В Fable 2 мы начали работу по переносу некоторых модулей на F #. Это было очень полезно, потому что это увеличивает догонку и позволяет нам легко повторно использовать улучшения в FSharp.Core (как в последнее время с картами и наборами). Тем не менее, кое-где еще есть кое-что, чтобы обойти проблемы с курицей и яйцом. И не будем говорить о большом монстре - модуле Replacements :)

Также, как говорит @ncave , поддержание этой библиотеки - огромная

@dbrattli Что @ncave или визуализатор Fantomas, который я использую в последнее время, когда мне нужно проверить F # AST (просто не забудьте выбрать Показать типизированный AST ). К сожалению, у нас пока нет специальных принтеров для Fable или Bable AST. В случае Fable AST, поскольку это просто объединения и записи, простой printfn "%A" работает, хотя информация о местоположении немного зашумлена.

О извлечении лямбд. Это возможно, но требует некоторой работы. Я думаю, мы можем сделать что-то похожее на импорт, то есть собирать их при прохождении AST и назначать им уникальный идентификатор. Позже мы можем просто объявить функции в верхней части файла . Основная проблема, вероятно, будет заключаться в захваченных значениях, я не помню, есть ли в F # AST информация об этом, но даже если она есть, к сожалению, мы не храним ее в AST, поэтому я думаю, что нам придется найти все иденты, используемые в теле лямбда, которые не соответствуют аргументам (или привязкам внутри лямбда), и преобразовать их в дополнительные аргументы лямбда (и я предполагаю, что они должны идти в начале, чтобы не мешать каррированию).

Спасибо за отличную информацию @alfonsogarciacaro , мой первый PoC просто изменял Babel.fs / Fable2Babel.fs , BabelPrinter.fs и т. Д. Теперь я начал с нуля с отдельным AST Python, т.е. Python.fs , PythonPrinter.fs . Затем я попытаюсь добавить Babel2Python.fs для преобразования из Babel AST в Python AST, поскольку это может быть проще, чем преобразование из Fable AST (но также возможно с Fable2Python.fs если преобразование из Babel заканчивается сложно). Таким образом, я не буду слишком сильно касаться существующей базы кода и сделаю собственное преобразование, в котором я попытаюсь выполнить лямбда-переписывание.

В этом есть смысл. Одна из трудностей заключается в том, что 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, в котором я храню упорядоченный словарь предыдущих операторов / объявлений (let, type, open) и создаю действительную программу 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 есть ThrowStatement. Есть ли веская причина, по которой он не используется, или я должен попытаться это исправить? @alfonsogarciacaro

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 в качестве thisKeyword. Я не могу быть уверен, что для Fable.IdentExpr установлено значение this если это ключевое слово this или нет, поскольку их нужно перевести на self в Python.
https://github.com/fable-compiler/Fable/blob/nagareyama/src/Fable.Transforms/Fable2Babel.fs#L792

Забавно, throw на самом деле был в Fable 2 AST, но я удалил его для Fable 3, чтобы упростить AST, поскольку он использовался только в одном месте, но я думаю, мы можем добавить его снова.

Насчет "этого" сложно. Мне нужно проверить это еще раз, потому что в F # AST это по-разному представлено в конструкторах или методах (есть некоторые комментарии по этому поводу, разбросанные по коду). В Fable AST есть свойство IsThisArgument для идентификаторов, но позвольте мне проверить, относится ли оно только к первому аргументу отсоединенных членов или оно также работает для «фактического этого».

@alfonsogarciacaro Я мог бы немного помочь с обработкой "внутреннего" и "пользовательского" кода, например, Exception, например, .message , но как мне это перевести? Могу ли я быть уверен, что код, скомпилированный пользователем, никогда не будет иметь .message а вместо этого всегда будет иметь вид, например, Test__Message_229D3F39(x, e) поэтому они никогда не будут мешать? Или мне нужно обернуть перехваченное исключение, например, в объект JsException и добавить свойство .message чтобы быть уверенным? Как Fable делает это с F # на Babel? Вы отслеживаете типы, или?

Например: как ex.Message.StartsWith в F # становится ex.message.indexOf в JS? Мне нужно будет перевести это в str(ex).index в Python (или обернуть объекты).

На самом деле у нас есть проблема с исключениями F # (например: exception Foo of int * int), которую я не исправлял до выпуска Fable 3 😅 System.Exception переводится как ошибка JS, поэтому у них есть поле .message. Но исключения F # не возникают из-за ошибки JS по причинам производительности (я думаю, что сейчас попытка доступа к .Message просто не выполняется). Где-то обсуждается это. Для вашего случая и для общего исключения, я думаю, вы можете предположить, что сейчас Fable имеет доступ к свойствам .message и .stack.

Как мы, кстати, хотим, чтобы в Fable был выбран язык? Сегодня мы можем выбрать, например, TypeScript, используя --typescript . Итак, мы могли выбрать Python, используя --python . Но должны ли мы тогда прекратить генерировать .js ? Вероятно, мы должны это сделать, поскольку сгенерированный JS может быть недействительным, если, например, используется Emit. Но иногда нам все же хочется увидеть JS. Должен ли у нас быть вариант для --javascript , или мы должны потребовать от пользователя работать без --python чтобы получить JavaScript? @alfonsogarciacaro

@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 улучшается. Многие языковые функции теперь доступны. Огромная оставшаяся задача - портировать fable-библиотеку. Чтобы упростить эту задачу, библиотека fable-library для Python была перемещена в репозиторий, поэтому мы можем преобразовать и повторно использовать многие файлы fable-library .fs в Python. Однако многие из них содержат JS-код, который нужно перехватывать и обрабатывать «вручную». В настоящее время существует тестовая установка для Python (на основе pytest) с пройденными 43 тестами, так что это значительно упрощает разработку и добавление новых функций (и показывает, что работает).

Сборка fable-библиотеки для Python: dotnet fsi build.fsx library-py
Запустить тесты Python: dotnet fsi build.fsx test-py

@dbrattli
В идеале мы должны попытаться преобразовать больше 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 (или любое другое имя). Возможно, это дает нам больше свободы в циклах выпуска, но я полностью согласен с тем, что у меня есть один инструмент dotnet, который может компилироваться для обоих языков. Это может быть проще, особенно вначале.

По поводу fable-library, да, если возможно, вместо того, чтобы переписывать текущие файлы .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 , это отличные идеи. Я попробую их посмотреть, что работает. Я вижу преимущества перевода fable-library на F #, поэтому я постараюсь помочь здесь, когда компилятор Python будет достаточно хорош, чтобы обрабатывать файлы там. Использование #if должно очень помочь, и мы, вероятно, могли бы избежать #if s, переместив излучение в файлы библиотеки для конкретного языка (поскольку у меня есть отдельный файл .fsproj для Python.

@alfonsogarciacaro, почему бы вместо этого не использовать два атрибута

За мои 2 цента мне нравится текущая низкая когнитивная нагрузка, связанная с наличием одного атрибута Emit, который просто излучает. Imo то, что делает @dbrattli прямо сейчас, имеет смысл (разные файлы проекта для каждой fable-library языковой версии). Мы можем разделить различные языковые сообщения в их собственных файлах, которые реализуют четко определенные интерфейсы (или модули), которые будут вызываться позже из общей реализации F #.

Хорошим примером может быть преобразование Array.Helpers в интерфейс (или просто оставить его как модуль), который можно реализовать для каждого целевого языка. Возможно, эта попытка переноса большего количества fable-library на F # (а также параметр компилятора --language ) может быть выделена в отдельный PR от этого, так что она может быть быстрее и легче внесена в .

Хорошая идея @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, я пытаюсь портировать 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 не имеет атрибута Delay.

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
...

Любые намеки на то, как я могу с этим справиться? @ncave @alfonsogarciacaro. Есть ли способ заставить классы F # генерировать классы с методами или заставить мои тесты генерировать код, который использует статический AsyncBuilder__Delay_458C1ECD вместо .Delay() ?

@dbrattli Обычно ответ на
В связи с этим, является ли поддержка async одной из тех функций, которые, возможно, лучше подходят для перевода на собственный Python asyncio ?

@ncave Хорошо, вот как это работает 😄 Спасибо за понимание. Я попытаюсь разместить построитель за интерфейсом и посмотреть, поможет ли он (даже если построители F # не являются интерфейсами, он, вероятно, должен работать нормально). Что касается async план состоял в том, чтобы использовать собственный asyncio Python ( см. Здесь ), но возникли проблемы с python cannot reuse already awaited coroutine , поэтому мне нужно ленить их за функцией. В любом случае, я понял, что с таким же успехом могу использовать конструктор task или asyncio для нативных ожидаемых файлов Python. Упрощение AsyncBuilder.ts привлекло меня, чтобы попытаться перенести его на F #.

Использование интерфейса, чтобы избежать искажений, должно работать, как говорит @ncave . Мы также попробовали несколько других подходов:

  • Атрибут 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 написал метод для построения искаженных имен более или менее, как ожидает Fable (я не помню, есть ли какие-то ограничения), чтобы вы могли писать код F # как обычно. См., Например, 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 . Может быть, мы воспользуемся этой возможностью, чтобы договориться об одном сейчас.

Спасибо за очень интересные идеи @ncave и @alfonsogarciacaro. С интерфейсами я наткнулся на другую проблему. Как бороться с unit в Python? Мой интерфейс выглядит так:

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, но в python вы должны указать аргумент, если функция принимает аргумент. Таким образом, вы получите сообщение об ошибке, если вызовете 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]

Но это требует от меня особой обработки для каждой универсальной функции с одним аргументом. Возможно, вместо этого мне следует просто заставить каждую функцию с одним аргументом принимать в качестве входных данных null (None в Python). Например:

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

Как правильно решить эту проблему?

IIRC, для методов с сигнатурой unit -> X у вызовов нет аргументов (как в F # AST), но для лямбда-выражений или в этом случае общие аргументы, заполненные unit вызовы / приложения имеют unit Аргумент transformCallArgs на шаге Fable2Babel. Возможно, мы могли бы добавить туда условие и оставить аргумент unit, если целевой язык - python:

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

Привет, @ncave , @alfonsogarciacaro , я мог бы использовать некоторые данные для обработки параметров [<Inject>] в Python. Как это делается в Php @thinkbeforecoding? Например, такие функции, как (в 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, которым нужна дополнительная информация, передаваемая аргументом, разрешенным во время компиляции:

  • Функции, которые создают массив, должны знать, является ли он динамическим или типизированным массивом JS.
  • Конструкторы наборов и сопоставлений и другие помощники, которым необходимо получить средство сравнения, чтобы знать, как нужно сравнивать тип (аналогично для функций, которые выполняют некоторые средние вычисления).

Однако эти функции не вызываются напрямую, поэтому 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 если она вам поможет.

Хорошо спасибо. Я попробую посмотреть, смогу ли я заставить это работать. Я не думаю, что нам нужно отключать его, нам просто нужно сгенерировать пустое (нулевое) значение для третьего аргумента, чтобы Python не подавился. то есть:

            | 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);

и для Python:

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

Кстати, извините, я не проверил этот PR подробно 😅 Но если он не нарушает тесты, мы могли бы вскоре объединить его в следующий POC, как мы это сделали с PHP. Это потому, что я планирую (позволяет время) провести некоторый рефакторинг, чтобы упростить таргетинг на несколько языков с помощью Fable, и, вероятно, это будет проще, если Python уже находится в ветке next для этого. Мы также избегаем синхронизации слишком большого количества расходящихся ветвей (main, next, python).

Хорошо, @alfonsogarciacaro , я посмотрю, смогу ли я сгенерировать None вместо null. Скоро мы сможем объединить это. Он все еще нарушает некоторые из существующих тестов Python, но сейчас осталось всего несколько, и я должен исправить это в течение недели или около того. Также необходимо создать Fable.Core.PY.fs с необходимыми излучениями. Переписывание из Babel2Python -> Fable2Python оказалось сложнее, чем я ожидал, но я, наконец, приближаюсь к тому, чтобы снова вернуться в нужное русло. Я немного поправлю PR и подготовлю его к слиянию.

@alfonsogarciacaro Я исправил все тесты, кроме одного. Однако это связано с той же старой проблемой, что и у меня с Babel 😱 Мне нужен способ узнать, используется ли Fable.Get для типа AnonymousRecord поскольку они преобразуются в Python dict, и мне нужно использовать подстрочный индекс т.е. доступ к [] а не к .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)

Как мы говорили в Discord @dbrattli , я думаю, вы можете проверить тип вызываемого, чтобы узнать, анонимная ли это запись. Но если вам нужен более систематический подход, можно добавить дополнительную информацию в 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);
}

Почему при вызове ofSeq добавляется это объектное выражение с Compare ?

Хм, мне нужно разобраться в этом. Похоже на ошибку, ofSeq должен принимать компаратор (аналогичные функции, такие как MapTree.ofSeq и Set.ofSeq do). Настройка немного сложна, поэтому, возможно, в какой-то момент что-то пошло не так: у нас есть файл с именем ReplacementsInject.fs, который используется Replacements, чтобы указать, какие методы нуждаются в инъекции аргументов. Где-то есть сценарий для автоматического создания этого файла, но мы давно его не использовали, и я не уверен, работает ли он с последней версией FCS. Я проверю, спасибо, что указали на это!

Я добавил временное исправление. Готов к просмотру 🎉 https://github.com/fable-compiler/Fable/pull/2345

Была ли эта страница полезной?
0 / 5 - 0 рейтинги