Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating methods bound to records in Haskell

I'm creating a lazy, functional DSL, which allows users to define non-mutable structures with methods (something like classes from OO languages, but they are not mutable). I compile the code of this language to Haskell code.

Recently I faced a problem with this workflow. I do not want to force the user to write explicit types, so I want to heavily use Haskell's type inferencer. The problem occurs when I'm translating a function, which calls multiple times a polymorphic method of an "object", passing each time different argument types, like here:

(pseudocode):

class X {
   def method1(a, b) {
       (a, b) // return
   }
}
def f(x) {
   print (x.method1(1,2))              // call method1 using Ints
   print (x.method1("hello", "world")) // call method1 using Strings
}

def main() {
   x = X() // constructor
   f(x)
}
  1. What is the best way of generating "equivalent" Haskell code of the OO pseudocode I've provided? I want:

    • to be able to translate non-mutable classes with methods (which can have default arguments) to Haskell's code. (preserving laziness, so I do not want to use ugly IORefs and mimic mutable data structures)
    • not to force the user to explicitly write any types, so I can use all available Haskell mechanisms to allow automatic type inference - like using Template Haskell to automatically generate typeclass instances for given methods (etc.).
    • to be able to generate such code with my compiler, without the need of implementing my own type inferencer (or with my own type inferencer if there is no other solution)
    • the result code to produce fast binaries (be nicely optimized while compiling).
  2. If the proposed below workflow is the best possible one, how can we fix the proposed Haskell code, in such a way, that both f con_X and f con_Y will work? (see below)

Current work status

The pseudocode can be easily translated into following Haskell code (it is hand-written, not generated, to be simpler to read):

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

-- class and its constructor definition
data X a = X { _methodx1 :: a } deriving(Show)
con_X = X { _methodx1 = (\a b -> (a,b)) }

-- There can be other classes with "method1"
class F_method1 cls sig where
  method1 :: cls sig -> sig

instance F_method1 X a where
  method1 = _methodx1

f x = do
  print $ (method1 x) (1::Int) (2::Int)
  print $ (method1 x) ("Hello ") ("World")

main = do
  let x = con_X
  f x

The above code does not work, because Haskell cannot infer implicit types of rank higher than 1, like the type of f. After a bit of discussion on #haskell irc, a partial solution was found, namely we can translate the following pseudo code:

class X {
   def method1(a, b) {
       (a, b) // return
   }
}

class Y {
   def method1(a, b) {
       a // return
   }
}

def f(x) {
   print(x.method1(1, 2))
   print(x.method1("hello", "world"))
}

def main() {
   x = X()
   y = Y()
   f(x)
   f(y)
}

to Haskell code:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}


data Y a = Y { _methody1 :: a } deriving(Show)
data X a = X { _methodx1 :: a } deriving(Show)

con_X = X { _methodx1 = (\a b -> (a,b)) }
con_Y = Y { _methody1 = (\a b -> a) }

class F_method1 cls sig where
  method1 :: cls sig -> sig

instance F_method1 X a where
  method1 = _methodx1

instance F_method1 Y a where
  method1 = _methody1

f :: (F_method1 m (Int -> Int -> (Int, Int)),
      F_method1 m (String -> String -> (String, String)))
      => (forall a. (Show a, F_method1 m (a -> a -> (a,a))) => m (a -> a -> (a, a))) -> IO ()
f x = do
  print $ (method1 x) (1::Int) (2::Int)
  print $ (method1 x) ("Hello ") ("World")

main = do
  f con_X
  -- f con_Y

This code indeed works, but only for data type X (because it has hardcoded the return type of method1 in signature of f. The line f con_Y does not work. Additionally, is there any way to automatically generate the signature of f or do I have to write my own type inferencer for that?

UPDATE

The solution provided by Crazy FIZRUK indeed works for this specific case, but using existential data types, like data Printable = forall a. Show a => Printable a force all methods with a specific name (ie. "method1") to have the same result type across all possible classes, which is not what I want to achieve.

The following example clearly shows what I mean:

(pseudocode):

class X {
   def method1(a, b) {
       (a, b) // return
   }
}

class Y {
   def method1(a, b) {
       a // return
   }
}

def f(x) {
   print(x.method1(1, 2))
   x.method1("hello", "world") // return
}

def main() {
   x = X()
   y = Y()
   print (f(x).fst())    // fst returns first tuple emenet and is not defined for string
   print (f(y).length()) // length returns length of String and is not defined for tuples
}

Is it possible to translate such code to Haskell, allowing f to return result of a specific type based on type of its argument?

like image 718
Wojciech Danilo Avatar asked Oct 20 '13 23:10

Wojciech Danilo


1 Answers

Solution

Ok, this is how you can mimic the desired behavior. You'll need two extensions, namely RankNTypes and ExistentialQuantification.

First, put rank-2 types into X and Y. Because it is the property of class method (I mean OO class here):

data X = X { _X'method :: forall a b. a -> b -> (a, b) }
data Y = Y { _Y'method :: forall a b. a -> b -> a }

Next, you need to specify what properties have the return type of "method". This is because when calling method in f you don't know the implementation of class you're using. You can either constraint return type with a typeclass or, probably, use Data.Dynamic (I'm not sure about last). I will demonstrate the first variant.

I will wrap the constraint in an existential type Printable:

data Printable = forall a. Show a => Printable a

instance Show Printable where
    show (Printable x) = show x

Now we can define the desired interface that we will use in type signature of f:

class MyInterface c where
    method :: forall a b. (Show a, Show b) => (a, b) -> c -> Printable

It is important that the interface is also polymorphic. I placed arguments in a tuple to mimic also usual OOP syntax (see below).

Instances for X and Y are straightforward:

instance MyInterface X where
    method args x = Printable . uncurry (_X'method x) $ args

instance MyInterface Y where
    method args y = Printable . uncurry (_Y'method y) $ args

Now f can be written simply:

f :: MyInterface c => c -> IO ()
f obj = do
    print $ obj & method(1, 2)
    print $ obj & method("Hello, ", "there")

Now we can create some objects of OO classes X and Y:

objX :: X
objX = X $ λa b -> (a, b)

objY :: Y
objY = Y $ λa b -> a

And run the thing!

main :: IO ()
main = do
    f objX
    f objY

Profit!


Helper function for convenient syntax:

(&) :: a -> (a -> b) -> b
x & f = f x
like image 190
fizruk Avatar answered Oct 24 '22 10:10

fizruk