fable-compiler / Fable

F# to JavaScript, TypeScript, Python, Rust and Dart Compiler
http://fable.io/
MIT License
2.89k stars 296 forks source link

Generate JS tuple from F# tuple #3546

Open sydsutton opened 11 months ago

sydsutton commented 11 months ago

Description

I was wondering if there are any configuration options for me to purposefully produce a JS (or TS) tuple from an F# tuple instead of a JS array from F# tuple? I'm working on an F# front-end library, and need a tuple for this particular implementation. Thanks!

Function: static member inline mergeClasses (classNames: 'T): string = import "mergeClasses" FluentUIv9

Usage: prop.className (Fui.mergeClasses (styles.backgroundColor, styles.textColor, styles.borderRadius))

Fable compilation: createObj(ofArray([["className", mergeClasses([styles.backgroundColor, styles.textColor, styles.borderRadius])]

Expected and actual results

The code works as it should if Fable were to compile it into:

createObj(ofArray([["className", mergeClasses(styles.backgroundColor, styles.textColor, styles.borderRadius)] (no array)

Related information

ncave commented 11 months ago

@sydsutton Not supported yet at the moment, no. It does makes sense to add optional support for JS tuples and records, once they are more widely supported. I'll tag this as a feature request.

In the mean time, is it possible to wrap the tuple arrays in Tuple.from ? I know it's probably a bad work-around, so only as a temporary solution.

sydsutton commented 11 months ago

@ncave Thanks for the quick response! I'll give it a shot. What namespace would Tuple.from be under?

ncave commented 11 months ago

@sydsutton Technically it should be in Fable.Core.JS, but those new primitives are not there yet.

You can perhaps directly emit the new tuples where you need them:

    let makeTupleInit x = Fable.Core.JsInterop.emitJsExpr (x) "#$0"
    let makeTupleFrom x = Fable.Core.JsInterop.emitJsExpr (x) "Tuple.from($0)"
    let t1 = (2, "three", 4)
    let t2 = makeTupleInit (2, "three", 4)
    let t3 = makeTupleFrom t1

will compile to:

    const t1 = [2, "three", 4];
    const t2 = #[2, "three", 4];
    const t3 = Tuple.from(t1);

BTW, do you have a link to which browser support is already there for Tuples and Records, or is it still in preview? Somehow I can't find it, at least there is none in Node.js yet.

sydsutton commented 11 months ago

No, I am not sure about browser support, but I have been successfully using tuples for functions in my project for a while now just by using partial application. Thanks for the response, btw.

ncave commented 11 months ago

@sydsutton I guess my question was, what JavaScript engine have you used those new #Tuples with? Somehow I can't find any references to actual support, besides a few articles about a preview spec.

MangelMaxime commented 11 months ago

It seems like Tuple in JavaScript are still in Stage 2: Draft and Can I use doesn't report any results.

So I don't think tuple are yet widely spread meaning that their implementation is subject to change. We can't add support for it in Fable directly, because we don't know if this going to be implemented or not in the future. And also, if the specification change then we would need to introduce breaking changes.

I summarised Ncave utilities + another example in this REPL link.

open System
open Fable.Core
open Fable.Core.JsInterop

// You could place this module under your own Fable.Core.JS modules
// for consistance
module JS =

    let inline makeTupleInit x = Fable.Core.JsInterop.emitJsExpr (x) "#$0"
    let makeTupleFrom x = Fable.Core.JsInterop.emitJsExpr (x) "Tuple.from($0)"

[<Erase>]
type Test =

    static member inline mergeClasses (classNames : 'T) = import "mergeClasses" "FluentUIv9"

    static member inline mergeClasses (v1 : 'T, v2 : 'T) = 
        Test.mergeClasses (JS.makeTupleInit (v1, v2))

    static member inline mergeClasses (v1 : 'T, v2 : 'T, v3 : 'T) = 
        Test.mergeClasses (JS.makeTupleInit (v1, v2, v3))

let style = 
    ("red", "bold")

Test.mergeClasses style
Test.mergeClasses (JS.makeTupleFrom style)
Test.mergeClasses (JS.makeTupleInit style) // Not sure if this works
Test.mergeClasses (JS.makeTupleInit ("red", "bold")) // Seems to generate the correct output

Test.mergeClasses ("red", "bold")
Test.mergeClasses ("red", "bold", "underline")

Another solution to add support for Tuple in your project, could be by using a Fable compiler plugin. Like Feliz does for React, but it needs more works and it is not well documented yet.

sydsutton commented 11 months ago

@MangelMaxime @ncave Great, thank you guys for the responses. I really appreciate the help.

ken-okabe commented 5 months ago

I have encountered the same issue because VanJS api heavily depends on n-ary functions that I need to use.

My workaround is to create a bridge.js and export the hooked functions like this

import van from 'vanjs-core'

// unary function ([a,b,c,...]) in F#
// -> n-ary function (a,b,c,...) in VanJS
let n =
    f => array =>
        f(...array);

export let tags =
    new Proxy(van.tags, {
        get: (target, property) => {
            return n(target[String(property)]);
        }
    });

export let add = n(van.add);

then imports from *.fs

open Fable.Core.JsInterop

let tags: obj
       = importMember "../ts/tags_add"

let add: List<obj> -> Element
       = importMember "../ts/tags_add"
sydsutton commented 5 months ago

Thanks, @ken-okabe ! I'll see if I can figure out something similar for my project.

ken-okabe commented 5 months ago

I'm sorry, but probably, I misunderstood your question. As already mentioned here, JS/TS currently does not have tuple data structure, so in theory, "Generate JS tuple" is impossible.

In JS, (a, b, c) is not a value, but f(a, b, c) means n-ary function that takes multiple arguments.

My workaround is just convert

f [a, b, c] in F# to f (a, b, c) in JS