Young Tableaux from Haskell to R
The goal of this article is to show how to do a Haskell DLL for R with functions returning an arbitrary number of vectors (atomic vectors or lists). For the basics of exporting a Haskell function to R with the help of a DLL, we refer the reader to the previous article.
For the illustration, we will deal with Young tableaux with the help of the combinat library.
Prelude> import Math.Combinat.Tableaux as T
Prelude T> tableau = [[1,2,5],[3,4]]
Prelude T> asciiTableau tableau
1 2 5
3 4
We will firstly port the dualTableau
function to R.
Prelude T> dualTableau tableau
1,3],[2,4],[5]] [[
Thus, we want a R function that takes as input list(c(1L,2L,5L),c(3L,4L))
and returns list(c(1L,3L),c(2L,4L),5L)
.
Below is a first way to do so. To import the input list in Haskell, we use peekArray
, which requires to enter the length of the input list. To export the output list, we use pokeArray
.
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE DataKinds #-}
module Lib where
import Foreign
import Foreign.C
import Foreign.R (SEXP)
import qualified Foreign.R.Type as R
import qualified Data.Vector.SEXP as VS
import Math.Combinat.Tableaux
foreign export ccall dualTableauR1 :: Ptr (SEXP s R.Int) -> Ptr CInt ->
Ptr (SEXP s R.Int) -> IO ()
dualTableauR1 :: Ptr (SEXP s R.Int) -> Ptr CInt -> Ptr (SEXP s R.Int) -> IO ()
dualTableauR1 _tableau l result = do
l <- peek l
_tableau <- peekArray (fromIntegral l :: Int) _tableau
let tableau = map (VS.toList . VS.fromSEXP) _tableau
let dtableau = dualTableau tableau
pokeArray result $ map (VS.toSEXP . VS.fromList) dtableau
This way has an inconvenient: in order to use the function in R, we need to give the length of the output list. But this is not a problem for this example: the length of the output list is the length of the first vector (the first “row”) of the input list.
> tableau <- list(c(1L, 2L, 5L), c(3L, 4L))
> .C("dualTableauR1", tableau=tableau, l=length(tableau),
+ result=as.list(integer(length(tableau[[1]]))))$result
1]]
[[1] 1 3
[
2]]
[[1] 2 4
[
3]]
[[1] 5 [
The second way we give below overcomes this inconvenient:
...
import Language.R.Literal (mkProtectedSEXPVector)
import Data.Singletons (sing)
foreign export ccall dualTableauR2 :: Ptr (SEXP s R.Int) -> Ptr CInt ->
Ptr (SEXP s R.Vector) -> IO ()
dualTableauR2 :: Ptr (SEXP s R.Int) -> Ptr CInt -> Ptr (SEXP s R.Vector) -> IO ()
dualTableauR2 _tableau l result = do
l <- peek l
_tableau <- peekArray (fromIntegral l :: Int) _tableau
let tableau = map (VS.toList . VS.fromSEXP) _tableau
poke result $ mkProtectedSEXPVector sing $
(map (VS.toSEXP . VS.fromList) (dualTableau tableau) :: [SEXP s R.Int])
Indeed, we don’t need to enter the length of the output list:
> tableau <- list(c(1L, 2L, 5L), c(3L, 4L))
> .C("dualTableauR2", tableau=tableau, l=length(tableau),
+ result=list(0L))$result[[1]]
1]]
[[1] 1 3
[
2]]
[[1] 2 4
[
3]]
[[1] 5 [
These two ways to export the dualTableau
function achieve a comparable performance:
> tableau <- list(c(1L, 3L, 4L, 6L, 7L), c(2L, 5L, 8L, 10L), 9L)
> library(microbenchmark)
> microbenchmark(
+ R1 = .C("dualTableauR1", tableau=tableau, l=length(tableau),
+ result=as.list(integer(length(tableau[[1]]))))$result,
+ R2 = .C("dualTableauR2", tableau=tableau, l=length(tableau),
+ result=list(0L))$result[[1]]
+ )
: microseconds
Unit
expr min lq mean median uq max neval35.253 51.5410 256.2568 58.9040 74.2990 9919.870 100
R1 30.791 36.1455 351.1271 41.5005 48.8635 8978.753 100 R2
Thus, using the second way, we are able to get an arbitrary number of atomic vectors, without knowing in advance this number.
Now, in order to show how to get an arbitrary number of lists, we will export the standardYoungTableaux
functions, which returns the list of standard Young tableaux whose shape is a given partition.
...
import Math.Combinat.Partitions.Integer
foreign export ccall standardYoungTableauxR :: Ptr CInt -> Ptr CInt ->
Ptr (SEXP s R.Vector) -> IO()
standardYoungTableauxR :: Ptr CInt -> Ptr CInt -> Ptr (SEXP s R.Vector) -> IO()
standardYoungTableauxR partition l result = do
l <- peek l
partition <- peekArray (fromIntegral l :: Int) partition
let tableaux = standardYoungTableaux (mkPartition $ map fromIntegral partition)
let tableaux32 = map (map (map fromIntegral)) tableaux :: [[[Int32]]]
let tableauxAsSEXP = map (\x -> (mkProtectedSEXPVector sing $
(map (VS.toSEXP . VS.fromList) x :: [SEXP s R.Int]))
:: SEXP s R.Vector) tableaux32
poke result $ mkProtectedSEXPVector sing tableauxAsSEXP
Here is an example of a call:
> shape <- c(3L, 2L)
> .C("standardYoungTableauxR", partition=shape, l=length(partition),
+ result=list(0L))$result[[1]]
1]]
[[1]][[1]]
[[1] 1 3 5
[
1]][[2]]
[[1] 2 4
[
2]]
[[2]][[1]]
[[1] 1 2 5
[
2]][[2]]
[[1] 3 4
[
3]]
[[3]][[1]]
[[1] 1 3 4
[
3]][[2]]
[[1] 2 5
[
4]]
[[4]][[1]]
[[1] 1 2 4
[
4]][[2]]
[[1] 3 5
[
5]]
[[5]][[1]]
[[1] 1 2 3
[
5]][[2]]
[[1] 4 5 [
I’ve included these functions and more in a R package. It will soon be available in my drat repo, and the source code is available in my Github repo.