Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python DU refinements: provide access to DU case constructors to python code #3558

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

smoothdeveloper
Copy link
Contributor

I'd like to provide DU case constructors to the python code that interops with code generated by Fable.

This suggested PR seems to do what I want, albeit there is edge case of generic type annotation not being carried in the case constructor of union with generic type argument, it doesn't seem to cause an issue to the python interpreter (and I'm not clear those would be required either).

Short quick test:

type U = Z of value: int | Y of name: string

let u1 = Z 1
let u2 = Y "abc"

let a : U = Fable.Core.PyInterop.emitPyExpr 1 "U.Z($0)"
let b : U = Fable.Core.PyInterop.emitPyExpr "abc" "U.Y($0)"
Fable.Core.Testing.Assert.AreEqual(a, u1)
Fable.Core.Testing.Assert.AreEqual(b, u2)

Turned by Fable into:

from __future__ import annotations
from typing import (Any, List)
from fable_modules.fable_library.reflection import (TypeInfo, int32_type, string_type, union_type)
from fable_modules.fable_library.types import (Array, Union)
from fable_modules.fable_library.util import assert_equal

def _expr0() -> TypeInfo:
    return union_type("QuickTest.U", [], U, lambda: [[("value", int32_type)], [("name", string_type)]])


class U(Union):
    def __init__(self, tag: int, *fields: Any) -> None:
        super().__init__()
        self.tag: int = tag or 0
        self.fields: Array[Any] = list(fields)

    @staticmethod
    def cases() -> List[str]:
        return ["Z", "Y"]

    @staticmethod
    def Z(value: int) -> U:
        return U(0, value)

    @staticmethod
    def Y(name: str) -> U:
        return U(1, name)


U_reflection = _expr0

u1: U = U(0, 1)

u2: U = U(1, "abc")

a: U = U.Z(1)

b: U = U.Y("abc")

assert_equal(a, u1)

assert_equal(b, u2)

cc: @dbrattli

@smoothdeveloper
Copy link
Contributor Author

Note that I also intended to work on exposing Is* members, but dotnet/fsharp#11394 has not been merged.

I'm wondering if I should still go forward, because there is still no easy way to "pattern match" / branch on the DU values from consuming python code (AFAIK).

@smoothdeveloper
Copy link
Contributor Author

Any opinion how those Is* members should look like in Python?

There is a bit of a conflict with the DU cases having the PascalCase "type" casing and this being an instance get property which would mandate snake_case.

@dbrattli
Copy link
Collaborator

dbrattli commented Oct 23, 2023

Experimenting with how we could support pattern matching of DUs from Python:

class _U(Union):
    def __init__(self, tag: int, *fields: Any) -> None:
        super().__init__()
        self.tag: int = tag or 0
        self.fields: Array[Any] = list(fields)

    @staticmethod
    def cases() -> list[str]:
        return ["Z", "Y"]


class Z(_U):
    __match_args__ = ("value",)

    def __init__(self, value: int) -> None:
        self.value = value
        super().__init__(0, value)


class Y(_U):
    __match_args__ = ("name",)

    def __init__(self, name: str) -> None:
        self.name = name
        super().__init__(1, name)


U = Z | Y


def create() -> U:
    return Z(1)


u: U = create()


match u:
    case Z(value=10):
        print("Z")
    case Y(name=name):
        print("Y")
    case _:
        print("Not Z or Y")

@smoothdeveloper
Copy link
Contributor Author

@dbrattli this looks interesting!

Would it make more sense to put the cases as inner classes? I'm wondering because there is the concern of having DU cases that are named the same as outer type, which is possible in F#, but would likely cause some headaches on the python side.

type Case1 = { a:int }
type Case2 = { b:string }
type DU =
  | Case1 of Case1
  | Case2 of Case2

@dbrattli
Copy link
Collaborator

dbrattli commented Oct 28, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants