Is Scala pattern matching harmful to object orientation? - scala

Disclaimer: I'm much more experienced in Java than in Scala (which I'm learning).
In Java, I have read several times that switch could be harmful to object orientation, especially when used against types (this kind of problems also led to this: http://www.antiifcampaign.com/).
In Scala, one of the introduction video lessons of Martin Odersky shows how pattern matching is a better alternative to multiple "low-level" isInstanceOf checks.
Although, pattern matching catches more flexible patterns than a simple Java switch, I still see the first as a generalization of the latter.
Don't pattern matching and "switch on types" roughly share the same fundamental approach?
Isn't pattern matching just some syntactic sugar hiding lots of isInstanceOf/asInstanceOf?
If not, how would pattern matching be more flexible (as in: resilient to change) than writing those low level checks ourselves (apart from the error-prone nature of this tedious task)?

Pattern matching may well be "syntactic sugar", in some senses, but it is a lot more than a code replacement.
a) The match construct will enforce completeness on the domain by making sure there are no uncovered cases, unless you specifically indicate that it is only a partial function.
b) The match syntax is much less verbose.
c) The match construct includes guards, predicates that qualify the match.
def mergeSort(a: List[Int], b: List[Int]): List[Int] = (a, b) match {
case (ah :: at, bh :: bt) if ah < bh =>
ah :: mergeSort(at, b)
case (ah :: at, bh :: bt) if ah >= bh =>
bh :: mergeSort(a, bt)
case (Nil, b) =>
b
case (a, Nil) =>
a
}
d) Deconstruction
...
case Node(x, Empty, Empty) =>
x - 52
...
The declarative form is much easier to read (given a bit of experience).
Edit: the term "syntactic sugar" is used frequently about Scala. However, type inferencing and "aggressive" type resolution such as is used in pattern matching make for powerful language constructs. As for the original question, pattern matching is independent of object orientation and provides powerful, type safe methods for accessing data that may make the use of classes less of a requirement.

If you have n types of nouns with m types of verbs, you have n x m noun-verb combinations to keep track of.
There are two good strategies for managing this complexity.
You can organize your code by nouns (classes), and each class needs to be able to deal with every verb (method). This makes it easy to add new nouns. Of course, when you add a new verb, it's annoying to go through each of your nouns and make sure that it can handle the new verb. This is the object-oriented approach.
You can also organize your code by verbs (functions), and each function needs to be able to deal with every noun (pattern). This makes it easy to add new verbs, Of course, when you add a new noun, it's annoying to go through each of your verbs and make sure that it can handle the new noun. This is the functional approach.
There's nothing clearly better about either approach, and both have been used very successfully. You can run into problems when you try to mix them, though, so generally you want to be very clear about which approach you are using for a given problem.

In Scala, pattern matching is done via extractors. Extractors are themselves objects and only have access to the public API of the object that is being pattern matched against.
Thus, pattern matching does not break object encapsulation.
Theoretically, all conditionals (including pattern matching) can be replaced by polymorphism. See Smalltalk as an example of a language which has no conditionals, no loops, no switches, no control structures of any kind except polymorphic message dispatch. And see Newspeak as an example of a Smalltalk-inspired language which has powerful pattern matching inspired by Scala and F# implemented as a library completely on top of polymorphic message dispatch.
When done this way, pattern matching is as object-oriented as can be.

Related

Polymorphic lambda calculus

In the very instructive talk Constraints Liberate, Rúnar says, there is exactly one way to implement a function with this signature:
def id[A](a: A): A
Well, obviously. But nitpicking people could come up with a implementation like
def id[A](a: A): A = {
if (a.isInstanceOf[Integer])
5
else
a
}
Ok, why do I care?
Exactly the same issue could be said about the functions in the famous Theorems for free! article, but there we have the restriction of functions in the polymorphic lambda calculus, and surely Type-Casing is invalid in this context.
I am looking for a precise way to make clear what subset of the Scala language is allowed when we say something like "there is only one possible implementation of a pure function with this signature: "
def id[A](a: A): A
The glib answer is "the subset of Scala modeled by the polymorphic lambda calculus" :).
Less glibly the the Scalazzi Safe Scala subset has a pretty good list of conditions. They're reproduced below with minor modifications.
No null
No exceptions
No type casing (_.isInstanceOf and case matches), except for one exception
No type casting (_.asInstanceOf)
No side effects
No .equals (_ == _), .toString, or .hashCode
No notify or wait (which I would lump with side effects)
No classOf or .getClass
No general recursion (more generally all functions must be total)
The one exception to "No type casing" is pattern matching with match ... case on the equivalent of algebraic data types with sealed hierarchies and case classes and case objects. To know if your particular match ... case statement is allowed, use the following rules:
Only use extractors in the case statement; do not use case (x: Int)... type matches. Make sure your extractors obey the Scalazzi rules (the easiest way to fulfill this is to not write the extractors at all, i.e. only use compiler-provided extractors in the form of case class and case object).
The match must cover all possible cases. This implies no match is not exhaustive warnings and that the thing you're matching against better be a subtype of something sealed.
Those rules are slightly different than the fold-encoding rule that the Typelevel blog article proposes, but are basically equivalent (the rules above are more conservative and hopefully easier to keep in your head).
If you can't/don't want to verify that these hold for all the functions you didn't write but use, I've found that following the rules above for your own code and then not depending on functions that take in Any as an argument or produce Unit as a return type is often enough.
The statement is correct, and does not require subsetting as such, if you restrict your attention to referentially transparent functions.

Motivation for Scala underscore in terms of formal language theory and good style?

Why is it that many people say that using underscore is good practice in Scala and makes your code more readable? They say the motivation comes from formal language theory. Nevertheless many programmers, particularly from other languages, especially those that have anonymous functions, prefer not to use underscores particularly for placeholders.
So what is the point in the underscore? Why does Scala (and some other functional languages as pointed by om-nom-nom) have the underscore? And what is the formal underpinning, in terms of complexity and language theory, as to why it often good style to use it?
Linguistics
The origin and motivation for most of the underscore uses in Scala is to allow one to construct expressions and declarations without the need to always give every variable (I mean "variable" as in Predicate Calculus, not in programming) of the language a name. We use this all the time in Natural Language, for example I referred to a concept in the previous sentence in this sentence using "this" and I referred to this sentence using "this" without there being any confusion over what I mean. In Natural Language these words are usually called "pronouns", "anaphors", "cataphors", the referents "antecedent" or "postcedent", and the process of understanding/dereferencing them is called "anaphora".
Algorithmic Information Theory
If we had to name every 'thing' in Natural Language before we can refer to it, similarly every type of thing in order to quantify over it, as in Predicate Calculus and in most programming languages, then speaking would become extremely long winded. It is thanks to context that we can infer what is meant by words like "this", "it", "that", etc, we do it easily.
Therefore why restrict this simple, elegant and efficient means to communicate to Natural Language? So it was added to Scala.
If we did attempt to name every single 'thing' or 'type of thing', sentences become so long and complicated that it becomes very difficult to understand due to it's verbosity and the introduction of redundant symbols. The more symbols you add to a sentence the more difficult it becomes to understand, ergo this is why it's good practice, not only in Natural Language, but in Scala too. In fact one could formalize this assertion in terms of Kolmogorov Complexity and prove that a sequence of sentences adopting placeholders have lower complexity than those that unnecessarily name everything (unless the name is exactly the same in every instance, but that usually doesn't make sense). Therefore we can conclusively say contrary to some programmers belief, that the placeholder syntax is simpler and easier to read.
The reason why it has some resistance in it's use, is that if one is already a programmer, one must make an effort to retrain the brain not to name everything, just as (if they can remember) they may have found learning to code in the first place required quite an effort.
Examples
Now let's look at some specific uses more formally:
Placeholder Syntax
Means "it", "them", "that", "their" etc (i.e. pronouns), e.g. 1
lines.map(_.length)
can be read as "map lines to their length", similarly we can read lineOption.map(_.length) as "map the line to it's length". In terms of complexity theory, this is simpler than "for each 'line' in lines, take the length of 'line'" - which would be lines.map(line => line.length).
Can also be read as "the" (definite article) when used with type annotation, e.g.
(_: Int) + 1
"Add 1 to the integer"
Existential Types
Means "of some type" ("some" the pronoun), e.g
foo: Option[_]
means "foo is an Option of some type".
Higher Kinded type parameters
Again, basically means "of some type" ("some" the pronoun), e.g.
class A[K[_],T](a: K[T])
Can be read "class A takes some K of some type ..."
Pattern Match Wildcards
Means "anything" or "whatever" (pronouns), e.g.
case Foo(_) => "hello"
can be read as "for a Foo containing anything, return 'hello'", or "for a Foo containing whatever, return 'hello'"
Import Wildcards
Means "everything" (pronoun), e.g.
import foo._
can be read as "import everything from foo".
Default Values
Now I read this like "a" (indefinite article), e.g.
val wine: RedWine = _
"Give me a red wine", the waiter should give you the house red.
Other uses of underscore
The other uses of underscores are not really related to the point of this Q&A, nevertheless we breifly discuss them
Ignored Values/Params/Extractions
Allow us to ignore things in an explicit 'pattern safe' way. E.g.
val (x, _) = getMyPoint
Says, we are not going to use the second coordinate, so no need to get freaky when you cant find a use in the code.
Import Hidding
Just a way to say "except" (preposition).
Function Application
E.g.
val f: String => Unit = println _
This is an interesting one as it has an exact analogue in linguistics, namely nominalization, "the use of a verb, an adjective, or an adverb as the head of a noun phrase, with or without morphological transformation" - wikipedia. More simply it is the process of turning verbs or adjectives into nouns.
Use in special method names
Purely a syntax thing and doesn't really relate to linguistics.

What do you call the data wrapped inside a monad?

In speech and writing, I keep wanting to refer to the data inside a monad, but I don't know what to call it.
For example, in Scala, the argument to the function passed to flatMap gets bound to…er…that thing inside the monad. In:
List(1, 2, 3).flatMap(x => List(x, x))
x gets bound to that thing I don't have a word for.
Complicating things a bit, the argument passed to the Kleisli arrow doesn't necessarily get bound to all the data inside the monad. With List, Set, Stream, and lots of other monads, flatMap calls the Kleisli arrow many times, binding x to a different piece of the data inside the monad each time. Or maybe not even to "data", so long as the monad laws are followed. Whatever it is, it's wrapped inside the monad, and flatMap passes it to you without the wrapper, perhaps one piece at a time. I just want to know what to call the relevant inside-the-monad stuff that x refers to, at least in part, so I can stop mit all this fumbly language.
Is there a standard or conventional term for this thing/data/value/stuff/whatever-it-is?
If not, how about "the candy"?
Trying to say "x gets bound to" is setting you up for failure. Let me explain, and guide you towards a better way of expressing yourself when talking about these sorts of things.
Suppose we have:
someList.flatMap(x => some_expression)
If we know that someList has type List[Int], then we can safely say that inside of some_expression, x is bound to a value of type Int. Notice the caveat, "inside of some_expression". This is because, given someList = List(1,2,3), x will take on the values of each of them: 1, 2, and 3, in turn.
Consider a more generalized example:
someMonadicValue.flatMap(x => some_expression)
If we know nothing about someMonadicValue, then we don't know much about how some_expression is going to be invoked. It may be run once, or three times (as in the above example), or lazily, or asynchronously, or it may be scheduled for once someMonadicValue is finished (e.g. futures), or it may never be used (e.g. empty list, None). The Monad interface does not include reasoning about when or how someExpression will be used. So all you can say about what x will be is confined to the context of some_expression, whenever and however some_expression happens to be evaluated.
So back to the example.
someMonadicValue.flatMap(x => some_expression)
You are trying to say "x is the ??? of someMonadicValue." And you are looking for the word that accurately replaces ???. Well I'm here to tell you that you're doing it wrong. If you want to speak about x, then either do it
Within the context of some_expression. In this case, use the bolded phrase I gave you above: "inside of some_expression, x is bound to a value of type Foo." Or, alternatively, you can speak about x...
With additional knowledge about which monad you're dealing with.
In case #2, for example, for someList.flatMap(x => some_expression), you could say that "x is each element of someList." For someFuture.flatMap(x => some_expression), you could say that "x is the successful future value of someFuture, if it indeed ever completes and succeeds."
You see, that's the beauty of Monads. That ??? that you are trying to describe, is the thing which the Monad interface abstracts over. Now do you see why it's so difficult to give ??? a name? It's because it has a different name and a different meaning for each particular monad. And that's the point of having the Monad abstraction: to unify these differing concepts under the same computational interface.
Disclaimer: I'm definitely not an expert in functional programming terminology and I expect that the following will not be an answer to your question from your point of view. To me the problem rather is: If choosing a term requires expert knowledge, so does understanding.
Choosing an appropriate term largely depends on:
your desired level of linguistic correctness, and
your audience, and the corresponding connotations of certain terms.
Regarding the linguistic correctness the question is whether you properly want to refer to the values/data that are bound to x, or whether you can live with a certain (incorrect) abstraction. In terms of the audience, I would mainly differentiate between an audience with a solid background in functional programming and an audience coming from other programming paradigms. In case of the former, choosing the term is probably not entirely crucial, since the concept itself is familiar, and many terms would lead to the right association. The discussion in the comments already contains some very good suggestions for this case. However, the discussion also shows that you need a certain background in functional programming to see the rationale behind some terms.
For an audience without a background in functional programming, I would rather sacrifice linguistic correctness in favor of comprehensibility. In such a situation I often just refer to it as "underlying type", just to avoid any confusion that I would probably create by trying to refer to the "thing(s) in the monad" itself. Obviously, it is literally wrong to say "x is bound to the underlying type". However, it is more important to me that my audience understands a concept at all. Since most programmers are familiar with containers and their underlying types, I'm aiming for the (flawed) association "underlying type" => "the thing(s) that are in a container" => "the thing(s) inside a monad", which often seems to work.
TL;DR: There always is a trade-off between correctness and accessibility. And when it comes to functional programming, it is sometimes helpful to shift the bias towards the latter.
flatMap does not call the Kleisli arrow many times. And "that thing" is not "inside" the monad.
flatMap lifts a Kleisli arrow to the monad. You could see this as the construction of an arrow M[A] => M[B] between types (A, B) lifted to the monad (M[A], M[B]), given a Kleisli arrow A => M[B].
So x in x => f(x) is the value being lifted.
What data?
data Monady a = Monady
Values of monads are values of monads, their wrapped type may be entirely a fiction. Which is to say that talking about it as if it exists may cause you pain.
What you want to talk about are the continuations like Monad m => a -> m b as they are guaranteed to exist. The funny stuff occurs in how (>>=) uses those continuations.
'Item' seems good? 'Item parameter' if you need to be more specific.
Either the source data can contain multiple items, or the operation can call it multiple times. Element would tend to be more specific for the first case, Value is singular as to the source & not sensible for list usages, but Item covers all cases correctly.
Disclaimer: I know more about understandable English than about FP.
Take a step back here.
A monad isn't just the functor m that wraps a value a. A monad is the stack of endofunctors (i.e., the compositions of m's), together with the join operator. That's where the famous quip -- that a monad is a monoid in the category of endofunctors, what's the problem? -- comes from.
(The whole story is that the quip means the composition of m's is another m, as witnessed by join)
A thing with the type (m a) is typically called a monad action. You can call a the result of the action.

Motivation for match expression syntax

The syntax for match expressions is pretty nice:
expr match {
case Test(l1) => ...
...
}
But it's driving me nuts that I don't understand the motivation to why this this syntax is used instead of match (expr) ..., like branching statements in a decent C descendant!
I see no reasonably explanation for this. And I don't find the answer neither in Programming in Scala, the Scala web site, in this paper, this thesis, here on SO nor on the rest of the web.
It's not that it's anything wrong with it, just that it's a complete mystery. And when match work in this way, why not if and for also?
Does anybody know? I don't think I can stand using the language any longer without finding this out. I think about it all the time. I can hardly sleep at night.
To take a similar bit of Scala syntax, guards in pattern matching cases don't require parentheses around their conditional expressions—e.g., the following:
case i if i % 2 == 0 => i / 2
Is just as valid as this:
case i if (i % 2 == 0) => i / 2
Sticking to the C-family style would mean requiring the latter form, even though the parentheses aren't necessary for disambiguation. The Scala language designers decided that reducing line noise trumped maintaining the family resemblance in this case.
I'd guess that a similar motivation is at work in the match syntax, and to my eye match (expr) { ... } does indeed look pretty awful (and misleading) compared to expr match { ... }.
Also, just this afternoon I refactored someone else's x match { ... } on an Option to x map { ... } instead. match as an infix operator makes the similarity between these two expressions clear.
On the issue of why match isn't just a method, here's a five year-old question from David Pollak on the scala-debate mailing list:
Why is 'match' a language level construct rather than a method on Any?
And Martin Odersky's answer:
It used to be that way in Scala 1. I am no longer sure why we changed.
syntax highlighting? error reporting? not sure. I don't think it
matters much either way, though.
I'm with Martin on this one.
Note that there are a couple of practical differences (apart from the simple "dot or not" question). For example, this doesn't compile:
def foo[A, B](f: PartialFunction[A, B])(a: A) = a match f
If match were still a method on Any, requiring a literal bunch of cases would be a rather strange requirement.

What are practical uses of applicative style?

I am a Scala programmer, learning Haskell now. It's easy to find practical use cases and real world examples for OO concepts, such as decorators, strategy pattern etc. Books and interwebs are filled with it.
I came to the realization that this somehow is not the case for functional concepts. Case in point: applicatives.
I am struggling to find practical use cases for applicatives. Almost all of the tutorials and books I have come across so far provide the examples of [] and Maybe. I expected applicatives to be more applicable than that, seeing all the attention they get in the FP community.
I think I understand the conceptual basis for applicatives (maybe I am wrong), and I have waited long for my moment of enlightenment. But it doesn't seem to be happening. Never while programming, have I had a moment when I would shout with a joy, "Eureka! I can use applicative here!" (except again, for [] and Maybe).
Can someone please guide me how applicatives can be used in a day-to-day programming? How do I start spotting the pattern? Thanks!
Applicatives are great when you've got a plain old function of several variables, and you have the arguments but they're wrapped up in some kind of context. For instance, you have the plain old concatenate function (++) but you want to apply it to 2 strings which were acquired through I/O. Then the fact that IO is an applicative functor comes to the rescue:
Prelude Control.Applicative> (++) <$> getLine <*> getLine
hi
there
"hithere"
Even though you explicitly asked for non-Maybe examples, it seems like a great use case to me, so I'll give an example. You have a regular function of several variables, but you don't know if you have all the values you need (some of them may have failed to compute, yielding Nothing). So essentially because you have "partial values", you want to turn your function into a partial function, which is undefined if any of its inputs is undefined. Then
Prelude Control.Applicative> (+) <$> Just 3 <*> Just 5
Just 8
but
Prelude Control.Applicative> (+) <$> Just 3 <*> Nothing
Nothing
which is exactly what you want.
The basic idea is that you're "lifting" a regular function into a context where it can be applied to as many arguments as you like. The extra power of Applicative over just a basic Functor is that it can lift functions of arbitrary arity, whereas fmap can only lift a unary function.
Since many applicatives are also monads, I feel there's really two sides to this question.
Why would I want to use the applicative interface instead of the monadic one when both are available?
This is mostly a matter of style. Although monads have the syntactic sugar of do-notation, using applicative style frequently leads to more compact code.
In this example, we have a type Foo and we want to construct random values of this type. Using the monad instance for IO, we might write
data Foo = Foo Int Double
randomFoo = do
x <- randomIO
y <- randomIO
return $ Foo x y
The applicative variant is quite a bit shorter.
randomFoo = Foo <$> randomIO <*> randomIO
Of course, we could use liftM2 to get similar brevity, however the applicative style is neater than having to rely on arity-specific lifting functions.
In practice, I mostly find myself using applicatives much in the same way like I use point-free style: To avoid naming intermediate values when an operation is more clearly expressed as a composition of other operations.
Why would I want to use an applicative that is not a monad?
Since applicatives are more restricted than monads, this means that you can extract more useful static information about them.
An example of this is applicative parsers. Whereas monadic parsers support sequential composition using (>>=) :: Monad m => m a -> (a -> m b) -> m b, applicative parsers only use (<*>) :: Applicative f => f (a -> b) -> f a -> f b. The types make the difference obvious: In monadic parsers the grammar can change depending on the input, whereas in an applicative parser the grammar is fixed.
By limiting the interface in this way, we can for example determine whether a parser will accept the empty string without running it. We can also determine the first and follow sets, which can be used for optimization, or, as I've been playing with recently, constructing parsers that support better error recovery.
I think of Functor, Applicative and Monad as design patterns.
Imagine you want to write a Future[T] class. That is, a class that holds values that are to be calculated.
In a Java mindset, you might create it like
trait Future[T] {
def get: T
}
Where 'get' blocks until the value is available.
You might realize this, and rewrite it to take a callback:
trait Future[T] {
def foreach(f: T => Unit): Unit
}
But then what happens if there are two uses for the future? It means you need to keep a list of callbacks. Also, what happens if a method receives a Future[Int] and needs to return a calculation based on the Int inside? Or what do you do if you have two futures and you need to calculate something based on the values they will provide?
But if you know of FP concepts, you know that instead of working directly on T, you can manipulate the Future instance.
trait Future[T] {
def map[U](f: T => U): Future[U]
}
Now your application changes so that each time you need to work on the contained value, you just return a new Future.
Once you start in this path, you can't stop there. You realize that in order to manipulate two futures, you just need to model as an applicative, in order to create futures, you need a monad definition for future, etc.
UPDATE: As suggested by #Eric, I've written a blog post: http://www.tikalk.com/incubator/blog/functional-programming-scala-rest-us
I finally understood how applicatives can help in day-to-day programming with that presentation:
https://web.archive.org/web/20100818221025/http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-html/index.html
The autor shows how applicatives can help for combining validations and handling failures.
The presentation is in Scala, but the author also provides the full code example for Haskell, Java and C#.
Warning: my answer is rather preachy/apologetic. So sue me.
Well, how often in your day-to-day Haskell programming do you create new data types? Sounds like you want to know when to make your own Applicative instance, and in all honesty unless you are rolling your own parser, you probably won't need to do it very much. Using applicative instances, on the other hand, you should learn to do frequently.
Applicative is not a "design pattern" like decorators or strategies. It is an abstraction, which makes it much more pervasive and generally useful, but much less tangible. The reason you have a hard time finding "practical uses" is because the example uses for it are almost too simple. You use decorators to put scrollbars on windows. You use strategies to unify the interface for both aggressive and defensive moves for your chess bot. But what are applicatives for? Well, they're a lot more generalized, so it's hard to say what they are for, and that's OK. Applicatives are handy as parsing combinators; the Yesod web framework uses Applicative to help set up and extract information from forms. If you look, you'll find a million and one uses for Applicative; it's all over the place. But since it's so abstract, you just need to get the feel for it in order to recognize the many places where it can help make your life easier.
I think Applicatives ease the general usage of monadic code. How many times have you had the situation that you wanted to apply a function but the function was not monadic and the value you want to apply it to is monadic? For me: quite a lot of times!
Here is an example that I just wrote yesterday:
ghci> import Data.Time.Clock
ghci> import Data.Time.Calendar
ghci> getCurrentTime >>= return . toGregorian . utctDay
in comparison to this using Applicative:
ghci> import Control.Applicative
ghci> toGregorian . utctDay <$> getCurrentTime
This form looks "more natural" (at least to my eyes :)
Coming at Applicative from "Functor" it generalizes "fmap" to easily express acting on several arguments (liftA2) or a sequence of arguments (using <*>).
Coming at Applicative from "Monad" it does not let the computation depend on the value that is computed. Specifically you cannot pattern match and branch on a returned value, typically all you can do is pass it to another constructor or function.
Thus I see Applicative as sandwiched in between Functor and Monad. Recognizing when you are not branching on the values from a monadic computation is one way to see when to switch to Applicative.
Here is an example taken from the aeson package:
data Coord = Coord { x :: Double, y :: Double }
instance FromJSON Coord where
parseJSON (Object v) =
Coord <$>
v .: "x" <*>
v .: "y"
There are some ADTs like ZipList that can have applicative instances, but not monadic instances. This was a very helpful example for me when understanding the difference between applicatives and monads. Since so many applicatives are also monads, it's easy to not see the difference between the two without a concrete example like ZipList.
I think it might be worthwhile to browse the sources of packages on Hackage, and see first-handedly how applicative functors and the like are used in existing Haskell code.
I described an example of practical use of the applicative functor in a discussion, which I quote below.
Note the code examples are pseudo-code for my hypothetical language which would hide the type classes in a conceptual form of subtyping, so if you see a method call for apply just translate into your type class model, e.g. <*> in Scalaz or Haskell.
If we mark elements of an array or hashmap with null or none to
indicate their index or key is valid yet valueless, the Applicative
enables without any boilerplate skipping the valueless elements while
applying operations to the elements that have a value. And more
importantly it can automatically handle any Wrapped semantics that
are unknown a priori, i.e. operations on T over
Hashmap[Wrapped[T]] (any over any level of composition, e.g. Hashmap[Wrapped[Wrapped2[T]]] because applicative is composable but monad is not).
I can already picture how it will make my code easier to
understand. I can focus on the semantics, not on all the
cruft to get me there and my semantics will be open under extension of
Wrapped whereas all your example code isn’t.
Significantly, I forgot to point out before that your prior examples
do not emulate the return value of the Applicative, which will be a
List, not a Nullable, Option, or Maybe. So even my attempts to
repair your examples were not emulating Applicative.apply.
Remember the functionToApply is the input to the
Applicative.apply, so the container maintains control.
list1.apply( list2.apply( ... listN.apply( List.lift(functionToApply) ) ... ) )
Equivalently.
list1.apply( list2.apply( ... listN.map(functionToApply) ... ) )
And my proposed syntactical sugar which the compiler would translate
to the above.
funcToApply(list1, list2, ... list N)
It is useful to read that interactive discussion, because I can't copy it all here. I expect that url to not break, given who the owner of that blog is. For example, I quote from further down the discussion.
the conflation of out-of-statement control flow with assignment is probably not desired by most programmers
Applicative.apply is for generalizing the partial application of functions to parameterized types (a.k.a. generics) at any level of nesting (composition) of the type parameter. This is all about making more generalized composition possible. The generality can’t be accomplished by pulling it outside the completed evaluation (i.e. return value) of the function, analogous to the onion can’t be peeled from the inside-out.
Thus it isn’t conflation, it is a new degree-of-freedom that is not currently available to you. Per our discussion up thread, this is why you must throw exceptions or stored them in a global variable, because your language doesn’t have this degree-of-freedom. And that is not the only application of these category theory functors (expounded in my comment in moderator queue).
I provided a link to an example abstracting validation in Scala, F#, and C#, which is currently stuck in moderator queue. Compare the obnoxious C# version of the code. And the reason is because the C# is not generalized. I intuitively expect that C# case-specific boilerplate will explode geometrically as the program grows.