I have read a book called Clean code
. One of the strongest messages that I took from the book is that the code must be readable. I do not understand why functional languages such as F# do not include function parameter names into the intellisense or in the type defintion?
Compare
val copy: string -> string -> unit
with
val copy: sourceFile:string -> destinationFile:string -> unit
What is the best practise in the functional world? Shall we prefer single parameter functions? This is what Clean code
promotes. Or shall we use records for all functions of 2+ parameters?
I know that one work-around is to use static member
instead of let
functions but this is not a functional approach, is it?
EDIT:
Just to give more information:
addThree :: Int -> Int -> Int -> Int
Unix.unlink: (string -> unit)
and surely others. They just do not show parameter names in the type definitions.
The parameters, in a function call, are the function's arguments.
A parameter is a special kind of variable used in a function to refer to one of the pieces of data provided as input to the function. These pieces of data are the values of the arguments with which the function is going to be called/invoked.
Functional programming languages are specially designed to handle symbolic computation and list processing applications. Functional programming is based on mathematical functions. Some of the popular functional programming languages include: Lisp, Python, Erlang, Haskell, Clojure, etc.
The variables used in the function definition as parameters are known as formal parameters. The constants, variables, or expressions used in the function call are known as actual parameters. Types: Default arguments and Constant Arguments. Concept: User-defined Functions.
If you practice typeful programming, which is the idea that a lot of the semantic content of programs can be reflected statically in the type system, you will find out that in many (but not all) cases, named arguments are not necessary for readability.
Consider the following examples in the List
standard library of OCaml. By knowing that they operate on lists, and with the (hopefully clear: we're all for good name choices) name of the function, you will find that you don't need explanations for what the arguments do.
val append : α list -> α list
val flatten : α list list -> α list
val exists: (α -> α bool) -> α list -> bool
val map: (α -> β) -> α list -> β list
val combine : α list -> β list -> (α * β) list
Note that the last example is interesting because it is not exactly clear what the code will do. There would in fact be several interpretations. combine [1;2] [3;4]
returns [(1,3); (2,4)]
and not, for example, [(1,3); (1,4); (2,3); (2,4)]
. It is also not clear what happens when the two lists are not of the same length (the failure mode is unclear).
Functions that are not total, that may raise an exception or not terminate, usually need more documentation about what the failure cases are and how they will behave. This is one strong argument in favor of what we call pure programming, where all the behavior of a function is expressed in terms of returning a value (no exceptions, observable state mutation, or non-termination), and can therefore be statically captured by the type system.
Of course this only works really well for functions that are parametric enough; they have a type that make it very clear what they do. This is not the case of all functions, consider for example the blit
function of the String
module (I'm sure your favorite language has such examples as well):
val blit : string -> int -> string -> int -> int -> unit
huh?
Programming languages add support named parameters for this reason. In OCaml for example we have "labels" that allow to name parameters. The same function is exported in the StringLabels
module as:
val blit : src:string -> src_pos:int -> dst:string -> dst_pos:int -> len:int -> unit
That's better. Yes, named parameters are useful in some cases.
Note however that named arguments can be used to hide bad API design (maybe the example above is targe to this criticism as well). Consider:
val add : float -> float -> float -> float -> float -> float -> float * float * float
obscure, huh? But then:
type coord = {x:float; y:float; z:float}
val add : coord -> coord -> coord
That's much better, and I didn't need any parameter labeling (arguably record labels provide naming, but in fact I could equally use float * float * float
here; the fact that value records may subsume named (and optionals?) parameters is also an interesting remark).
David M. Barbour develops the argument that named parameters are a "crutch" of language design, that is used to tamper over the lazyness of API designers, and that not having them encourages better design. I am not sure I agree that named parameters can be profitably avoided in all situations, but he certainly has a point that agrees with the typeful propaganda at the beginning of my post. By raising the level of abstraction (through more polymorphic/parametric types or better problem domain abstractions), you'll find you decrease the need for parameter naming.
Haskell's type synonyms can help in making the type signatures more self-documenting. Consider for example the function writeFile
, that just writes a string to a file. It has two parameters: the string to write, and the filename to write to. Or was it the other way around? Both parameters are of type String
, so it's not easy to tell which is which!
However, when you look at the documentation, you see the following type signature:
writeFile :: FilePath -> String -> IO ()
That makes it clear (to me, at least!) how the function is intended to be used. Now, since FilePath
is just a synonym for String
, there's nothing preventing you from using it like this:
writeFile "The quick brown fox jumped over the lazy dog" "test.txt"
but if you get the type FilePath -> String -> IO ()
as a hint in your IDE, I think that's at least a big push in the right direction!
You could even go a step further and make a newtype
for filepaths so you don't accidentally mix up filenames and contents, but I guess that adds more hassle than it's worth, and there's probably also historical reasons why this isn't done.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With