Awesome
PsKt
Kotlin-Backend for PureScript. PsKt is forked from purescript-native
Why
The goal of this project is to support native android development in PureScript using Kotlin. Kotlin was chosen, because it has first-class support from Android. This means, that the complete API is available through the FFI.
Installation
There are some pre-build executables available:
To build the executable:
$ git clone https://github.com/csicar/pskt
$ make
$ make install
# pskt should no be available in your PATH
$ pskt
Project Setup
Spago now has buildin support for alternative backends. To build you project for kotlin:
$ cd your-project
$ spago init
# tell spago that you want to use the pskt backend
$ sed -i '6 a , backend = \n "pskt"' spago.dhall
# now use spago normally
$ spago build
# compiled files can be found in output/pskt
$ ls output/pskt
Main.kt Prelude.kt ...
To run build a jar file and run it, you will first need to import foreign-files:
$ git clone git@github.com:csicar/pskt-foreigns.git
$ spago run --node-args --foreigns --node-args ./pskt-foreigns
Troubleshooting
When compiling with kotlinc
you might get some weird errors like OutOfMemory
exceptions.
This can be fixed by using a gradle build script. See kotlin test for an example.
Sometimes the kotlin compiler reports this error: e: ...Main.kt: (6, 8): Redeclaration: Module
The fix is removing the gradle build folder and compiling again: rm -r build
Implementation
PureScript | Kotlin |
---|---|
Int | Int (might be changed to Long) |
Number | Double |
Array | List<Any> |
String | String |
Char | Char |
Boolean | Boolean |
Records | Map<String, Any> |
Unit | Unit |
Data types are faithfully represented using sealed classes
:
data T a = T a | B Int String
becomes
sealed class _Type_T() {
data class T(val value0: Any)
data class B(val value0: Any, val value1: Any)
}
Function calls are sugared with .app(...)
to make the generated code more readable. app
is an extension function, that can be inlined by the kotlin compiler adding to runtime overhead.
Modules
- All transpiled Purescript modules will be located in the
PS.
package in Kotlin (eg.Data.Array
in PS will bePS.Data.Array
in Kt) - Definitions in a module in Purescript will be located in the
Module
object (eg.Data.Array.range
in PS will bePS.Data.Array.Module.range
in Kt) - Foreign definitions are located in the
Foreign.
package in Kotlin, followed by the same Namespace that is used in Purescript (eg. the foreign definitionarrayMapImpl
in the moduleData.Array
should be located inForeign.Data.Array
in Kotlin)
Bootstrapping
When pskt
is started with the --run <QualifiedMainFunction>
option, bootstrapping will be done by the compiler. Example: pskt --foreigns foreigns/ --run Main.main
When using --run
, the entry point of the purescript program is expected to have type :: Effect _
.
It is also possible to do custom bootstrapping:
To run a purescript program main :: Effect Unit
defined in the module MyModule
:
fun main() {
PS.MyModule.Module.main() // or use .appRun() if the type of `main` is Any
}
If your main
function expects additional arguments, you call it like this: PS.MyModule.Module.main.app(activity).appRun()
FFI
Implementations for FFI files for prelude and some default libraries can be found here. The implementation is not complete but mostly working.
FFIs should be implemented using lambda expressions (not anonymous functions) and accept Any
as the arguments:
package Foreign.Data.MyFFI;
val myFFI = { str: Any ->
{ f: Any ->
str as String; f as (String -> (Int -> Double))
f(str)(2)
}
}
The actual types can be added using casts in the body. Kotlin will smart-cast the arguments (here str
and f
) to their respective type.
Effect a
is represented as () -> Any
; just like PureScript represents it in JavaScript.
Note, that names are normalized: _
is replaced by __
and $
is replaced by _dollar
. For example if you define a foreign import named _myFFI
in purescript, the corresponding definition in kotlin must be named __myFFI
.
Android
An example for android can be found here.
ToDos
- MagicDo
- Build cache
- use Shake
-
--run
for spago CLI spec - TCO: through Kotlin's
tailrec
- expand inlining
-
$
-
>
,>=
...
-
- Android library
- beginnings in the android example
- Aff
- build on Coroutines'
Async
&Deferred
?bind
:{a, f -> async { f(a.await()) }
- ...
- reuse message queue? https://developer.android.com/reference/android/os/MessageQueue
- build on Coroutines'