Class Types in Dynamic Languages

I must admit that I have only basic Python knowledge and Haskell training.

I was wondering if the concept of type classes / exists in Python or in Clojure (or some other dynamically strongly typed language)?

In other words, if I have a function name f , then depending on the run-time parameter supplied to f , another implementation function will be called (for example, the function == for types related to Eq type in Haskell). Is there such a concept in dynamic languages ​​like Clojure / Python?

+8
python lisp clojure
source share
5 answers

You can get closer to this with multimethods or protocols in clojure or with simple member functions (class methods) in python. There is one important function missing in each of them that are present in haskell, though: return type polymorphism.

The compiler knows what type you are "expecting" the return function, and you can accordingly transfer to another implementation. This means that the same function, called the same arguments, can do something completely different, depending on what is done with its return value. For example:

 Prelude> read "[5,6,7]" :: [Int] [5,6,7] Prelude> read "[5,6,7]" :: [Double] [5.0,6.0,7.0] 

Similarly, you can even have polymorphic constants that have different meanings for each typeclass instance:

 Prelude Data.Word> (minBound, minBound, minBound) :: (Int, Bool, Word8) (-9223372036854775808,False,0) 

You cannot do this in a dynamic language because there is no type inference. You can fake it a bit by skipping objects that represent the "type of result I want" and using it as a dispatcher, but this is not exactly the same.

+4
source share

Multiple submissions ( an example in Julia's language ) have similar goals with class types. Multiple submissions provide compile-time polymorphism (like type classes), while object interfaces in typical dynamic languages ​​(like Python) are usually limited to run-time polymorphism. Multiple dispatching provides better performance than regular interfaces that you can see in dynamic object-oriented languages, so it makes sense in dynamic languages.

There are several few implementations of submitting for Python, but I'm not sure if they provide compiler polymorphism.

+4
source share

Multimethods seems to do the trick in Clojure. For example, let's define a plus function that adds numbers, but combines the string representation of something else.

 (defmulti plus (fn [& xs] (every? number? xs))) (defmethod plus true [& xs] (apply + xs)) (defmethod plus false [& xs] (apply str xs)) (plus 1 8) ;9 (plus 1 \8) ;"18" 

Multimethods are functions ( (ifn? plus) is true ), so they are first-class, as you might wish:

 (let [z (partial plus 5)] (z \3)) ;"53" 
+4
source share

You cannot define multiple functions with the same name in Python (in the same scope). If you do this, the second definition will be overwritten by the first and will be the only one that is called (at least when both are in the same scope - obviously, you can have class methods in different classes that have a name). The parameter list is also ignored by type, therefore, even if you could define functions twice, the interpreter will distinguish them only by the number of parameters, and not by type. What you need to do is write one function that can process several different lists of arguments, and then, if necessary, check their type inside this function.

The easiest way to do this is to use the default parameters and keyword arguments.

Default options

Say you had a function like this:

 def BakePie(crust, filling="apple", oventemp=(375,F), universe=1): ... 

You can call this function using the following positional arguments:

 BakePie("graham cracker") BakePie("buttery", "cherry") BakePie("fluffy", "lemon meringue", (400,F)) BakePie("fluffy", "key lime", (350,F), 7) 

Keyword Arguments

Everything will work, but you cannot always change each by default. What if you want to bake a standard Apple Pie, just in a different universe? Well, you can call it using the keyword arguments:

 BakePie("buttery", universe=42) 

In this case, the default arguments will be used to fill in and oventemp, and only the argument for the universe (and the cortex, which should always be specified, since there is no default value) will be changed. One rule here is that any keyword arguments must be to the right of any positional arguments when calling the function. The order of the keyword arguments does not matter, for example. this will also work:

 BakePie("fluffy", oventemp=(200, C), filling="pecan") 

But it will not be:

 BakePie(filling="boysenberry", "crumb") 

More with keyword arguments

Now, what if the behavior of your function is completely different depending on which arguments are passed? For example, you have a multiplication function that takes either two integers, or takes an integer and a list of integers, or two lists of integers, and multiplies them. This situation is one in which you, the caller, want to use only the keyword arguments. You can configure the function definition as follows:

 def GenericMultiply(int1=False, int2=False, ilist1=False, ilist2=False): # check which parameters have values, then do stuff 

(Or use None instead of False.)

When you need to multiply two integers, call this:

 GenericMultiply(int1=6, int2=7) 

NOTE. You can also perform the above with only two positional arguments and manually check their type inside the function, either using the type () function or using try: except: blocks and eg. calling methods that work only on lists or integers for each argument.

additional literature

There are many other ways you could do this in Python, I just tried to describe the simplest one. If you want to know more, I recommend the official section of the Python manual on “Defining Functions” and the next section “More on Defining Functions” (this will lead to a detailed discussion of the positional and keyword arguments, as well as the * args and ** kwargs syntax, which allows you to define functions with variable length parameter lists without the need to use default values).

+2
source share

To some extent, this function is replicated by class methods. For example, the __repr__ method __repr__ about the same as a class like Show in Haskell:

 $ ghci >>> x = 1 >>> show x "1" >>> x = [1,2,3] >>> show x "[1,2,3]" 

or in python

 $ python >>> x = 1 >>> x.__repr__() '1' >>> x = [1,2,3] >>> x.__repr__() '[1,2,3]' 

It is clear that in each case a different function is called depending on the type (in the case of Haskell) or the class (in the case of Python) that Show / __repr__ to /.

A closer approximation for the languages ​​that support them is the interface — abstract classes with all their methods are also abstract (they are called interfaces in Java and virtual classes in C ++). You are not inclined to see them in dynamically typed languages, because the main point of the interface is the declaration of a set of methods and the types associated with them that the implementation class must correspond to. Without static types, it makes no sense to indicate what types should be.

+1
source share

All Articles