Chcę przetłumaczyć jakiś kod Haskell w Pythonie. Klasy / instancje HASKELL wyglądają jak:

{-# 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

Czy w Pythonie jest sposób, aby przybliżyć konstrukt klasowy / instancji Haskell? Jaki jest najmniej obraźliwy sposób tłumaczenia tego do Pythona?

4
Mulone 13 sierpień 2014, 22:42

2 odpowiedzi

Najlepsza odpowiedź

To naprawdę nie ma analogu w Pythonie, ale możesz go udać:

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)

Wszystko, co musisz zrobić, to dodać nowe wartości do Example dla każdej kombinacji typu, które chcesz mieć. Można nawet rzucić inną obsługę błędów w doSomething i doSomethingElse lub niektórych innych metod, aby ułatwić. Innym sposobem byłoby złożenie obiektu, który śledzi wszystkie z nich i pozwala dodać nowe typy na mapę w bardziej zarządzany sposób, ale to tylko więcej księgowości na szczycie tego, co już pokazałem.

Należy pamiętać, że jest to zasadniczo jak to robi Herkell, z wyjątkiem kontroli przeprowadzanych w czasie kompilacji. TypeClasses są naprawdę niczym więcej niż wyszukiwanie słownika na typ, aby wybrać odpowiednie funkcje, aby wstawić do obliczeń. Haskell właśnie robi to automatycznie dla Ciebie w czasie kompilacji zamiast Ciebie, musisz sobie z tym poradzić, jak w Pythonie.


Aby dodać tę księgowość, możesz zrobić coś podobnego do następującego, utrzymując go we własnym module, a następnie tylko (domyślnie) wyeksportuj symbole w __all__. To utrzymuje się bardziej jak wersja haskell:

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'
]

Wtedy możesz zrobić wystąpienia

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

Który wygląda prawie prawie jak haskell.

4
bheklilr 13 sierpień 2014, 19:16

Nie masz typów parametrycznych w Pythonie, ponieważ jest dynamicznie wpisany. Również rozróżnienie między classes a instances jest jasne w Pythonie, ale jako classes Są sami "Live Obiekty", rozróżnienie użycia może być trochę niewyraźne ...

W przypadku twojej sprawy klasyczna implementacja może iść jako:

#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)

Ale osobiście nie lubię tego zawiłego stylu, więc możesz po prostu iść z czymś bardziej lekkim, jak:

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

I oczywiście polegamy na wpisaniu kaczki i zwykle "pozwala na awarię". Brak bezpieczeństwa typu należy składać z rozległymi testami jednostek.

0
fortran 13 sierpień 2014, 18:52