This talk is about two Perl modules (Call:Haskell and Functional::Types) I developed to call Haskell functions as transparently as possible.
In general, the only way to guarantee the correctness of the types of the function arguments in Haskell is to ensure they are well-typed in Perl. So I ended up writing a Haskell-inspired type system for Perl. In this talk I will first discuss the approach I took to call Haskell from Perl, and then the reasons why a type system is needed, and the actual type system I developed. The type system is based on "prototypes", functions that create type descriptors, and a small API of functions to create type constructors and manipulate the types. The system is type checked at run time and supports sum types, product types, function types and polymorphism. The approach is not Perl-specific and suitable for other dynamic languages.
https://github.com/wimvanderbauwhede
How to submit a standout Adobe Champion Application
Perl and Haskell: Can the Twain Ever Meet? (tl;dr: yes)
1. Perl and Haskell: Can the Twain Ever Meet?
Wim Vanderbauwhede
School of Computing Science, University of Glasgow, UK
(tl;dr: yes)
2. Outline
Perl
Haskell
Calling Haskell from Perl
C
Haskell FFI
Perl Inline::C
Serialisation
What About the Types?
The Final Picture
The Need for Proper Typing
A Modest Proposal
Implementation
Internals
Example
Conclusions
10. Calling Haskell from Perl
C, the lingua franca
Haskell FFI to call Haskell from C
Perl Inline::C to call C from Perl
Serialisation
11. Code Generation
The call to “use Call::Haskell” triggers extensive code generation:
Haskell FFI and serialisation wrappers
Corresponding C wrappers and create a C library
Inline::C wrapper to call the functions in the C library
Perl serialisation wrapper
Compile only when Haskell source code has changed, otherwise
cache
12. Serialisation
JSON? YAML? MessagePack? No: Read and Show
Because serialisation needs to be type-aware
Serialisation of Perl values is done via a custom showH function
uses Haskell type information via Data.Typeable, serialised using
show, converted to a Perl datastructure in Haskell and deserialised
using eval.
De-serialisation of Perl serialised values in Haskell is done via
read
Serialisation of Haskell values is done via show
string generated by show is converted into Perl code in Haskell
De-serialisation of Haskell result is done via a custom readH
function
uses eval and Perl’s autload feature
13. Linking
FFI uses ghc as linker
Perl requires gcc linker
ghc does not compile dynamic libraries by default
Lots of fun
14. The Final Picture
1. Perl script calls a
2. Perl serialisation wrapper which calls a
3. Perl Inline::C wrapper which calls a
4. C FFI wrapper which calls a
5. Haskell FFI wrapper which calls a
6. Haskell serialisation wrapper which calls
7. the original Haskell function
All wrappers are generated and built automatically on loading
Call::Haskell
15. What About the Types?
The outlined approach relies on Data.Typeable, Read and Show.
Data.Typeable does not provide information on type constructors
for algebraic types.
So this approach only works for “primitive” types and containers
holding primitive types.
This is already quite a lot, but it’s not good enough.
We could add other types (e.g. Data.Map and Maybe) ad hoc
However ...
16. The Need for Proper Typing
There is a fundamental issue with variant types.
Given following Haskell type:
data Qual a = Groot a | Klein a
It is impossible to generate the correctly typed value for Haskell
unless the value in Perl is also typed with the same information!
So we need a type system for Perl...
17. A Modest Proposal
I propose to create a type system to type datastructures and
function signatures in a dynamically typed language (Perl),
without changing anything to the language itself
but nevertheless with an acceptable and usable syntax.
The types must be compatible with Haskell’s types so that it is
possible to call Haskell functions with well-typed arguments and
return values.
show and read the typed values in Haskell fashion.
18. Basic Functionality
Declare the type of a typed variable:
tv :: T
Construct a typed value from other typed and/or untyped values:
tv = TC v
Bind a typed value to a typed variable:
tv = tv
Convert a typed value into an untyped value:
v
u
= tv
19. A Simple Type System
The type system provides:
The primitive scalar types Int, Float, String, Bool.
type TI = Int
type TF = Float
The container types Tuple, Array and Map:
type TT = (T1, T2, ..., TN )
type TA = [T1]
type TM = {Tk , Tv }
A mechanism to declare and construct sum and product types:
data TS = T1 | T2 ... TN
data TP = TCP T1 ... TN
20. Implementation
To implement this type system in Perl, we use
functions for the type constructors,
a small API of type naming, binding and construction functions.
prototypes to construct the actual types. Prototypes are functions
that return a type descriptor.
lightweight objects to represent the typed values and the type
descriptors returned by the prototypes.
The implementation relies on two language features:
a function can query the name of its callers (caller in Perl)
data structures can be transformed into lightweight objects (bless
in Perl)
21. newtype and typename
To create type constructors:
TC = newtype T (Prototype T1...TN )
If the type T is polymorphic, the type name can take parameters:
TC = newtype (T a b) (Prototype a b)
Also used for container types
TC = newtype T (Prototype T1...TN )
And to create an alias for an existing primitive type
T = newtype Tprim
To declare the type name T
T = typename
22. Prototypes
Algebraic datatypes:
Record: For product types, with optional support for named fields
Variant: For sum types
Container types:
Tuple: For tuples
Array: For lists
Map: For dictionaries
Functions: Function
To construct primitive types: Scalar
23. type and bind
To declare a typed variable
type tv T
If the type is a polymorphic type, we can provide the type
argument:
type tv (T T1)
To bind a typed variable to a value, we use bind, which
corresponds to “=” in Haskell.
bind tv tv
If the type of a typed variable is primitive, bind can take an
untyped value as the argument:
bind tv v
24. untype, show and read
To return an untyped value from a typed value
v = untype tv
Finally, read and show work as in Haskell
25. Internals: Type Names and
Prototypes
Type Names
The typename function returns a tuple of the name of the type
and a list of the names parameters, if any. For example, given
Couldbe = typename
then the call Couldbe a b will return the tuple (Couldbe, [a,b]).
Prototypes
The prototypes take type name functions as arguments, and
return a type descriptor, implemented as a lightweight object.
Prototypes can only be called as argument to newtype, which in
its turn can only be called in the body of a type constructor
function. For example:
IntVar = typename
MkIntVar = newtype IntVar (Record String Int)
The call to Record will return and object of type Record with as
attributes the type constructor name and a list of arguments to
the constructor, i.e. typenames
26. Internals: Typed Value Objects and
Type Construction
Typed Value Object
An object with attributes Type and Value. The Type attribute
contains the information returned by the typename or prototype
calls, the Value attribute is populated by a call to newtype or bind.
Type Construction
The call to newtype typechecks the arguments against the type
information returned by the prototype, and returns a typed value
object, with the Type field provided by the prototype.
27. Internals: Typing and Binding
The call to type returns a typed value object with empty Value
attribute, to be filled by a call to bind.
The call to bind takes a value, typechecks it and stores it in the
Value attribute of the typed value.
If the type is a primitive type or a container type holding primitive
types, then the value can be untyped and is stored as-is in the
Value attribute. In that case, the only checking that is performed is
on the type of the container.
Otherwise, the value must be typed and will be typechecked.
28. Internals: Typing and Binding
For example:
type iv IntVar
bind iv (MkIntVar “55” 7188 )
The call to MkIntVar will return a typed value object with as Type
attribute a Record object with typename IntVar. The typed value
iv will contain as Type attribute a tuple with as first argument the
typename IntVar. Type checking is simply a comparison of the
typenames.
In a simpler example:
type iv’ Int
bind iv’ (Int 42)
the call to (Int 42) would return a typed value object, and the bind
call would typecheck the typename from the Type attribute with
the typename from iv’
29. Function Types
We often want to create typed functions from untyped functions.
type f Int -> Int -> Int
bind f (x y ->x+y)
But an untyped function can still take and return typed values:
type f (Int,Int) -> Maybe Int
bind f ((x,y) -> if (y/=0) then Just x/y else Nothing)
We allow both bare and typed values for the argument, and
return typed values from the function.
The call to bind creates a new function which takes typed
arguments, typechecks, untypes them if required, passes them
to the original function, and types the return value if required.
30. Polymorphic Binary Tree Example
Polymorphic Binary Tree
Tree = typename
Branch = newtype (Tree a) (Variant Tree a Tree)
Leaf = newtype (Tree a) Variant
type tree Tree(Int)
bind tree (Branch (Branch Leaf 41 Leaf) 42 Leaf)
Actual Perl code:
31. Typed Function Call Example
use Call::Haskell import => 'VarDeclParser( parse_vardecl )';
use Types;
use VarDecl;
my $tstr = String("integer :: v");
type my $fp = String => VarDecl;
bind $fp, &parse_vardecl;
my $tres = $fp−>($tstr);
say show $tres;
my $res = untype $tres;
32. Summary
Want proper typechecking in Perl?
https://github.com/wimvanderbauwhede/Perl-Functional-Types
Want to call Haskell from Perl?
https://github.com/wimvanderbauwhede/Perl-Call-Haskell