Hoot FFI: Map JS arrays to Scheme lists

I am making a breath timer with Guile Hoot:

In this app, I’d like to manipulate some CSS animations from Guile.

AFAICT, the correct way to do this is with the getAnimations() method on the Element API. The thing is that getAnimations() returns an array of Animation objects, and I’d like to manipulate each one. Ideally, I’d be able to map over that array of objects in Guile Scheme by using the Hoot FFI to first converting the array to a Scheme list. For each (ref extern) Animation object in the list, I would then call the foreign function I have defined here:

However, since I do not know how to map over an array of objects, I simply do it all in one pass:

My current approach feels like a hack, and I would appreciate advice.

Thank you!

Joseph

1 Like

You can convert a JS array to a Scheme list by writing bindings for array access. I haven’t tested the code below but it should be close to correct:

(define-foreign array-length "array" "length" (ref extern) -> i32)
(define-foreign array-ref-extern "array" "refExtern"
  (ref extern) i32 -> (ref extern))
(define (array->list array)
  (let ((n (array-length array)))
    (let lp ((i 0))
      (if (< i n)
          (cons (array-ref-extern array i) (lp (1+ i)))
          '()))))

Thank you, Dave! The code works.

I have another FFI question: I would like to pass a boolean to the JS element.getAnimations({ subtree }) method. What is the correct way to specify the subtree argument from Scheme?

Currently I am passing a string to the Scheme function and then checking its value on the JS side:

;; Scheme side

(define-foreign element-animations
  "element" "getAnimations"
  (ref extern) (ref string) -> (ref extern))

;; Javascript side

getAnimations: (elem, subtree) => elem.getAnimations({ subtree: subtree === "subtree" })

Is there a more correct way?

Is it possible to make the second argument to element-animations optional?

Thank you!!

For boolean arguments I recommend using the i32 type as it will avoid allocation. On the Scheme side, convert with (if foo 1 0) and on the JS side do foo === 1. Browser engines also convert booleans to i32s when crossing over to Wasm so you should also use an i32 type if you are wrapping any JS functions that return booleans.

Regarding optional arguments, Wasm doesn’t support them. You can make an argument optional in your Scheme interface by writing a wrapper around the low-level binding.

Understood. Thank you for the information about i32 type!