[SOLVED] In JS, how to differentiate PureScript type from an object?

Regarding my screenshot, I figured the name is accessible with temp1.size.constructor.name. Now gotta see if I can get anything useful of it. I’m a bit uneasy seeing the name is mangled…

So, I figured out the algorithm, but I stumbled upon what seems like a compiler bug.

The solution is basically this:

export const canonicalizeTypedProps = function(props) {
    for (var k in props) {
        if (!props.hasOwnProperty(k)) // skip <prototype> properties
            continue;
        if (props[k] instanceof Array)
            continue; // TODO: recurse into
        if (props[k].constructor.name !== 'Object')
            props[k] = show_(props[k]);
    }
    return props;
}

Here the show_ is a re-export of show, like this (I haven’t found a way to make it visible to the JS without re-declaring, suggestions are welcome):

show_ :: ∀ a. Show a => a -> String
show_ = show

Now, the problem is, show_ doesn’t work. Calling it upon “showable” property just gives undefined. More over, if I spago bundle-app and check resulting index.js for the string to produce, it turns out there’s none, so it doesn’t seem like it even produces a working code for show_.

Reported… But as far as the question concerned, marking as SOLVED.

You’re trying to do something really strange, it seems you want to use purescript code in JS foreign code without understanding how it (“show” function and ad-hoc polimorphism) works, though it was explained in the github issue you recently created.

1 Like

You’re trying to do something really strange, it seems you want to use purescript code in JS foreign code without understanding how it (“show” function and ad-hoc polimorphism) works

I don’t see anything strange. The “understanding” you mention is a very special knowledge that few people have, which is the main point of the issue (to have it documented).

though it was explained in the github issue you recently created.

As of writing the words, the explanation is too shallow. It’s about the missing dictionary, which as I gather from the sources inspection is about fooShow. But calling fooShow.show() is something I found myself before and it doesn’t answer the question about calling show for some general Show-implementing type.

You’re trying to use show function in JS code as if it is a magical function that knows how to convert to a string anything you feed to it, this aint gonna work. You can call show in JS only if you pass to it a function (in a form of typeclass dictionary) with a concrete implementation of how it should convert input values to a string. It should be intuitive that you cannot just take PS objects/functions and use and manipulate them without care in a foreign land where they do not belong.

This is not so. If you want to use the language well, you should understand not only shallow basics. Also, what you are doing in your JS code is a potential disaster, as you mutate the input record object. Immutability principles are definitely not a secret knowledge.

You’re trying to use show function in JS code as if it is a magical function that knows how to convert to a string anything you feed to it, this aint gonna work. You can call show in JS only if you pass to it a function (in a form of typeclass dictionary) with a concrete implementation of how it should convert input values to a string. It should be intuitive that you cannot just take PS objects/functions and use and manipulate them without care in a foreign land where they do not belong.

It’s all irrelevant to what we’re discussing. Just a few days ago you were trying to explain to me the reason for confusing FFI interface that made purescript devs forbid using type-constraints on foreign functions. You didn’t succeed back then, because you provided no examples, it was mostly handwaving, and then I just got tired of discussion. But I realized it as part this issue: the parameter to show would be a dictionary that would dispatch to the fooShow.show().

I see this, but how is that relevant, given I’m passing what should to be exactly this dictionary, and then purescript compiler makes it not be such object? You think this is intuitive? I would imagine we wouldn’t have been discussing this here have this been intuitive, do you think? And if it’s so “intuitive”, why didn’t you still write an answer in the Github thread regarding how to implement a -> String: if you’re making this that easy, then prove it, code better than words, aye? :man_shrugging:

This is not so. If you want to use the language well, you should understand not only shallow basics.

It’s interesting how you made intrinsic purescript compiler knowledge a “non shallow basics”.

Also, what you are doing in your JS code is a potential disaster, as you mutate the input record object. Immutability principles are definitely not a secret knowledge.

That’s a good point, I’ll add copy creation once things are sorted out.

In general, when dealing with FFI you should avoid passing PS native values (data types) into FFI, first convert them in purescript code to simple FFI compatible types (String, Int, Number, Boolean). You don’t need typeclass related stuff inside FFI.

Do not try to do complicated conversions inside FFI code, FFI should be as simple as possible. It should accept simple values and return simple native PS values. Sometimes you may need to return more complex PS types, for example Either or Maybe from FFI , for this you pass type constructors (which are simple functions) as a paramters to FFI function. For Maybe values though you can use Nullable.

Actually it is not so intrinsic, because you may just open generated JS code and see what is going on, debug what is happening with methods outside of compiler’s scope. You should definitely understand what the compiler outputs (it is pretty readable JS code) to work with it correctly. And of course you should understand how FFI works and best practices, because when you are steping into FFI realm you are not safe anymore.

Just to conclude for future readers: while the topic title is solvable/solved, using show from FFI just isn’t possible. More details in this Github comment, but in short, PureScript compiler never generates dynamic dispatch from show to show of the underlying type, instead it analyzes callchains, duplicates them for known types, and in the end just hardcodes the show for the underlying type. The underlying type showFoo is callable of course, but the generic show isn’t, it just doesn’t exist.

UPD: I’d add that per my understanding, this applies to all functions that have some generic lower-case type, like a. Such generic functions simply won’t exist in the JS output. It’s akin to C++ templates, where the template functions only exist in compile time, and then in machine code they get duplicated (barring optimizations of course) for types being passed.

I haven’t tried to follow the entire discussion around this, but JS functions that rely on intrinsic representation of PureScript data types are an anti-pattern. You should perform the conversion inside PureScript if you want to deal with arbitrary PureScript data types in a sensible way.

data ButtonSize = Sm | Md | Lg

buttonSize :: ButtonSize -> String
buttonSize Sm = "sm"
buttonSize Md = "md"
buttonSize Lg = "lg"

And then explicitly call the conversion function when you pass it to a JS function -

someReactComponent {size: buttonSize Sm, ...}

This is best practice as it keeps a separation between JS values and PureScript values.


If for some reason, you really do want to pass typed PS values directly to a JS API, you can create an opaque foreign datatype and change its runtime representation to match the API. You can’t use ADTs -

data ButtonSize

sm :: ButtonSize
sm = unsafeCoerce "sm"

md :: ButtonSize
md = unsafeCoerce "md"

lg :: ButtonSize
lg = unsafeCoerce "lg"

And now you can do -

someReactComponent {size: sm, ...}
1 Like

JS functions that rely on intrinsic representation of PureScript data types are an anti-pattern. You should perform the conversion inside PureScript

[…]

someReactComponent {size: buttonSize Sm, ...}

This is best practice as it keeps a separation between JS values and PureScript values.

This beats the purpose, because you get no type-safety. Why even bother creating/using buttonSize function if everyone can pass a string directly, and it will α) work, β) be shorter to type and read :man_shrugging:

If for some reason, you really do want to pass typed PS values directly to a JS API, you can create an opaque foreign datatype and change its runtime representation to match the API. You can’t use ADTs -
[…]

Wow! This is quite an interesting idea I didn’t even think is possible! I’ll definitely try it out!

Your public interface should be a wrapper function that accepts typed PS values and passes them to the JS FFI function after conversion.

reactComponent :: { size :: ButtonSize, ... } -> ReactComponent
reactComponent props = reactComponentFFI {size: buttonSize props.size, ...}

This falls apart when you consider that size may be a ButtonSize, but may also be some CheckboxSize, because size is a pretty generic name that will be present in almost every component, all with their own values/types.

One could say, no one size fits them all :smile:

This problem gets you in position, where having such generic function requires it to be implemented in FFI. And then you end up in the situation the discussion was about :blush:


So far, your suggestion with unsafeCoerce seems the best solution, and requires the least amount of code. Weren’t for this, I was going to implement a O(n) terrible algo that just uses instanceof with hardcoded show functions in FFI, because I had dug far and wide, and there was no other solution whatsoever. So I am very thankful to you for this idea, I just didn’t know it was possible :blush:

Sure, there are always pros and cons with any approach.

Have you seen GitHub - doolse/purescript-tscompat: Purescript types and classes for interop with Typescript? It takes the match-representation-to-JS approach quite far.

1 Like

Oh, this is interesting… I did actually see this lib, but discarded it because I thought it only creates the visual representation that is similar to TypeScript’s. But just as I was writing a comment about this, I realized the library uses unsafeCoerce, and so given type Buttonsize = OneOf ( … ), the ButtonSize parameter will be a string and not some PureScript-specific dictionary. Under the hoods it’s basically like your suggestion about unsafeCoerce trick, except implemented as a 3rd-party library.

Thank you! Although, in my particular case it seems it would require more code compared to using unsafeCoerce manually, so I see no benefits — but might come in handy for some similar case in the future, thanks!

(Amost) anything is possible with unsafeCoerce, but it is a poor programming style to use it when there is a way to handle the case without it. Also, the desire to have “the least amount of code” is too often flawed.

That’s true for any “unsafe” code :wink: Given there doesn’t exist a “safe” solution, among “unsafe” ones it makes sense to chose one that is easier to maintain.

It’s just not clear what you are really trying to avoid not using simple FFI integration methods.

I usually have wrappers for such cases, and use safe data types and appropriate structure for component’s API:

type Props = {size :: ButtonSize }

type RawProps = {size :: String}

foreign import _someReactComponent :: RawProps -> ReactComponent

convertSize :: ButtonSize -> String
convertSize Sm = "sm"
convertSize Md = "md"
convertSize Lg = "lg"

convertProps = Props -> RawProps
convertProps {size} = {size : convertSize size }

someReactComponent :: Props -> ReactComponent
someReactComponent props =
  _someReactComponent (convertProps props)

Is there too much conversion/wrapper code for you? It may seem to be boilerplaty sometimes, but with this approach you get correctness (with transparency and explicitness) and flexibility (in terms that you may adjust API as needed).

Your idea already was suggested/discussed since this message and in short it gives no type-safety, because you end up with size :: String which people will use and not size :: ButtonSize. Trying to hide RawProps (and hence size :: String) was also discussed further below and would, in short, result in you having to go through FFI to create a function-converter, at which point we turn full circle back to my idea that started this whole topic.

Actually, maybe you’re right. I just realized I was missing the nuance that I could create unique converter function for each component, which wouldn’t require FFI. I mean, I could implement ButtonProps -> RawProps, then CheckBoxProps -> RawProps, etc. All of them to be hidden, of course.

I am not quite sure why didn’t I think that way… I will look at it.

@wclr so, I tried implementing it the native way, but couldn’t get past compilation error, which I decided to post separately to not clutter this discussion. Please see if you understand what’s that about, because I’ve spent some time on this and have no slightest idea what the compiler wants from me…