How to work raw JavaScript recursive Array?

Kind of new to PureScript… read a lot but first time actually using it.

I have a library using JavaScript arrays as argument, a recursive array, like ["a", ["d", ["c"], "d"]], or in TypeScript type A = string | A[]. First idea I thought was using data A = String | Array A, turned out PureScript is not using JavaScript Arrays directly underneath.

Is there a solution to construct such a recursive JavaScript thing within PureScript.

currently in my code I have to use really dirty code to make it work…

let transformNodes = (xs, leafCtor, listCtor) => {
  if (xs instanceof leafCtor) {
    // dirty solution to use `.value0`
    return xs.value0;
  }
  if (xs instanceof listCtor) {
    return xs.value0.map((x) => transformNodes(x, leafCtor, listCtor));
  }

  console.log("Unexpected node:", xs);
  throw new Error("Unexpected node");
};

exports.writeCirruImpl = (xs) => (options) => (leafCreate) => (listCreate) => {
  // dirty way of detecting type
  let leafCtor = leafCreate(null).constructor;
  let listCtor = listCreate(null).constructor;

  let ys = transformNodes(xs, leafCtor, listCtor);
  return writer.writeCirruCode(ys, options);
};
foreign import writeCirruImpl ::
  CirruNode ->
  { useInline :: Boolean} ->
  (String -> CirruNode) ->
  (Array CirruNode -> CirruNode) ->
  String

writeCirru :: CirruNode -> { useInline :: Boolean } -> String
writeCirru nodes options = writeCirruImpl nodes options CirruLeaf CirruList

Welcome to PureScript!

You seem to have the wrong idea about PureScript data types. PureScript does not support type unions like TypeScript does. Instead, data A = String | Array A creates a type which has two constructors: String and Array A. The equivalent of this in TS would be the following:

type A = AString | AArray A

interface AString {
  kind: "string";
}

interface AArray {
  kind: "array";
  value: A;
}

These are called tagged unions, while something like number | string in TypeScript is called an untagged union.

Since you want to wrap foreign data types in PureScript, you may want to do something like this:

foreign import data A :: Type

fromString :: String -> A
fromString = unsafeCoerce

fromArray :: Array A -> A
fromArray = unsafeCoerce

foo = fromArray [ fromString "a", fromArray [ fromString "b" ] ]

That doesn’t look too ergonomic though. There’s probably another solution that looks better than this one, but I am not too familiar with advanced PureScript FFI, so take my suggestion with a grain of salt.

That’s incorrect. x = [1, 2, 3] will compile down to something like var x = [1, 2, 3] in JS. You just weren’t actually using the Array type in your code.

Since you’re new to PureScript, I would recommend you try out at least some of the resources in https://github.com/purescript/documentation#learning. I especially recommend the PureScript book and Jordan’s reference.

1 Like

I also forgot to say something important: avoid depending on the implementation of PureScript ADTs (i.e. value0, xs instanceof listCtor, etc.). These are internal implementation details and are not intended to be used by FFI functions, so they might be changed between PureScript compiler versions without warning. I realize that you already know that this is “really dirty” code, but I want to emphasize this.

1 Like

exactly, I will replace the dirty code as soon as I find a solution.

I have rough concepts about tagged unions, thanks for examples. I haven’t using fromArray yet, it seems not dynamic enough… trying if there’s anything I can do here.

1 Like

Figured out a less dirty solution, that I transformed my data to nested data with Record type first, since Records are using plain JavaScript fields, now the glue code does not need to know properties like .value0:

newtype FatNode = FatNode { isList :: Boolean, leaf :: String , list :: (Array FatNode) }