6 ms·
Polymorphism can be acheived without using inheritance. See Clojure's Protocols or Haskell's type classes for examples of this.
by glenjamin 13y ago
Polymorphism can be acheived without using inheritance.
See Clojure's Protocols or Haskell's type classes for examples of this.
- oinksoft 13y agoSomebody has also made a JavaScript library for this: https://github.com/Gozala/protocol https://github.com/Gozala/protocol
- dllthomas 13y agoTwo points here regarding Haskell. First, a function with typeclass constraints is less polymorphic (in that it will operate on fewer types) than polymorphic function without typeclass constraints. Of course, [the fully polymorphic version is] also more limited in how it interacts with the corresponding values. Second, parametric polymorphism in Haskell is statically resolved. You can have polymorphic functions, but any given container still contains a single type. You can still do dynamic polymorphism in Haskell (by storing a list of records of functions, rather than storing data directly) but it doesn't typically involve type classes.
- gsg 13y ago> Of course, it's also more limited in how it interacts with the corresponding values. This is backwards. Polymorphic values without constraints admit almost no operations at all - you can "copy" them, that's it. This is true to the point that (save diverging) given f :: a -> a, f can only have one meaning. Invariance of sequence elements is also far less problematic in ML-family languages because they have sums.
- dllthomas 13y ago"It" here being the fully polymorphic version. Rereading, it does seem misleading (or at best ambiguous) so I've expanded the pronoun.
- thinkpad20 13y agoI haven't really explored this area of Haskell, but I think there are certain cases where this is possible. For example, I think there might be a way to have a list of tuples of `forall a. [(a -> b, a)]`, where a's type can vary, but applying the first element of the tuple to the second will always produce a `b`. I'm not sure if this is actually the case but it seems (theoretically) possible, and certainly would be convenient. More experienced Haskellers feel free to chime in...
- tel 13y agoYeah, that's certainly possible. It's also largely frowned upon because it's usually over complex. For instance, in your example [exists a . (a -> b, a)] is literally completely equivalent to [b] as the types ensure there is no other thing that can be done with those pairs. The convenience factor is thus almost never the case. There are some nice theoretical properties and a great embedding of OO in Haskell via existential typing [0], but it should rarely be used. [0] http://www.cs.ox.ac.uk/jeremy.gibbons/publications/adt.pdf http://www.cs.ox.ac.uk/jeremy.gibbons/publications/adt.pdf
- thinkpad20 13y agoOK, since I didn't give a very good example, let me try to show a better one. Let's imagine you're writing a testing library. You have a series of tests; each one of them takes in an input, a function to run on the input, and an expected output. data Test i o = Test String i (i -> o) o and then say your testing function is something like runTest :: Eq o => Test i o -> IO () runTest (Test name input f output) = case f input of o | o == output -> putStrLn $ name ++ " passed" | otherwise -> putStrLn $ name ++ " failed" Then let's say you had a bunch of tests. For example, you want to test that addition works: test1 = Test "addition" (1, 2) (\a b -> a + b) 3 And you want to test string concatenation: test2 = Test "concat" ("hello", "world") (\a b -> a ++ b) "helloworld" Then you could write your tests as doTests = runTest test1 >> runTest test2 Now if you have a lot of tests, it would be nice to put them in a list: doTests tests = forM_ tests runTest However, this would require that every test have the same inputs and outputs. You couldn't do doTests [test1, test2] Even though the resulting type is known (it will be an IO () regardless), and even though runTest will operate on each one, because test1 and test2 have different types, you can't put them all in a list. I think that `forall` and similar allow you to get around this restriction somehow, but I don't really know how that works.
- chongli 13y agoYou can have polymorphic functions, but any given container still contains a single type. This is the case for all typed programming languages, not just Haskell. So-called dynamic languages like Python, Ruby or Javascript are merely static languages with but a single type[0]. [0] http://existentialtype.wordpress.com/2011/03/19/dynamic-languages-are-static-languages/ http://existentialtype.wordpress.com/2011/03/19/dynamic-lang...
- dllthomas 13y agoIn C++, if I have a std::list<Parent *> then some of those pointers may actually point at a Child. The client code doesn't care. This is an important kind of polymorphism. In Haskell, you can't reasonably express this with typeclasses, which surprises folks new to Haskell. You can still express it (as I mentioned), it just takes a different form (and precisely which form is best can vary with other considerations).
- chongli 13y agoIn Haskell you don't need to express this with type classes as it is trivially covered by sum types. Type classes are mostly syntactic sugar providing for ad-hoc polymorphism. The real power is provided by the underlying algebraic data types.
- swift 13y agoThe second point brings up an issue that confused me when I was first learning Haskell; maybe I can help others that are similarly confused. Coming from OO languages, the lack of heterogeneous containers seems painful in Haskell - after all, in OO languages you use containers of superclass or interface pointers all the time. Haskell has a different approach to handling the same problems, though, and it turns out there are several ways you can create (or eliminate the need for) heterogeneous containers. The idiomatic way you'd create a "heterogeneous container" is to store a single algebraic datatype with different constructors, rather than try to store different types at all. This doesn't actually give you a heterogeneous container, of course, but it works perfectly in most cases, because in most cases the set of things you need to store in the container is closed. You really only need a truly heterogeneous container when you need the ability for someone else to come along and extend that set. Concretely, if you're writing a ray tracing application, you know all the possible shapes you may need to handle, and this approach is perfect. On the other hand, if you're writing a ray tracing library and you want the library user to be able to define new shapes, you may want to consider another approach. The idiomatic approach here was already mentioned by dllthomas: this is a functional programming language, so use functions! Specifically, use a record of functions, with each function in the record serving the same role as a method in OO. The functions can have private data by closing over it. Haskell has a couple of other options available, as well. You can use existential types, but they don't really buy you anything over the record of functions approach other than perhaps looking superficially more like how you'd do things in an OO language. With this approach you define a typeclass and make all the types you want to store instances of it. The container then stores instances of the typeclass, rather than a concrete type. You can also use Data.Dynamic to create dynamic values, which will allow you to store a truly unconstrained mix of types in the same container. Since you have to cast the dynamic values back to their real type before using them, though, this isn't a great solution - you end up with code that looks similar to chains of 'instanceof' in Java or 'dynamic_cast' in C++.
- seanmcdirmid 13y ago> The idiomatic approach here was already mentioned by dllthomas: this is a functional programming language, so use functions! Specifically, use a record of functions, with each function in the record serving the same role as a method in OO. The functions can have private data by closing over it. This is also how one does OO programming in C: just roll your own v-table using a struct of function pointers. You don't get to close over an environment in that case, so you have to be careful to pass everything in.
- brentshields 13y agoYou can use GADTs for this. For example: data Showable where Showable :: Show a => a -> Showable This allows you to create a polymorphic container like [Showable 5, Showable "hello"] where the polymorphic type is constrained to be a member of the Show typeclass.
- tel 13y agoNote that GADTs are a bit overkill for this. All you really need is ExistentialQuantification. GADTs are ExistentialQuantification + TypeEqualities.