Hyphen
hyphen - access Haskell modules from Python
Install / Use
/learn @tbarnetlamb/HyphenREADME
Hyphen
Hyphen allows one to access Haskell modules from Python (3 or better). (More precisely, it allows one to access Haskell modules compiled with GHC from CPython.) It is in some sense the dual of the cpython package on Hackage, which allows Haskell code to access Python modules.
For instance:
>>> import hyphen, hs.Prelude
>>> hs.Prelude.drop(1, [1,2,3])
<hs.GHC.Types.[] object of Haskell type [GHC.Integer.Integer], containing '[2,3]'>
>>> list(hs.Prelude.drop(1, [1,2,3])) # Convert back to Python list
[2, 3]
>>> hs.Prelude.id(3)
3
Why the name?
The other obvious portmanteau is 'Pascal', which is taken.
Building
For a guide to building Hyphen, see BUILDING.md. Hyphen has been successfully built and used on Mac OS X, Ubuntu, Windows (32 bit and 64 bit) and Windows+Cygwin (32 bit and 64 bit).
Basic usage
Once you have imported hyphen, you can import Haskell modules as though they were Python modules with an hs prefix, so for example
>>> import hyphen
>>> import hs.Prelude
>>> import hs.Data.Text
The functions defined in these modules can now be called from
Python. For instance, the Haskell function Prelude.drop can be
referred to from python as hs.Prelude.drop. The only slightly fiddly
thing is that some Haskell functions can have names that Python won't
accept. (So for instance there's a function Prelude.(+), but Python
won't let you refer to hs.Prelude.(+).) In such cases, you can find
these symbols as, for example, hs.Prelude._["+"]. (Note that
hs.Prelude._["drop"] works too.)
(Quick note: so far we're just talking about how to access Haskell
code from installed modules. In this usage pattern, the Haskell code
that you want to access from Python should have Cabal install it to
the (system or user) library, and you'll be able to import it from
there. Hyphen also supports just having a file Whatever.hs in the
same directory as your python script and importing functions from
there into python: see 'non-library modules' below for more.)
When you call a Haskell function, you can call it with arguments that are themselves Haskell objects, but you can also call it with python objects as arguments, in which case we will try to marshall those arguments into Haskell objects before calling the Haskell function. For instance
>>> hs.Prelude._["+"] (1, 2)
3
>>> hs.Prelude.sum([1,2,3]) # list converted to Haskell list
6
>>> hs.Prelude.drop(5, "Hello, world")
", world"
In these three cases, the return value has been marshalled back from Haskell to Python. Our philosophy is to aggressively try to marshall parameters to Haskell types, but only to convert the very simplest types (String, Text, Int, Integer, Float, Complex) back to Python types on return. More complicated objects stay as Haskell objects. For instance:
>>> hs.Prelude.drop(1, [1,2,3])
<hs.GHC.Types.[] object of Haskell type [GHC.Integer.Type.Integer], containing '[2,3]'>
These Haskell objects can be used from python in many ways. The basic way of using them is to pass them to further Haskell functions; and this certainly works, for example:
>>> my_list = hs.Prelude.drop(1, [1,2,3])
>>> hs.Prelude.sum(my_list)
5
But we can also do other things. For instance, if the return value is a Haskell list, its python representation will be a python iterable:
>>> for x in my_list:
... print(x)
...
2
3
...and if the return value is a Haskell Map or HashMap, the resulting Haskell object will behave like a python dict...
>>> import hs.Data.Map
>>> my_map = hs.Data.Map.fromList([(1, 'Hello'), (2, 'World')])
>>> my_map[1]
'Hello'
>>> print(sorted([key for key in my_map]))
[1, 2]
...and if the return value is a function object, we can call it. (Indeed, the Haskell function objects we get returned by calling Haskell-functions-that-return-functions are the exact same kind of thing as the Haskell function objects that get imported into modules when we import hs.something; in other words, they're the same as the objects we've been calling so far.)
>>> my_func = hs.Prelude.const(4)
>>> my_func
<hyphen.HsFunObj object of Haskell type b_0 -> GHC.Integer.Type.Integer>
>>> my_func('Hello')
4
In a similar vein, if the Haskell object is in Cmp or Eq then the
corresponding python object will support comparisons or equality
tests, and if the Haskell object is hashable, the python object will
be too.
As you'd expect, you can partially apply any function just by calling it with fewer than the full number of arugments, and you'll get a function which accepts the remaining arguments later.
In addition, Haskell objects that represent IO actions can be induced to actually perform the action by calling .act on them. This returns whatever the return type of the action might be.
>>> hs.Prelude.putStrLn("Test") # Construct IO action, but don't perform it
<hs.GHC.Types.IO object of Haskell type GHC.Types.IO ()>
>>> hs.Prelude.putStrLn("Test").act()
Test
<hs.GHC.Tuple.() object of Haskell type (), containing '()'>
It goes without saying that it is important to remember to call .act: if your code doesn't seem to be doing what it's meant to be doing, it's possible that you're constructing an IO action but then discarding it without performing it!
Finally, if we find a a Haskell type T and a function f :: T -> <something> which is defined in the same module, then this gives
rise to a member function on the Python representation of objects of
type T, such that foo.f(*args) means the same as f(foo, *args). For example, if we have Haskell code in the module Test as
follows:
data Test = Test Integer deriving (Typeable, Show)
extract_number :: Test -> Integer
extract_number (Test i) = i
make_sum :: Test -> Integer -> Integer
make_sum (Test i) j = i + j
Then from python we can do:
>>> import hyphen
>>> hyphen.find_and_load_haskell_source()
>>> from hs.Test import Test
>>> my_test_obj = Test(3)
>>> my_test_obj
<hs.Test.Test object of Haskell type Test.Test, containing 'Test 3'>
>>> my_test_obj.extract_number
<bound method Test.extract_number of <hs.Test.Test object of Haskell type Test.Test, containing 'Test 3'>>
>>> my_test_obj.extract_number()
3
>>> my_test_obj.make_sum(4)
7
Names of Haskell functions that begin with an underscore are exempt
from this rule because we don't want users to accidentally end up
defining python members like __getitem which can change the behavior
of an object quite dramatically. If you want to create such members
deliberately (which you might be: perhaps you're trying to use Haskell
to build a python type that has interesting non-standard behaviors),
then you can escape the exemption as follows: if you define a member
is Haskell with a name like hy__<type-name>__<something>__, then
this will be used to create a python member called __<something>__.
For instance, continuing the example above, if the Haskell code continues
hy__Test__getitem__ :: Test -> Integer -> Integer
hy__Test__getitem__ (Test i) j = i + j
Then the python example could have continued
>>> my_test_obj[5]
8
More detail about marshalling to Haskell
We have already seen some examples of how objects are marshalled from Haskell to Python. A key point about this process is that a single Python object could be used to construct Haskell objects of various different types, depending on type expected by the Haskell function which we're applying. For instance:
>>> import hyphen, hs.Prelude, hs.Data.Text
>>> hs.Prelude.drop(6, "Hello world") # Python string -> Haskell String
'world'
>>> hs.Data.Text.drop(6, "Hello world") # Python string -> Haskell Text
'world'
>>> hs.Prelude.drop(1, (1, 2)) # Python tuple -> Haskell list
<hs.GHC.Types.[] object of Haskell type [GHC.Integer.Type.Integer], containing '[2]'>
>>> hs.Prelude.snd((1, 2)) # Python tuple -> Haskell tuple
2
On the other hand, you can apply polymorphic Haskell functions to Python objects, and the type of the Python object to which we apply the function will be used to determine what Haskell type should be used for the polymorphic arguments; for instance:
>>> hs.Prelude._['+'] (1, 2) # Select Integer version
3
>>> hs.Prelude._['+'] (1+0j, 2+3j) # Select Complex Float version
(3+3j)
>>> hs.Prelude.id([1, 2, 3]) # Invoke version of id for lists of integers
<hs.GHC.Types.[] object of Haskell type [GHC.Integer.Type.Integer], containing '[1,2,3]'>
When a Python object could have been conerted into multiple Haskell types, we will 'break the tie' and convert it to some preferred type:
>>> hs.Prelude.id((1, 2)) # Prefer to convert Python tuples to Haskell tuples, not lists
<hs.GHC.Tuple.(,) object of Haskell type (GHC.Integer.Type.Integer, GHC.Integer.Type.Integer), containing '(1,2)'>
>>> hs.Prelude.id((1, "Test")) # Prefer to convert Python strings to Haskell Text
<hs.GHC.Tuple.(,) object of Haskell type (GHC.Integer.Type.Integer, Data.Text.Internal.Text), containing '(1,"Test")'>
This is not foolproof however; for instance, we get an error in the following case:
>>> hs.Prelude._['+'] (1, 2+3j)
Traceback (most recent call last):
...
TypeError: Incompatible types: cannot resolve object of type
a -> a -> a
to type
GHC.Integer.Type.Integer -> Data.Complex.Complex GHC.Types.Float -> a
Before we close this section, we'll cover two other behaviors of the marshalling code that are important or useful.
One key point is that Python functions can be marshalled into Haskell functions. For example:
>>> hs.Prelude.foldr((lambda x, y: x + y), 0, [1, 2, 3])
6
Although you should be careful when doing this; Haskell idioms ma
