So I have seen questions that ask how do you do Object Oriented Programming in Haskell, like this for example. To which the answer is along the lines of "type classes are like interfaces but not quite". In particular a type class doesn't allow a list to be built of all those types. E.g. we can't do map show [1, 1.4, "hello"] despite that having a logical result.
Given some time I wondered if it wasn't possible to do better. So I had an attempt at coding polymorphism for a simple Shape class, which can be found below (if you like sanity probably better to stop reading now, and apologies for it being so long).
module Shapes (
Shape(..)
, Point
, Circle(..)
, Triangle(..)
, Square(..)
, location
, area
) where
data Point = Point {
xcoord :: Float
, ycoord :: Float
} deriving (Read, Show)
data Shape = CircleT Circle | PolygonT Polygon deriving (Read, Show)
data Circle = Circle {
cLocation :: Point
, cRadius :: Float
} deriving (Read, Show)
data Polygon = SquareT Square | TriangleT Triangle deriving (Read, Show)
data Square = Square {
sLocation :: Point
, sLength :: Float
} deriving (Read, Show)
-- only right angled triangles for ease of implementation!
data Triangle = Triangle {
tLocation :: Point
, tSide1 :: Float
, tSide2 :: Float
} deriving (Read, Show)
class ShapeIf a where
location :: a -> Point
area :: a -> Float
instance ShapeIf Shape where
location (CircleT a) = location a
location (PolygonT a) = location a
area (CircleT a) = area a
area (PolygonT a) = area a
instance ShapeIf Polygon where
location (SquareT a) = location a
location (TriangleT a) = location a
area (SquareT a) = area a
area (TriangleT a) = area a
instance ShapeIf Square where
location = sLocation
area a = (sLength a) ^ 2
instance ShapeIf Circle where
location = cLocation
area a = pi * (cRadius a) ^ 2
instance ShapeIf Triangle where
location = tLocation
area a = 0.5 * (tSide1 a) * (tSide2 a)
Despite all the madness this ends up having some quite nice properties: I can have a list of shapes and I can map functions over them that make sense (like location and area). But also if I have a particular Shape (say a Triangle) then I can also call area just on that. But it is horrendous. I don't like the code at all (indeed I'm sure it would be much shorter in any object oriented programming language).
So where have I gone wrong? How can this be made nicer? Saying "don't think in terms of objects" is nice, but this seems to have several applications (e.g. a list of characters in a role playing game ... who have some shared attributes but different abilities, or GUI programming where objects tend to make sense).
You can use simple data types for this purpose without resorting to typeclasses. If you do want to use typeclasses, it's better to use it to describe a conversion to your base type rather than having it include all the implementation details:
data Point = Point
{ xcoord :: Float
, ycoord :: Float
} deriving (Eq, Read, Show)
data Shape = Shape
{ shapeLocation :: Point
, shapeArea :: Float
} deriving (Eq, Show)
This might be the only two types you need, depending on your application, since you could write functions
circle :: Point -> Float -> Shape
circle loc radius = Shape loc $ pi * r * r
square :: Point -> Float -> Shape
square loc sLength = Shape loc $ sLength * sLength
triangle :: Point -> Float -> Float -> Shape
triangle loc base height = Shape loc $ 0.5 * base * height
But maybe you want to preserve those arguments. In which case, write a data type for each
data Circle = Circle
{ cLocation :: Point
, cRadius :: Float
} deriving (Eq, Show)
data Square = Square
{ sLocation :: Point
, sLength :: Float
} deriving (Eq, Show)
data Triangle = Triangle
{ tLocation :: Point
, tBase :: Float
, tHeight :: Float
} deriving (Eq, Show)
Then for convenience, I'd use a typeclass here to define toShape:
class IsShape s where
toShape :: s -> Shape
instance IsShape Shape where
toShape = id
instance IsShape Circle where
toShape (Circle loc radius) = Shape loc $ pi * radius * radius
instance IsShape Square where
toShape (Square loc sideLength) = Shape loc $ sideLength * sideLength
instance IsShape Triangle where
toShape (Triangle loc base height) = Shape loc $ 0.5 * base * height
But now there's the problem that you have to convert each type to Shape in order to get its area or location in a more generic way, except you can just add the functions
location :: IsShape s => s -> Point
location = shapeLocation . toShape
area :: IsShape s => s -> Float
area = shapeArea . toShape
I would keep these out of the IsShape class so that they can't be re-implemented, this is similar to functions like replicateM that work on all Monads, but aren't part of the Monad typeclass. Now you can write code like
twiceArea :: IsShape s => s -> Float
twiceArea = (2 *) . area
And this is fine when you're only operating on a single shape argument. If you want to operate on a collection of them:
totalArea :: IsShape s => [s] -> Float
totalArea = sum . map area
So that you don't have to rely on existentials to build a collection of them you can instead have
> let p = Point 0 0
> totalArea [toShape $ Circle p 5, toShape $ Square p 10, toShape $ Triangle p 10 20]
278.53983
> totalArea $ map (Square p) [1..10]
385.0
This gives you the flexibility to work on a list of objects of different types, or on a list of just a single type using the same function and absolutely no language extensions.
Bear in mind that this is still trying to implement a sort of object model in a strictly functional language, something that isn't going to be completely ideal, but considering this allows you to have
multiple "interfaces" (conversions to different types)
generics (totalArea :: IsShape s => [s] -> Float)
sealed methods if you were to use a smart constructor for Shape and add more methods to it then alias them like with area and location
unsealed methods if you just allowed those to be set by the smart constructor
public and private are set by module exports
and probably some other OOP paradigms, all with really less code than it would take in Java or C#, the only difference is that the code isn't all grouped together. This has it's benefits and disadvantages, such as being able to define new instances and data types more freely, but making the code somewhat more difficult to navigate.
You can use existential quantification for such purposes:
{-# LANGUAGE ExistentialQuantification #-}
data Point = Point {
xcoord :: Float
, ycoord :: Float
} deriving (Read, Show)
data Circle = Circle {
cLocation :: Point
, cRadius :: Float
} deriving (Read, Show)
data Square = Square {
sLocation :: Point
, sLength :: Float
} deriving (Read, Show)
data Triangle = Triangle {
tLocation :: Point
, tSide1 :: Float
, tSide2 :: Float
} deriving (Read, Show)
class ShapeIf a where
location :: a -> Point
area :: a -> Float
instance ShapeIf Square where
location = sLocation
area a = (sLength a) ^ 2
instance ShapeIf Circle where
location = cLocation
area a = pi * (cRadius a) ^ 2
instance ShapeIf Triangle where
location = tLocation
area a = 0.5 * (tSide1 a) * (tSide2 a)
data Shape = forall a. ShapeIf a => Shape a
instance ShapeIf Shape where
location (Shape s) = location s
area (Shape s) = area s
p = Point 0 0
shlist :: [Shape]
shlist = [Shape (Square p 0), Shape (Circle p 1), Shape (Triangle p 2 3)]
main = print $ map area shlist
But note, that there are no downcasts in Haskell, so it's not a direct analogue to Java-style subtyping. Have a look at this also.
After being pointed to this blog post about existential quantification being an anti-pattern (which I had reinvented in a slightly clumsier way), I had a try at a rewrite and came up with:
module Shapes (Shape(), Point, Circle(..), Triangle(..), Square(..), location, area) where
data Point = Point {
xcoord :: Float
, ycoord :: Float
} deriving (Read, Show)
data Shape = Shape {
location :: Point
, shape :: ShapeT
}
data ShapeT = CircleT Circle | PolygonT Polygon deriving (Read, Show)
data Circle = Circle {
cRadius :: Float
} deriving (Read, Show)
data Polygon = SquareT Square | TriangleT Triangle deriving (Read, Show)
data Square = Square {
sLength :: Float
} deriving (Read, Show)
-- only right angled triangles for ease of implementation!
data Triangle = Triangle {
tSide1 :: Float
, tSide2 :: Float
} deriving (Read, Show)
square :: Point -> Float -> Shape
square p l = Shape p (PolygonT $ SquareT (Square l))
circle :: Point -> Float -> Shape
circle p r = Shape p (CircleT (Circle r))
triangle :: Point -> Float -> Float -> Shape
triangle p s1 s2 = Shape p (PolygonT $ TriangleT (Triangle s1 s2))
area :: Shape -> Float
area = area' . shape
area' (PolygonT (SquareT (a))) = (sLength a) ^ 2
area' (CircleT (a)) = pi * (cRadius a) ^ 2
area' (PolygonT (TriangleT (a))) = 0.5 * (tSide1 a) * (tSide2 a)
You can get madder.
Analysed in Haskell terms, declaring a Java-style class does a number of things:
Declares the existence of a set of types that share a common interface, requiring that all members of the set are also members of all the base classes' associated type sets
Declares a concrete record type
Declares the new data type is a member of the new set of types, and all the base classes' associated sets
Declares an existential type, able to hold any concrete type that is a member of the new set of types
Whew. Features like interfaces, final classes, etc, basically allow you to skip parts of that list if you don't need/want the whole bundle. And on top of that, Java-style classes also provide a module system, which I'm not going to address at all.
Seen this way, you can get all of the above in Haskell if you use an "OO design pattern" to implement each of them yourself. But in a language like Java there's a lot of help that is provided by the language, which would manifest in Haskell as sensible defaults and syntactic sugar if it were present. An example is inheritance, which is basically automatic containment of superclass records within subclass records and automatic delegation to superclass implementations from subclass implementations. Haskell will give you none of this help, so everything must be explicit and the "OO design pattern" comes out incredibly verbose.
Part 1 is pretty easy to see; a set of types sharing a common interface is what a type class is. Haskell allows us to put superclass constraints on the new type class too. Done.
Part 2 is also straightforward; just declare a new data type holding all the member variables. Note that if you intend to be able to "inherit" from this "class" and use the same assessors to get member variables out, you'll want to have those as part of the type class, not just use Haskell's record syntax to declare them for you. And if you're "inheriting" from other "OO pattern" classes, you'll want to included their data types as members of your new data type.
Part 3 is where the lack of help from the language starts to get tedious. You need to implement instances for each type class implied by the OO inheritance hierarchy, going all the way up (i.e. not just the immediate bases). If you're not overriding "methods" then this will be extremely mechanical and tedious, because you can just delegate all the "inherited" methods to the contained member data of the base classes (which should already have all the needed instances if you're following the pattern). This is manually implementing what OO inheritance defaults for you.
Part 4 is the doozy. OO programmers are masters of existentially quantified types, they just don't know it. Haskell supports existentially quantified types, but only through extensions, and a little awkwardly. And the language, idioms, and libraries aren't really expecting you to make really heavy use of existential types, so you'll start to experience a lot of friction using them; mostly in the form of annoying type errors that go away when you manage to figure out the correct type to write explicitly, and occasionally you'll need to explicitly eta expand (i.e. turn f = foo into f x = foo x, where the logic of higher order functions should say that makes no difference).
You might think that we shouldn't need the existential types, since type-class-constrained type variables should be enough to allow code to work on any member of the type class. The trouble is that a type variable constrained by a type class must be instantiated at each call to any one type in the type class (and the choice is made by the caller, not by whatever data happens to arrive at runtime).
This is why type classes don't allow you to use heterogenous lists; although the type Shape a => [a] can hold objects of any type that implements Shape, there is only one single type variable for all the elements of the list, so they all must be the same "any type that implements Shape". An existential type is a wrapper that contains data with a type variable but where the wrapper does not itself have that type variable in its own type. This allows you to just have a list of [Shape], where it's Shape that internally contains a ShapeI a => a.
I think I've exhausted how well I can explain this without example code, so here goes. Warning, it's pretty ugly:
{-# LANGUAGE ExistentialQuantification, GADTs, RankNTypes #-}
newtype Point = Point (Double, Double)
deriving (Show, Eq)
-- The Shape common interface
-- Shape is just an interface, so no member data type
class ShapeI a
where area :: a -> Double
-- The Shape existential reference
data Shape
where Shape :: ShapeI a => a -> Shape
-- The Polygon common interface: 'subtype' of Shape
-- Polygon is just an interface, so no member data type
class ShapeI a => PolygonI a
where vertexes :: a -> [Point]
-- The Polygon existential reference
data Polygon
where Polygon :: PolygonI a => a -> Polygon
-- The Circle common interface
class ShapeI a => CircleI a
where centre :: a -> Point
radius :: a -> Double
-- The Circle existential reference
data Circle
where Circle :: CircleI a => a -> Circle
-- The Circle member data type
data CircleM = CircleM Point Double
deriving (Show, Eq)
-- Circles are Shapes
instance ShapeI CircleM
where area (CircleM _ r) = pi * r * r
-- Circles are Circles
instance CircleI CircleM
where centre (CircleM c _) = c
radius (CircleM _ r) = r
data Colour = Med | Blue
deriving (Show, Eq)
-- The ColouredCircle member data type
-- ColouredCircle is final, so not bothering with a type class or existential reference
data CircleColouredM = CircleColouredM CircleM Colour
deriving (Show, Eq)
-- ColouredCircles are Shapes
instance ShapeI CircleColouredM
where area (CircleColouredM circle _) = area circle
-- ColouredCircles are Circles
-- Note there is no actual implementation logic here, ColouredCircleM implements
-- the Circle methods purely by using Circle's implementations
instance CircleI CircleColouredM
where centre (CircleColouredM circle _) = centre circle
radius (CircleColouredM circle _) = radius circle
-- The Triangle member data type
-- Triangle is final, so not bothering with a type class or existential refernce
data TriangleM = TriangleM Point Point Point
deriving (Show, Eq)
instance ShapeI TriangleM
where area = const 7 -- In this hypothetical universe, all triangles have area 7
instance PolygonI TriangleM
where vertexes (TriangleM a b c) = [a, b, c]
Given all that:
-- Heterogenous list of different types of objects which are all Circles
circles :: [Circle]
circles = [Circle (CircleM (Point (3, 7)) 2), Circle (CircleColouredM (CircleM (Point (8, 1)) 1) Blue)]
-- Casts a Circle existential to a Shape existential
-- Note that the object *indside* the existential reference is the same; we're
-- just treating it as a Shape now
circleToShape :: Circle -> Shape
circleToShape (Circle c) = Shape c
-- Heterogenous list of different types of objects which are all Shapes
-- Note we are able to uniformly cast the circles list to shapes in order store
-- them in this list, even though they're not all the same type already
shapes :: [Shape]
shapes = [Shape (TriangleM (Point (0, 0)) (Point (10, 0)) (Point (0, 10)))] ++ map circleToShape circles
-- Helper function; can apply any function that is polymorphic in ShapeI to a
-- Shape existential; the explicit type is necessary, because it's a rank 2 type
apply :: (forall a. ShapeI a => a -> b) -> Shape -> b
apply f (Shape x) = f x
areas = map (apply area) shapes
So you see we do get heterogenous lists (or in general, types which can independently hold any member of a "class" and allow access to that class' common interface), OO style inheritance hierarchies (though with manual boilerplate to inherit methods unchanged), and even upcasting.
Another issue you'll likely run into is how strict Haskell is about type discipline. You won't be able to downcast; in fact you won't be able to refer to any property of a Shape existential other than what's implied by the ShapeI interface; all knowledge about what it particularly contains is gone.
This also means that the shapes list is close to useless; the only meaningful thing we can do with it is map (apply area) shapes, so we might as well have done away with the masses of boilerplate and just created a list of Double in the first place. Also, the root class in OO languages tends to provide a surprising amount of functionality; you can toString arbitrary objects in Java, compare them for equality, etc. You get none of that here. Once something's an existential reference, you can access nothing but what its constraints say you can. No Show constraint, no show method (even though all of the types I've used here do implement Show). Likewise, no Eq constraint, no == function; and that probably wouldn't work as you'd like here because (being an idiomatic Haskell function and not expecting to deal with existentials emulating OO class heirarchies) == only works on two values guaranteed to be of the same type, and the existential reference gives up all knowledge about being of any particular type so you could never guarantee that.
I'm certain you could refine the pattern above to make it more usable, maybe even automate bits of it (can we write a generic upcast function? can TemplateHaskell generate the boilerplate for us?). If you threw in constraints like Typeable into the mix you should even be able to get runtime-checked downcasts if you really want, and might be able to implement an equality operator that worked (returning False for different concrete types and delegating to == when the types do match). But personally I'm not terribly inclined to try to flesh this out further.
TLDR: OO style classes (ignoring mutations) are basically a particular combination of type classes, types holding member data, existential types, with a whole lot of default machinery to make it work easily instead of being a huge pain. Given that Haskell gives you each of those pieces as orthogonal minimal concepts, I find it much easier to know and understand those concepts separately and apply them individually or in concert as they are needed, rather than to take OO's swiss-army-knife approach and try to force every program to fit the facilities provided by that structure.
Related
The List type in Lean 4's prelude has a lot of nice goodies implemented, e.g. List.map, List.join, etc.
A classic example in dependently-typed languages is Vector a n where a is the type of the elements of the container, and n is the length. This allows you to do nice things like write a function concat (u : Vector a m) (v : Vector a n) : Vector a (m + n) whose type signature guarantees that the function returns a vector of the expected length.
Following a comment on a previous answer I'd like to write a Vector type as a Subtype of List, as a first step towards getting these methods for a sized container. But I'm new to Lean, and I don't exactly understand how this works. Subtype takes a predicate a -> Prop, which appears to work a bit like a filter on the parent type, which in this case is a := List. But all lists are valid vectors, so it doesn't seem like this is much use. The predicate should always return true, no?
My question is:
How can I use Subtype to get a subtype of List whose length is encoded in its type? And as a follow-up, how can I construct an element of this type from an existing List?
You're right that all lists are valid vectors, but the predicate is exactly how to specify that the Vector has a certain number of elements.
def Vec (n : Nat) (a : Type) := { ls : List a // ls.length = n }
def myVec : Vec 3 Nat := ⟨[1, 2, 3], rfl⟩
Let's say we have a bunch of triangles 1,2,...,N, and a line segment. By making a tree, I want to get the intersection of the line segment with triangles and the index of the triangle that intersects the line segment (there is only one). How I call it from "intersection" below?
Thanks.
for (int i=0; i<NumTriangles; i++) {
Point a(Triangles[i].vert1[0], Triangles[i].vert1[1], Triangles[i].vert1[2]);
Point b(Triangles[i].vert2[0], Triangles[i].vert2[1], Triangles[i].vert2[2]);
Point c(Triangles[i].vert3[0], Triangles[i].vert3[1], Triangles[i].vert3[2]);
triangles.push_back(Triangle(a,b,c));
}
Tree tree(triangles.begin(),triangles.end());
Point a(0,0,0);
Point a(0,0,1);
Segment segment_query(a,b);
Segment_intersection intersection = tree.any_intersection(segment_query);
Each triangle you put in the three is called a primitive. This primitive has a unique identifier (also known as ID) and a geometry (datum, here a triangle).
The type of the primitive ID is defined by the template parameter of the class CGAL::AABB_primitive. If you are using the CGAL::AABB_triangle_primitive, which is just a simplified API around CGAL:AABB_primitive, then the ID is an iterator referring to the primitive within the range triangles.
The function any_intersection() returns an optional object which is a pair that contains both the geometry of the intersection (as first) and the intersected primitive (as second), from which you can get the ID with the id() function.
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
How to use polymorphism in functional programming (with dynamic type system)?
Let's consider following example (first in OOP second in FP). The program is very simple - there are list of figures and we need to draw all of them, different figures use different drawing algorithms.
In OOP it can be done trivially, but how to do it in FP? Especially in languages with dynamic type system, like Scheme, Clojure (without static type resolving at compile time)?
I created simple code ( live version http://tinkerbin.com/0C3y8D9Z , press 'Run' button ). I used if/else switch in FP sample, but it's a very bad approach. How such problem can be solved better?
Samples are in JavaScript, but it's only for purpose of simplicity, it would be interesting to see a solution in any functional language with dynamic typing system.
OOP
var print = function(message){document.write(message + "\n<br/>")}
// Object Oriented Approach.
var circle = {
draw: function(){print("drawing circle ...")}
}
var rectangle = {
draw: function(){print("drawing rectangle ...")}
}
var objects = [circle, rectangle]
objects.forEach(function(o){
o.draw()
})
FP
var print = function(message){document.write(message + "\n<br/>")}
// Functional Approach.
var circle = {type: 'Circle'}
var drawCircle = function(){print("drawing circle ...")}
var rectangle = {type: 'Rectangle'}
var drawRectangle = function(){print("drawing rectangle ...")}
var objects = [circle, rectangle]
objects.forEach(function(o){
if(o.type == 'Circle') drawCircle(o)
else if(o.type == 'Rectangle') drawRectangle(o)
else throw new Error('unknown type!')
})
Your "FP" version is not what I consider the idiomatic FP example. In FP, you often use variants and pattern matching where in OOP you'd use classes and method dispatch. In particular, you only have one draw function that already does the dispatch internally:
var circle = {type: 'Circle'}
var rectangle = {type: 'Rectangle'}
var draw = function(shape) {
switch (shape.type) {
case 'Circle': print("drawing circle ..."); break
case 'Rectangle': print("drawing rectangle ..."); break
}
}
var objects = [circle, rectangle]
objects.forEach(draw)
(Of course, that is JavaScript. In a functional language you typically have much more elegant and concise syntax for this, e.g.:
draw `Circle = print "drawing circle..."
draw `Rectangle = print "drawing rectangle..."
objects = [`Circle, `Rectangle]
foreach draw objects
)
Now, the average OO aficionado will see the above code and say: "But the OO solution is extensible, the above is not!" That's true in the sense that you can easily add new shapes to the OO version and don't have to touch any of the existing ones (or their draw functions) when you do. With the FP way, you'd have to go in and extend the draw function, and all other operations that may exist.
But what those people fail to see is that the converse is also true: the FP solution is extensible in a way the OO one isn't! Namely, when you add a new operation over your existing shapes then you don't need to touch any of the shape definitions, nor existing operations. You simply add another function, whereas with OO, you have to go and modify every class or constructor to include an implementation for the new operation.
That is, there is a dualism here in terms of modularity. The ideal, achieving simultaneous extensibility along both axes is known in literature as "the expression problem", and while various solutions exist (especially in functional languages), they are typically more complex. Hence, in practice you'll often want to decide for one dimension, depending on which dimension is more likely to matter for the problem at hand.
There are other advantages to the functional version. E.g it trivially scales to multi-dispatch or more complex case distinctions. It also is preferable when implementing an algorithm that is complicated and where the different cases are interrelated, so that you want to have the code in one place. As a rule of thumb, whenever you start using the visitor pattern in OO, a functional-style solution would have been more appropriate (and far, far easier).
Some further remarks:
This different preference in program organisation isn't the central idea of FP. What matters more is discouraging mutable state, and encouraging highly reusable higher-order abstractions.
The OO community has this habit of inventing new (buzz)words for every old idea. Its use of the term "polymorphism" (which is completely different from what it means elsewhere) is one such example. It says little more than being able to call functions without statically knowing what the callee is. You can do that in any language where functions are first-class values. In that sense, your OO solution is perfectly functional as well.
Your question has very little to do with types. Both the idiomatic OO and the idiomatic FP solution work in an untyped or typed language.
OO polymorphism is not a part of functional programming. However some functional languages (e.g. clojure) have oo polymorphism.
Another kind of polymorphism is multimethods
(def circle {:type :circle
:radius 50})
(def rectangle {:type :rectangle
:width 5
:height 10})
(defmulti draw :type)
(defmethod draw :circle [object]
(println "circle: radius = " (:radius object)))
(defmethod draw :rectangle [object]
(println "rectangle: "
"width = " (:width object)
"height = " (:height object)))
(doseq [o [rectangle circle]] (draw o))
=> rectangle: width = 5 height = 10
circle: radius = 50
Or you just can use functional style
(defn circle [] (println "drawing circle ..."))
(defn rectangle [] (println "drawing rectangle ..."))
(def objects [circle rectangle])
(doseq [o objects] (o))
=> drawing circle ...
drawing rectangle ...
In Clojure there are Protocols which provide basically the same ad-hoc polymorphism as Haskell's type classes:
(defprotocol shape (draw [e]))
(defrecord circle [radius])
(defrecord rectangle [w h])
(extend-protocol shape
circle (draw [_] "I am a nice circle")
rectangle (draw [_] "Can I haz cornerz please?"))
You can also extend an existing type:
(extend-protocol shape
String (draw [_] "I am not a shape, but who cares?"))
And then you can apply the draw method to some instances
user=> (map draw [(->circle 1) (->rectangle 4 2) "foo"])
("I am a nice circle" "Can I haz cornerz please?" "I am not a shape, but who cares?")
There really isn't anything non-functional about your first code sample. Even in languages that have no support for object orientation, you can do the same thing. That is you can create a record/structure/map that contains functions and then put those into your list.
In your simple example where there's only one function, you could also just create a list of functions directly, like objects = [drawCircle, drawRectangle].
In several languages designed primarily for functional programming, there are ways to achieve (ad-hoc, as it is called) polymorphism, though they differ from what you call polymorphism. Haskell, for example, has type classes (not to be confused with classes from classical OOP):
class Draw a where
draw :: a -> SomethingSomthing -- probably IO () for your example, btw
(Scala has objects, and also implicits which apparently parallel or even surpass type classes.)
You can then implement any number of independent types, and make each an instance of the type class (again independently, e.g. in an entirely different module):
data Circle = Circle Point Double -- center, radius
data Rectangle = Rect Point Double Double -- center, height, width
instance Draw Circle where
draw (Circle center radius) = …
instance Draw Rectangle where
draw (Rect center height width) = …
This is probably what you'd use in Haskell, if you actually needed that degree of extensibility. If you have a finite number of cases belonging together (i.e. you could use sealed classes in the OOP alternative), you'd likely use algebraic data types (see below).
Another way is to do just what your JS snippet does (which is, by the way, not what you'd do to achieve polymorphism if you had any number of objects of each type, and this version has the same problem): Embed a function that does the polymorphic behavior in each object. In a sense, your "OOP" snippet is already functional.
data Drawable = Drawable (Drawable -> SomethingSomething) {- other fields -}
draw (Drawable draw) = draw Drawable
Though in a static language, this does not permit different objects to have different attributes.
A more bearable alternative to the bunch of conditions you present, but nevertheless similar and with the same limitation (it's hard to add another shape), is pattern matching with algebraic data types. Other answers on Stackoverflow have explained these well, I'll just give this concrete example in that style:
data Shape = Circle {- see second snippet -}
| Rect {- ditto -}
draw (Circle center radius) = …
draw (Rect center height width) = …
Related:
Lazy datatypes in Objective C
From the related question I was able to figure out how to use block objects to mimic suspended computation, but I am still trying to grasp the concept. For a horribleComputation it would work, but how would one model an infinite stream ?
How it is normally done in SML,
(* Have a datatype to wrap a computation *)
datatype 'a susp = Susp of (unit -> 'a)
(* Create a recursive datatype to handle an infinite stream *)
datatype 'a stream = Cons of 'a * ('a stream') susp
type 'a stream = ('a stream') susp
Now in Objective-C there is typedef which takes predefined values
enum suit {hearts, spades, diamonds, clubs};
I just cannot figure out how to get the Cons of part
For now, if infinite data modelling is not possible, how would one model for example a Hand of cards. Again in SML,
datatype suit = Clubs | Spades | Hearts | Diamonds
datatype rank = Two | Four | Five (*... etc *)
(* Then a card *)
type card = rank*suit
(* And now we can have a Hand :) *)
datatype hand = Empty | Hand of card * hand;
Most likely everything is not transferable, but I am just curious how well I can use Objective C for lazy programming ... such as processing all natural numbers on demand. I have been making circles with my search for this.
There are two separate, orthogonal concepts: recursive datatypes and lazy computations. In C and C-like languages, you model the former with a struct that contains pointer(s) to either itself, or to other data type that contains/points to that struct directly or indirectly. Use block objects or whatever to suspend your computations. Combine the two concepts together and get a struct that contains a pointer to a suspended computation that returns (a pointer to) that struct.
I'm writing an API for creating geometric shapes, and I'm running into some difficulties naming my methods.
Let's take a simple case: Creating a circle. Most of us might be familiar with a method like graphics.drawEllipse(x, y, w, h). To draw a circle, you need to know the top left coordinate, and the width and height of the circle.
My API is intended to make it easy for a developer to draw shapes using a variety of information, without doing a lot of math - which is trivial for circles, but more complicated for other shapes. For example, you should also be able to draw a circle given its center coordinates and radius, or the top left and bottom right coordinates.
So I have a Circle class with factory methods like:
Circle.createWithCenterAndRadius(cx, cy, r)
Circle.createWithBoundingBox(x1, y1, x2, y2)
Circle.createWithWidthAndHeight(x, y, w, h)
I feel like there might be a "code smell" here, but I'm not sure. On the one hand, these factory methods are necessarily descriptive. On the other hand, I can forsee these method names getting out of control. For example, how would I name a Triangle factory method that creates a triangle given a point, the length of one side, an angle, and the length of another side? Triangle.createWithPointSideAngleAndSide(x, y, side1, angle, side2)? Is that just evil?
If you were to use this API, would method names like this be okay to you? Do you have advice on how I can make the method names more sane?
You might change your circle methods to
Circle.FromCenterAndRadius(...)
Circle.FromBoundingBox(...)
Circle.FromWidthAndHeight(...)
It implies that you're creating circles from their different representations in a kind of concise way...
It is ok in any language that doesn't support named parameters. If the language supports named parameters, I like more the short Create and just have obvious parameters names.
For a language with named parameters, you would:
Circle.Create(
centerX = cx,
centerY = cy,
radius = r
);
Another more involved option, would be a fluent interface like (but that is probably too much):
circleBuilder.Center(cx,cy).Radius(r)
circleBuilder.Center(x,y).Width(w).Height(y)
circleBuilder.BoundWith().Left(x1,y1).Right(x2,y2)
Center returns an instance of an intermediate class that only allows Radius or Width. And BoundWith returns one that only allows Left.
I think there is nothing wrong with your descriptive methods - they are the compact and describe exactly what's going on. The users of the library will have no doubt about the function of your methods, neither the maintanance programmers.
You could also apply some design pattern here if you are really worried about exposing a large number of factory methods - like having factory methods with property classes. You could have a CircleProperties class with properties like CenterX, CenterY, Radius, (bool)UseCenterX, (bool)UseCenterY etc and then you pass this to the public factory method which will figure out which (private) factory method to use.
Assuming C#:
var circleProperties = new CircleProperties()
{
CenterX = 10,
CenterY = -5,
Radius = 8,
UseCenterX = true,
UseCenterY = true,
UseCenterRadius = true
};
var circle = Circle.Create(circleProperties);
My first instinct is to have more types, which would allow for more intuitive method overloading.
// instead of Circle.createWithCenterAndRadius(cx, cy, r)
Circle.create( new Point(cx,xy), r);
// instead of Circle.createWithBoundingBox(x1, y1, x2, y2)
Circle.create( new Point(x1,y1), new Point(x1,y1) );
// or even...
Circle.create( new Box(p1,p2));
// instead of Circle.createWithWidthAndHeight(x, y, w, h)
Circle.create( new Point(x,y), w, h);
As well as Point, you could define Distance (which would allow for different units)
If this style suits you, consider why you need a factory method instead of a constructor.
Circle c = new Circle(new Point(cx,xy), r);
For languages that don't support named parameters, would it be cleaner to make the method name something very simple like Circle.create and then just add an additional input flag string (like "center" or "bounding") that indicated how the input values should be interpreted for cases that are hard to discriminate based only on input variable number and type? Drawbacks to this would be that it requires extra logic inside of the method to handle different types of input arguments and also requires that the user remember the flag options.
I would have methods CreateTriangle and have the overloads show the different pieces of information required.
E.g.
Circle.CreateCircle(cx, cy, r)
Circle.CreateCircle(point1, point2)
Circle.CreateCircle(point, width, height)
Yes, this is more of a meta-answer, but I suggest you take a peek at how naming is done in Apple's Cocoa.
Your instinct is correct--the entire pattern of creating things this way is--iffy.
Unless these are used just once or twice, they are going to become pretty messy. If you were creating a shape with 5 circles and 3 triangles, it would be a mess.
Anything beyond a trivial example would probably be best done with some kind of data-driven implementation.
Towards those ends, having it take a string, hash or XML to define your shapes might be extremely useful.
But it all depends on how you expect them to be used.
I have the same kind of issues with creating Swing controls in Java. You end up with line after line of "new Button()" followed by a bunch of .set property calls as well as a line of code to copy the value to an object (or add a listener), and a line to reset the value..
That kind of boilerplate should never happen in code, so I usually try to find a way to drive it with data, binding the controls to objects dynamically--and towards that end, a descriptive string-based language would be very helpful.
I know, I know. This sounds completely crazy for you C/C++/Java people, but the examples given in the question and in all those answers clearly demonstrate what a bad, bad convention CamelCaseNaming really is.
Let's take another look at the original example:
Circle.createWithCenterAndRadius(cx, cy, r)
Circle.createWithBoundingBox(x1, y1, x2, y2)
Circle.createWithWidthAndHeight(x, y, w, h)
And now let's get rid of that camel case notation
Circle.create_with_center_and_radius(cx, cy, r)
Circle.create_with_bounding_box(x1, y1, x2, y2)
Circle.create_with_width_and_height(x, y, w, h)
This may seem terribly unfamilar, but be honest: which version is easier to read?