You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This post describes how to call go code from your PureScript code in psgo. This is also often referred to as the Foreign Function Interface (FFI) because we are interfacing with functions that are foreign to the PureScript language.
Prerequisites
This guide assumes you have gone through the getting started tutorial and have successfully created a Hello World psgo program with spago.
One time setup:
The steps outlined in this section have to be performed only once per project. Once you are done with this, whenever you would like to add more FFI files after, you can refer to the next section.
This process involves the following steps:
Setting up a folder that will contain all your FFI files
Creating a go.mod file in that folder
Linking that go module up in your main go.mod
Create a folder for your local FFI files:
This folder will contain all the local FFI files you will write for the current project.
mkdir ffi
Create a file called go.mod in the ffi folder with the following content:
moduleproject.localhost/ffi
Modify the file go.modin the root folder thusly:
After the existing lines starting with replace add the following:
replaceproject.localhost/ffi=> ./ffi
Add the following line to the require block towards the end:
project.localhost/ffiv0.1.0// indirect
This diff shows the additions:
Add a new FFI module
Now you're ready to add the first FFI module to your project.
Here's the gist of what needs to be done:
Add an entry in ffi_loader.go
Add a folder in ffi
Add the go file in a subfolder of ffi
Add the PureScript file in src
For this example we will make go print the words "Hello from Go".
Create an entry in purescript-native/ffi_loader.go
Change the bottom of the file to say:
// Add your own FFI packages here.
import (
_ "project.localhost/ffi/my-first-ffi"
)
Create a folder in ffi
We will create folders in the ffi folder that we created in the previous section.
A folder here has the granularity of a PureScript library. It may contain multiple files.
In accordance with the name chosen above name the folder: my-first-ffi.
Write the Go file
We will create but one file that we call Go_Hello.go. The convention here is to call the file according to the module name under which it appears in the PureScript code. In this case this would be Go.Hello.
This file lives in ffi/my-first-ffi/Go_Hello.go
As an example, here is the full content of the file:
Now in order to try it out change your Main.purs file to
moduleMainwhereimportPreludeimportEffect (Effect)
importGo.Hello (hello)
main::EffectUnit
main = hello
Do:
spago run
And you should see:
Hello from Go!
As your output.
Congratulations! You have successfully created a new FFI module for psgo and called go code from PureScript!
Explanations:
Let's go through the lines of Go_Hello.go one by one:
We start with:
package my_first_ffi
Conventionally this is the same as the folder name except that dashes become underscores.
Then there's an import statement which imports:
. "github.com/purescript-native/go-runtime"
This gives the go runtime which is a tiny (43 lines) library. That mostly provides type aliases for writing Go/PureScript FFI.
Next is this bit:
funcinit() {
exports:=Foreign("Go.Hello")
Here we define the entry function for this module (always called init). We also initialise our exports which is the dictionary of functions that this module will expose. We specify the exact module name that we will call later from PureScript: "Go.Hello".
What then follows is our export, the function hello:
exports["hello"] =func() Any {
fmt.Println("Hello from Go!")
returnnil
}
It is a function that takes zero arguments func() and it returns Any.
Let's compare this to the PureScript:
foreignimport hello ::EffectUnit
Effect really stands for a function that takes no arguments and Unit is a type with only one possible value unit (hence the name).
So the func() fits Effect but Any is a type alias for interface{} which really could be anything.
We always type non-function types in Go FFI as Any. It is left to the programmer to ensure that they actually use the correct types.
In order to return a unit we simply do return nil in the go code.
exports["strLen"] =func(str_Any) Any {
str:=str_.(string)
returnlen(str)
}
In this example len needs a string. Therefore we need to use an FFI pattern where we follow the argument name with an underscore (str_) and then cast to a string further down. The other direction of widening the type of the result of len(str) is no problem and there is no cast needed to go from int to Any.
Functions with multiple parameters
Purescript:
foreignimport addInts ::Int->Int->Int
Go:
exports["addInts"] =func(n1_Any) Any {
returnfunc(n2_Any) Any {
n1:=n1_.(int)
n2:=n2_.(int)
returnn1+n2
}
Be careful to always specify a return type with Any.
Because functions in PureScript are always curried the FFI for functions with multiple arguments becomes quite unwieldy.
To make this more bearable you can install the functions library with spago install functions.
Then the following works: