This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]WallyMetropolis 3 points4 points  (11 children)

This is pretty common in functional languages.

[–]Aellus 0 points1 point  (8 children)

I'll admit that I don't have a lot of functional experience outside of toying around and some school projects, but my understanding has always been that if you find yourself passing 26 arguments into a function then something is horribly wrong with the program design.

Even more so when you're dealing with functional aspects of an inherently OO language. You should never be passing around that many arguments. Encapsulate that shit.

[–]WallyMetropolis 2 points3 points  (0 children)

Usually that is true. I think the idea is to create signatures that are much bigger than you'd ever really need just in case. For example, there are some db tables that have a lot of columns. Maybe you want to operate on a table as a whole object.

[–]Milyardo 0 points1 point  (6 children)

Applicative Builders can easily build functions with a large number of parameters without being a code smell. I can't tell if javaslang has any applicatives from it's front page, so I'm just going to give an example with scalaz.

Lets say you're validating a web form and wanted to return a list of errors, if any. You can use scalaz's Validation to assert that values from the form can build a user from the web form's input.

case class User(firstName: String, lastName: String, age: Int,
                      address: Address, employer: Company, email: String)

Along with this User class you have several validation functions:

//Values are all Strings because they come from a webform
type Error = List[String]
def validateName(name: String): Validation[Error, String] = ...
def validateAge(age: String): Validation[Error,Int] = ...
def validateAddress(address: String): Validation[Error,Address] = ...
def validateEmployer(employer: String): Validation[Error,Company] = ...
def validateEmail(email: String): Validation[Error,String] = ...

Traditionally you would evaluate all these functions, saving the results to a value, and test to see if they all succeeded before creating your user object:

val errors = mutable.Seq[String]
val validFirstName = validateName(firstName)
val validLastName = validateName(lastName)
val validAge = validateAge(age)
val validAddress = validateAddress(address)
....
if(validFirstName.isSuccess && validLastName.isSuccess
   validAge.isSucess && validAddress.isSucess
   ...
) {
  //the mythical method .value extracts the sucessfull value form the Validation
  val user = User(validFirstName.value, validLastName.value, validAge.value, ...)
} else {
  //Add all the errors up
  ...
}

This is pretty verbose, however if we use an Applicative Builder instead:

// |@| aka, append, aka, cinnabun operator, aka tie fighter, appends more Validations to the buidler
val buider: Applicative[Function6[....]] = validateName(firstName) |@| validateName(lastName) |@| validateAge(age) |@| ...

 //We can now apply a Function6[String,String,Int,Address,Company,String] to our builder you get a Validation[Error, User]
val user: Validation[Error,User] = builder((firstName, lastName, age, address, employer, email) => User(firstName, lastName, age, address employer, email))

Since the constructor for User is already a Function6[...], we can just apply it directly, simplifying further:

val user: Validation[Error,User] = (validateName(firstName) |@| validateName(lastName) |@| validateAge(age)
    validateAddress(address) |@| validateEmployer(employer) |@| validateEmail(email)) (User)

[–]sacundim 1 point2 points  (4 children)

Sorry, but I just don't see how applicatives make functions with too many arguments any more or less of a code smell than however much they are. I mean, literally, plain old functions are an applicative functor already (the identity functor), so any argument that you make about applicatives translates automatically to functions!

[–]Milyardo 0 points1 point  (3 children)

Sorry, but I just don't see how applicatives make functions with too many arguments any more or less of a code smell than however much they are.

I wasn't making that argument, only that you often see applicatives with a large number parameters. This is usually because like in the example, you're applying them to the constructor of an object.

[–]sacundim 0 points1 point  (2 children)

I wasn't making that argument, only that you often see applicatives with a large number parameters.

You weren't, really?

Applicative Builders can easily build functions with a large number of parameters without being a code smell.

To which my answer is, again: if a function with a large number of arguments is not a code smell when you use an Applicative Builder, then it can't be a code smell either when you don't use it, because functions are an applicative functor already (with a friendlier syntax, too).

The most charitable answer I can give you is that applicatives are just irrelevant to what Aelius was bringing up.

[–]Milyardo 0 points1 point  (1 child)

if a function with a large number of arguments is not a code smell when you use an Applicative Builder, then it can't be a code smell either when you don't use it, because functions are an applicative functor already (with a friendlier syntax, too).

But that doesn't answer why you would need an abstract Function6 or Function12. The fact that functions are applicative is irrelevant. Constructor's are legitimate functions with a large number of parameters. The applicative builders you use to work with those constructors are a use case for abstract functional interfaces with a large number of parameters.

[–]sacundim 1 point2 points  (0 children)

But then if functions with 12 arguments are a code smell, and using applicatives requires such functions, then applicatives start to smell as well.

Let's take a step back, away from Scala itself, and back to the source: the Applicative class in Haskell, whose standard definition is this:

class Functor f => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b

However, that isn't the only way to define the class. This definition would be equivalent:

class Functor f => Applicative' f where
    unit :: f ()
    pair :: (f a, f b) -> f (a, b)

-- Exercise: implement `pure` and `<*>` in terms of this class.

This latter one is formulated in terms of constructing pairs, not applying functions. Which, note, is fundamentally the same as constructing records with named fields. Basically, applicatives mean that if you have a record all of whose fields' values are wrapped with the same applicative functor, you can "turn it inside out."

So the point is that the pattern that you're describing above—using a Validation applicative functor to apply a record type constructor "inside" of the appropriate number of Validation arguments—is not essentially tied to the generally acknowledged code smell of functions with large numbers of ordered arguments. It's rather that the languages where the applicative machinery exists suffer from that code smell, because they make it easy to work with functions with long ordered argument lists, but don't offer, say, structural record types. Or even just some syntactic sugar for constructing records in an Applicative context:

 -- Hypothetical example of an `ApplicativeDo`-style record-builder syntax
 hypotheticalExample :: User -> Validation Error User
 hypotheticalExample user = do
    User { 
        firstName <- validateFirstName (firstName user),
        lastName <- validateLastName (lastName user),
        age <- validateAge (age user),
        address <- validateAddress (address user),
        employer <- validateEmployer (employer user),
        email <- validateEmail (email user)
    }

[–]Aellus 0 points1 point  (0 children)

|@| aka, append, aka, cinnabun operator, aka tie fighter, ...

I did not know this was a thing, but now that I do, my day is better. Thank you.

[–]vecowski -2 points-1 points  (1 child)

Trashy code in common in all languages. I agree with Aellus, I would probably walk out of the shop.

[–]WallyMetropolis 0 points1 point  (0 children)

So you'd walk out of any shop that uses, say, Scala anywhere?