Fable icon indicating copy to clipboard operation
Fable copied to clipboard

Generate JS tuple from F# tuple

Open sydsutton opened this issue 2 years ago • 10 comments

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

  • Fable version: 4.1.4
  • Operating system: Windows 11

sydsutton avatar Oct 16 '23 17:10 sydsutton

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

ncave avatar Oct 16 '23 17:10 ncave

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

sydsutton avatar Oct 16 '23 17:10 sydsutton

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

ncave avatar Oct 16 '23 20:10 ncave

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.

sydsutton avatar Oct 16 '23 20:10 sydsutton

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

ncave avatar Oct 16 '23 20:10 ncave

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.

MangelMaxime avatar Oct 17 '23 13:10 MangelMaxime

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

sydsutton avatar Oct 18 '23 02:10 sydsutton

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"

ken-okabe avatar Mar 27 '24 02:03 ken-okabe

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

sydsutton avatar Mar 28 '24 14:03 sydsutton

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

ken-okabe avatar Mar 28 '24 14:03 ken-okabe