`%identity` optimizations?

not sure about how feasible this will be - but it’s be nice for functions like Array.map and/or Js.Array.map to just… disappear when passed %identity?

Do you mean like this?

type t
external unsafeCast: int => t = "%identity"
let a1 = [1, 2, 3]
let a2 = Js.Array2.map(a1, unsafeCast)

If you’re asking if line 4 can be optimized into a2 = a1, then that would actually break things. Since arrays are a mutable structure, then creating one via map is different than just referencing an existing array.

But if you’re using %identity to unsafely type cast already, then you can just write an %identity function that casts arrays of the type you want.

type t
external unsafeCastArray: array<int> => array<t> = "%identity"
let a1 = [1, 2, 3]
let a2 = unsafeCastArray(a1)
1 Like

that… works, however it’s a bit of a pain to have to do that for potentially every unsafe cast i have (and every generic type…)

re: arrays being mutable, that’s a valid point, however surely something could be done for at least array literals (well… just literals and constructors in general?)

That’s where ReScript’s powerful module system helps, e.g. Playground.

Above example shows casting an array<int> to array<userId> without any map function calls. You can define modules for any ‘container’ type, e.g. options, promises, sets, etc.

1 Like

i guess something like this might work? Playground
but my point is it’s a bit painful having to do this boilerplate, plus you potentially have to keep going back to add more generics as needed

what practical problem are you trying to solve?

1 Like

not really a problem tbh… just that it’s… less than ideal having an unnecessary map - i’d even be fine if we could have like, @<something> to let the compiler know we’re sure that this won’t cause any issues (like mentioned above because map normally makes a new array)

it doesn’t matter too much in the end… it’d just be a nice optimization to have available

In practice, unless the code is all compute and no I/O, the I/O will make the time spent in a map function irrelevant. You should double-check by profiling your app, though. Premature optimizations, etc.

sure… but i think this is (potentially) a low hanging fruit - premature optimizations is one thing, optimizations being completely impossible without some gymnastics is another

I actually don’t think it’s a low-hanging fruit, for the reason that John pointed out above. It is potentially unsafe. The compiler shouldn’t automatically be doing it on your behalf. When you do need it, it’s there (%identity), but this escape hatch should be used after careful consideration, not automatically as the first option.

1 Like

i am aware; that’s why i said this could potentially be @<something> (similar to, say, @unwrap)

Well, there is Obj.magic, which is %identity provided as a function. So instead of @<something> foo, you can do Obj.magic(foo).

but Obj.magic isn’t restricted to just coercions the library author says is safe, right?

Right. Honestly, compared to what you’d have to do to get safety from other techniques, it’s probably better to define a functor once and be able to use that to cast whatever types you have safely, e.g.

The challenge is that from the compiler’s point of view, Array.map is just a normal function, it does not have built-in knowledge of what it does.
We could certainly encode some built-in knowledge into the compiler, but that could complicate the compiler and the benefit is only a niche use case, so in general, the compiler does not do such optimizations

2 Likes

Note if your time is able to coerce to int, you have to put it in an immutable collection. For immutable collections, such typing rule works in ReScript:

a :> int
collection<a>  :> collection<int>

The collection could be list, for example. ReScript does not have built-in immutable array support, you can build it from userland.

For example, you can create an immutable array module starts with:

type t <+'a> // co-vaiant
// your unsafe implementations below
2 Likes

hmm… i guess what would be ideal would be some kind of HKT support:

let asTargetHkt: 't<Source> -> 't<Target>

but i don’t think that exists so… oh well