Mulone
Mulone

Reputation: 3663

From Haskell to functional Python

I want to translate some Haskell code into Python. The Haskell classes/instances look like:

{-# LANGUAGE MultiParamTypeClasses #-}

module MyModule where

class Example a b where
    doSomething :: a -> b -> Bool 
    doSomethingElse :: a -> b -> Int

instance Example Int Int where
    doSomething a b = (a + b * 2) > 5
    doSomethingElse a b = a - b * 4

Is there a way in Python to approximate the Haskell class/instance construct? What is the least offensive way to translate this into Python?

Upvotes: 4

Views: 305

Answers (2)

bheklilr
bheklilr

Reputation: 54058

This doesn't really have an analogue in Python, but you can fake it:

def int_int_doSomething(a, b):
    return (a + b * 2) > 5


def int_int_doSomethingElse(a, b):
    return a - b * 4


Example = {}
Example[(int, int)] = (int_int_doSomething, int_int_doSomethingElse)


def doSomething(a, b):
    types = type(a), type(b)
    return Example[types][0](a, b)


def doSomethingElse(a, b):
    types = type(a), type(b)
    return Example[types][1](a, b)

All you have to do is add new values to Example for each type combination you want to have. You could even throw in some extra error handling in doSomething and doSomethingElse, or some other methods to make it easier. Another way would be to make an object that keeps track of all of these and lets you add new types to the map in a more managed way, but it's just more bookkeeping on top of what I've already shown.

Keep in mind that this is essentially how Haskell does it, too, except the checks are performed at compile time. Typeclasses are really nothing more than a dictionary lookup on the type to pick the appropriate functions to insert into the computation. Haskell just does this automatically for you at compile time instead of you having to manage it yourself like you do in Python.


To add that bookkeeping, you could do something like the following, keeping it in its own module and then it'll only (by default) export the symbols in __all__. This keeps things looking more like the Haskell version:

class _Example(object):
    def __init__(self, doSomething, doSomethingElse):
        self.doSomething     = doSomething
        self.doSomethingElse = doSomethingElse

ExampleStore = {}

def register(type1, type2, instance):
    ExampleStore[(type1, type2)] = instance

def doSomething(a, b):
    types = type(a), type(b)
    return ExampleStore[types].doSomething(a, b)

def doSomethingElse(a, b):
    types = type(a), type(b)
    return ExampleStore[types].doSomethingElse(a, b)

def Example(type1, type2, doSomething, doSomethingElse):
    register(type1, type2, _Example(doSomething, doSomethingElse))

__all__ = [
    'doSomethingElse',
    'doSomethingElse',
    'Example'
]

Then you can make instances like

Example(int, int,
    doSomething=lambda a, b: (a + b * 2) > 5,
    doSomethingElse=lambda a, b: a - b * 4
)

Which looks almost like Haskell.

Upvotes: 4

fortran
fortran

Reputation: 76057

You don't have parametric types in Python, as it's dynamically typed. Also the distinction between classes and instances is clear in Python, but as classes are themselves "live objects", the distinction of usage might be a little bit blurred sometimes...

For your case, a classical implementation might go as:

#you don't really need this base class, it's just for documenting purposes
class Example:
    def doSomething(self, a, b):
        raise "Not Implemented"
    def doSomethingElse(self, a, b):
        raise "Not Implemented"

class ConcreteClass(Example):
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    def doSomething(self, a, b):
        return (a + b * self.x) > self.y
    def doSomethingElse(self, a, b):
        return a - b * self.z

 instance = ConcreteClass((2, 5, 4)

but I personally dislike that convoluted style, so you might just go with something more lightweight, like:

 from collections import namedtuple
 Example = namedtuple('Example', 'doSomething doSomethingElse')
 instance = Example((lambda a, b: (a + b * 2) > 5),
                    (lambda a, b: a - b *4 ))

And of course, rely on duck typing and usually "let it crash". The lack of type safety should be made up with extensive unit testing.

Upvotes: 0

Related Questions