mathlink-0.1.0.1: examples/src/Main.hs
{-# OPTIONS_GHC -O #-}
{- The -O option is necessary to get the rewrite rules which use more
efficient marshaling for certain types of lists to fire. According
to the GHC documentation, it should be sufficient to set
-fenable-rewrite-rules, but this didn't work in the test cases I
examined, while -O (which implies -fenable-rewrite-rules) did.
In the case of 'String,' there is not just an efficiency difference,
but a semantic one, as well. Without the rule rewrite, the string is
marshaled out as a list of characters, which yields a list of single-
character strings on the Mathematica side (and probably breaks when
trying a get). In the absence of the rewrite rules for strings,
there are two alternatives: (1) marshal strings as a generic
Expression (or part of an expression), or, for just a single string
in isolation you can (2) use the putString/getString exported from
the Expressible module.
-}
module Main where
import Foreign.MathLink
import Data.Array.Unboxed
import Data.Complex
import Control.Monad.Error (throwError)
-- Example 1: get a pair of integers and return their sum.
addTwo :: ML ()
addTwo = do
(m1,m2) <- get
put ((m1 + m2) :: Int)
-- callPattern <=> mprep's :Pattern: directive
-- argumentPattern <=> mprep's :Arguments: directive
-- Note that, in argumentPattern, the desired 2-tuple of integers
-- is represented as a Mathematica list of length 2.
addTwoFunction =
Function { callPattern = "AddTwo[i_Integer,j_Integer]"
, argumentPattern = "{i,j}"
, function = addTwo
}
-- Example 2: reverse a list of numbers (coercing them to Doubles).
reverseNumbers :: ML ()
reverseNumbers = do
is <- get
put $ reverse (is :: [Double])
-- Like tuples, lists are represented on the Mathematica side as
-- lists, but of arbitrary length. Here, the Mathematica pattern
-- would accept a list of zero or more numeric values.
-- Notice that argumentPattern provides some preprocessing on the
-- arguments, taking its real part and coercing the values to
-- machine precision. This trick allows for a callPattern that
-- matches more expressions, while still ensuring that on the
-- Haskell side, the arguments have the desired, more specific,
-- form when marshaled.
reverseNumbersFunction =
Function { callPattern = "ReverseNumbers[is___?NumericQ]"
, argumentPattern = "N[Re[{is}]]"
, function = reverseNumbers
}
-- Example 3: put a string.
-- It's a good test to see if the rewrite rules are firing: if the
-- rule fired, you should see a single string in Mathematica,
-- otherwise, a list of single-character strings.
greetWorld :: ML ()
greetWorld = do
put "Hello, world!"
greetWorldFunction =
Function { callPattern = "GreetWorld[]"
, argumentPattern = ""
, function = greetWorld
}
-- Example 4: get and put a 2D array, reversing its contents.
-- NB: When marshaling in an array, make sure that you test for the
-- array rank in callPattern to match that expected on the Haskell
-- side, as below. Otherwise, you're likely to have the underlying
-- call to fromDimensions raise an error that halts the program.
reverseArray :: ML ()
reverseArray = do
arr <- get
let xs = elems (arr :: UArray (Int,Int) Int)
put $ ((listArray (bounds arr) $ reverse xs) :: UArray (Int,Int) Int)
reverseArrayFunction =
Function { callPattern = "ReverseArray[a_?(ArrayQ[#,2,IntegerQ]&)]"
, argumentPattern = "a"
, function = reverseArray
}
-- Example 5: gets an arbitrary Mathematica expression and returns
-- a tweaked version of it.
tweakExpression :: ML ()
tweakExpression = do
expr <- get
put $ tweak expr
where tweak ex =
case ex of
ExInt i -> ExInt (-i)
ExReal r -> ExReal (-r)
ExString s -> ExString $ reverse s
ExSymbol s -> ExSymbol $ reverse s
ExFunction hd args -> ExFunction (reverse hd) $ map tweak args
tweakExpressionFunction =
Function { callPattern = "TweakExpression[expr_]"
, argumentPattern = "expr"
, function = tweakExpression
}
-- Example 6: use your own instance of Expressible
data ExtendedComplex = Finite (Complex Double)
| Infinity
deriving (Eq,Show)
instance Expressible ExtendedComplex where
put Infinity = put $ ExSymbol "Infinity"
put (Finite (r :+ i)) = put $ ExFunction "Complex" [ExReal r,ExReal i]
get = do
expr <- get
case (expr :: Expression) of
ExFunction "DirectedInfinity" [_] ->
return Infinity
ExFunction "Complex" [ExReal r, ExReal i] ->
return $ Finite (r :+ i)
_ ->
throwError $ "Unexpected expression: " ++ show expr
addExtendedComplexes :: ML ()
addExtendedComplexes = do
(ec1,ec2) <- get
case (ec1,ec2) of
(Infinity,_) -> put Infinity
(_,Infinity) -> put Infinity
(Finite c1,Finite c2) -> put $ Finite (c1 + c2)
addExtendedComplexesFunction =
Function { callPattern = "AddExtendedComplexes[ec1:(Infinity|Complex[_,_]),ec2:(Infinity|Complex[_,_])]"
, argumentPattern = "N[{ec1,ec2}]"
, function = addExtendedComplexes
}
-- run mathlink exposing the given list of actions
main = runMathLink [ addTwoFunction
, reverseNumbersFunction
, greetWorldFunction
, reverseArrayFunction
, tweakExpressionFunction
, addExtendedComplexesFunction
]