I just go strict. Never use Any unless I absolutely must. Hunting down the types is just the price you pay for what is basically a huge reduction in hidden runtime errors.
f(a, b)
f'((a, b))
It's just syntactic sugar that causes a difference to occur. f(a, b) -> c
f'(Tuple[a, b]) -> c
g(d) -> Tuple[a, b]
Both f' and f are equivalent in theory. Plus you can compose g with f'. f :: int -> int
f x = (x + 1) * 3 * 2
main = print (f 2)
The above code is your typical functional program. Coded in a way that's not apparently re-useable at first. However, I look at this function f and I realize I can easily begin refactoring it piece by piece into something more re-useable / lego-like. Take "* 2" let's extract that first into a function called g: g :: int -> int
g x = x * 2
f :: int -> int
f x = g ((x + 1) * 3)
main = print (f 2)
Then see "* 3" it's very easy to see how you can extract that into another function called d: g :: int -> int
g x = x * 2
d :: int -> int
d x = x * 3
f :: int -> int
f x = d (g (x + 1))
main = print (f 2)
Then see "+ 1". Again easily extracted into a new function called m. g :: int -> int
g x = x * 2
d :: int -> int
d x = x * 3
m :: int -> int
m x = x + 1
f :: int -> int
f x = d (g (m x))
main = print (f 2)
Pretty re-useable right? Let's rewrite the function "f" so that this becomes more apparent using the point free style: g :: int -> int
g x = x * 2
d :: int -> int
d x = x * 3
m :: int -> int
m x = x + 1
f :: int -> int
f = d . g . m
main = print (f 2)
Note that now it becomes obvious. The function f is a composition of d and g and m. It fits to together like legos. Now let us consider the same thing but with unix pipes. How would that work? Imagine you have three programs each one takes an input from stdin does a mathematical operation in them and throws the result to stdout. Each of these programs is named after the functions shown above and given the same mathematical operations as defined above: g, d, and m. What does the unix expression look like? $> echo 2 | m | g | d
So you see I've given you a very primitive example using very basic mathematical operations to show you how easily it is to convert functional code into unix-like code which in turn is very lego-like. class F:
def __ init__(self, x)
self.x = x
def addOne(self)
self.x += 1
def mulTwo(self)
self.x *= 2
def mulThree(self)
self.x *= 3
f = F(2)
f.addOne()
f.mulTwo()
f.mulThree()
print (f.x)
The act of mutation ties methods to context and makes it so methods cannot be reused outside of a context. None of those methods can be reused outside of the context of F. If you want to make OOP into lego building blocks you're going to have to divide things up into smaller classes. Which is not nearly as straightforward as the functional program above. class F:
def __ init__(self, x)
self.x = x
def addOne(self)
self.x += 1
def mulThree(self)
self.x *= 3
class G:
def __ init__(self, x)
self.x = x
def mulTwo(self)
self.x *= 2
f = F(g)
f.addOne()
f.mulThree()
g = G(f.x)
g.mulTwo()
print(g.x)
Now for mulThree and addOne: class F:
def __ init__(self, x)
self.x = x
def addOne(self)
self.x += 1
class G:
def __ init__(self, x)
self.x = x
def mulTwo(self)
self.x *= 2
class D:
def __ init__(self, x)
self.x = x
def mulThree(self)
self.x *= 2
f = F(g)
f.addOne()
g = G(f.x)
g.mulTwo()
d = D(g.x)
d.mulThree()
print(d.x)
You will note that I'm reusing the constructor, How can that be factored out? Inheritance. class Base:
def __ init__(self, x)
self.x = x
class F(Base):
def addOne(self)
self.x += 1
class G(Base):
def mulTwo(self)
self.x *= 2
class D(Base):
def mulThree(self)
self.x *= 2
f = F(g)
f.addOne()
g = G(f.x)
g.mulTwo()
d = D(g.x)
d.mulThree()
print(d.x)
So let's look at things from a birds eye view. With the functional style I was able to refactor to 3 primitives all of which can be reused and composed. class F:
def addOne(x):
return x + 1
Which is heading in a more functional direction as the class just becomes useless sugar around a method that ignores internal state and is basically a function.