(WIP) Kotlin Backend for Purescript

backend
#1

Hi there,
I’ve been working on a new backend for PS to enable writing PureScript programs for native android using kotlin: https://github.com/csicar/pskt
The backend is forked from purescript-native and uses the CoreFn interface.

At this point, the transpiler should be functionally complete, but still lacks TCO and ~MagicDo~. Update: magicdo ist now implemented.
FFI-files for standard PureScript libraries are available here: https://github.com/csicar/pskt-foreigns
A basic example of an android app can be found here: https://github.com/csicar/pskt-android-example

10 Likes
#2

Hello hello! This is awesome news, it’ll be exciting to use for Android apps :smiley:

One suggestion I’d make is to rewrite some of the FFI as functions. In Kotlin val toString = { a: Number -> "$a" } is one allocation at runtime, whereas fun toString(a: Number) = "$a" has no allocations and represents a location in bytecode.

Allocating all the FFI functions at runtime incurs on a non-trivial app startup and memory penalty that’s easily avoidable! Curried functions, explicit casts…all have runtime costs.

#3

Thank’s for giving me insight info the performance penalties of the design decisions.

I would like to use functions instead of lambdas everywhere, as that would also open up the possibillity of using kotlins inline and tailrec modifier.

Unfortunately, using functions is not as easy as I thought: If f is a function, it cannot only be passed as a value if the reference operator is used ::f. But non-function values need to be passed as just the variable name. (It seems to be possible to pass a normal variable as ::n, which creates a Kproperty0, but i guess, that that also reduces the performance)

That means, that I would need to know the type of a value, when I encounter it in the CoreFn AST (which is untyped).
Also a lot of casts could be avoildable if the type of values was known.
The only solutions I see is doing type inference on the CoreFn ast, which seems quite involved. Or somehow tapping into the purescript Compiler for extracting the actual types.

If you have another idea for how to reduce the memory penalty, ideas would be appreciated.

#4

I did not understand this, that’s supposed to be why the operator exists, and the types for parameter and returns are the same. The parser is very simple, so sometimes it’s a case of parenthesizing (::f).

Do you have an example where you tried to use it and it didn’t work?

#5

The problem is, that knowing whether f is a function or a normal value is hard.

Let’s take the following example:

val a = PS.SomeOtherModule.doStuff(PS.SomeModule.f)

if f is a function, it must be passed as ::f, but if f is a normal value (like listOf(1)), it should be passed as f. Because no type information is available for PS.SomeModule.f, it is hard to know which of those options should be taken.

#6

Gotcha! So now if you tried:

object Module {
  val f: (Number) -> String
    get() = { a: Number -> "$a" }
}

it creates a function that can be accessed as a property and doesn’t have that ambiguity. Except! it now allocates a function object every time, which is overall worse.

It’s frustrating that this cannot be done efficiently.

#7

I think you’re onto something. Is it possible to set getters on methods?

If not, I also got a different idea. Create a field for referencing and calling a value:

object Module {
  fun f_call(a: String) = "$a"
  val f_ref = ::f_call
 
  val lambda_ref = {x : Any -> ...}
  fun lambda_call(x: Any) = (lambda_ref as (Any) -> Any)(x)

}

and then replace the pattern (::f_ref)(a) with f_call(a)

Not really beautiful, but maybe worth it

#8

Is it possible to set getters on methods?

On properties, yes, with the syntax I showed you above. val a get() <fun_body>.

Your idea still incurrs in the additional allocation, although it makes dispatching faster because it’ll go though a different path in bytecode.

Also, I wouldn’t be surprised if the Kotlin compiler pools all uses of a function reference as long as it’s monomorphic. So, same as you did manually. Maybe worth checking on the IDE decompilation.

#9

Yeah, probably the right idea.
While I’m at it, I will also check the byte-code for the impact of casting

1 Like