

Making Map Operations Implicit in Programming - farginay
https://michaelfeathers.silvrback.com/making-map-operations-implicit-in-programming

======
jheriko
This is an 'ancient' idea pre-dating the recent functional craze: array
programming, it is so well known that it has a substantial wikipedia page:

[http://en.wikipedia.org/wiki/Array_programming](http://en.wikipedia.org/wiki/Array_programming)

its also important to note that this is syntactic sugar - and like most kinds
of syntactic sugar it can make code confusing to read, in order to make it
prettier. being explicit is often a very good thing for readability and for
compilers (they must also read your code) - i value it highly.

although there is something nice about being able to add two arrays together
and it do 'what i would expect'... but what do i expect if the arrays do not
match in size? how much more thinking do i need to do over being explicit?

~~~
farginay
I think that it is more than syntactic sugar, it's an affordance. Different
notation schemes make it more or less likely to find and code particular
solutions. As an example, the syntax of ML-derived languages makes partial
applications a much more reasonable choice than they are in Scala or in
C-derived languages: the syntax is tuned to them.

Array programming is due for a renaissance. Like any other form of
abstraction, we gain leverage once we are able to internalize it and use it.
The thinking you mention is, I think, just the normal hurdle of unfamiliarity.

~~~
jheriko
you make a good point.

i lean heavily towards code being explicit - i think of black boxes and third
party libraries as things to grudgingly use because i don't have superhuman
productivity for instance, and i prefer if i can debug their internals... i
know this is not entirely common.

------
latk
This is a standard feature of many languages or libraries, and generally known
as _array programming_ – something APL is known for. It crops up in numerical
libraries like "numpy" (Python) or "PDL" (Perl) where selected scalar
operations distribute over the whole collection. This is great when using
collections of numbers, but is not very generalizable: It may not be possible
to distinguish whether a method was called on a collection or all items in the
collection. One solution would be a “mapping method call operator” like "!"
(it's like a method call "." _and_ a shell pipe "|") – but now we have an
explicit "map" again.

I think an explicit "map" is more flexible in a general-purpose language. Some
languages do interesting experiments with this, e.g. Perl6 which uses
“hyperoperators” to precisely control how a given operator is applied.

------
DanWaterworth
How many times will Monads be reinvented?

    
    
        name.trim.filter{ _.length != 0 }.toUpperCase
    

becomes:

    
    
        name >>= (return . trim) >>= (guard . ((/= 0) . length)) >>= (return . toUpperCase)
    

I've purposefully used >>= and return instead of fmap to show that '.' above
is almost >>=.

EDIT: I've just noticed a type error in the use of guard, but you get the
idea.

~~~
farginay
I think the big thing is to make the case for one and the case for many look
exactly the same in the language. Is that nicer to look at with do-notation?

~~~
DanWaterworth
With do notation, it becomes:

    
    
        do
          n <- name
          let trimmed = trim n
          guard (length trimmed /= 0)
          return (toUpperCase trimmed)

~~~
farginay
Wouldn't it be nice if it was just:

    
    
        toUpperCase . guard (\x -> length x /= 0) . trim
    

I think that is how it would be if it was part of the language.

~~~
DanWaterworth
I like my code to do what it says. You can pretty much do that in Haskell. If
you allow me to add a definition first:

    
    
        mfilter' :: MonadPlus m => (a -> Bool) -> a -> m a
        mfilter' f = mfilter f . return
    

Then it's just:

    
    
        toUpperCase <=< mfilter' (\x -> length x /= 0) . trim
    

Which is pretty close. No need to change the language.

------
dragonwriter
The problem I see is that implicit mapping restricts the scope of operations
that can work on collections -- if there is no distinction between applying an
operation to an single object and applying the same operation to all members
of a collection, then no collection can directly support the same operation as
anything which might be a member of the collection, because you can't
disambiguate between attempts to operate on the collection and attempts to
operate on the members.

This would seem to be a problem in general, and a particular problem for
nested collections. Explicitness makes intent clear.

~~~
farginay
It's interesting to see how the array programming languages have handled this.
The way it works (loosely) is that everything is an array. A scalar is kind of
a degenerate array, and a vector is an array of rank 1. Matrices can be seen
as arrays of arrays of arrays..

Each operator defines its extent. The count operator '#' in J works at the top
level. If you have a 3 x 4 matrix, it returns 3. Take '{.' works the same way.
If you have a 3 x 4 matrix and you take 1, you will get a 4 element vector
which is the first row of the matrix.

Something like decrement '<:' applies to all of the elements in a matrix
regardless of the dimensionality.

This isn't like OO where collections have operations attached to them.

------
axblount
The example in the article was taken directly from the Scala documentation for
[Option]([http://www.scala-
lang.org/api/current/index.html#scala.Optio...](http://www.scala-
lang.org/api/current/index.html#scala.Option)). The map _is_ implicit if you
use Scala's for notation. From the linked docs:

    
    
        val upper = for {
          name <- request getParameter "name"
          trimmed <- Some(name.trim)
          upper <- Some(trimmed.toUpperCase) if trimmed.length != 0
        } yield upper
        println(upper getOrElse "")
    

In Scala, an implementation of flatMap (bind), map, and filter allows you to
use for expressions like the above. Objects that support these operations are
not strictly monads, but they're close enough for most practical uses. Haskell
is great, but give Scala a little credit.

~~~
dragonwriter
for expressions (and Haskell's do notation, etc.) are alternative syntax for
explicit mapping: implicit mapping would be syntax that would make applying an
operator to all members of a collection (including one with limited
cardinality like Option) indistinguishable from applying the same operator to
a simple value.

------
dangirsh
This is somewhat similar to the List monad in Haskell:

[http://en.m.wikibooks.org/wiki/Haskell/Understanding_monads/...](http://en.m.wikibooks.org/wiki/Haskell/Understanding_monads/List)

Bind is concatMap and return puts something into a singleton list. This allows
"nondeterministic" computations, where applying functions over all elements in
the list is implicit (via bind). Using the monad is not the same as having map
be implicit everywhere, but it allows cases where you need several chained
implicit (concat)maps to be cleanly implemented.

This is arguably a better design than making all functions polymorphic over
lists, and having implicit maps be a global rule (rather than something
constrained in a monad's context).

------
hopp_check
How difficult will code be to read if implicit operations are happening
without any mention of the language construct executing them? As far as this
goes, Haskell's Maybe monad is the simplest system I've ever seen:

    
    
      let x = Just 3 in
      do
       value <- x
       guard (value > 1)
       return (value * 4)
    

If x was 'Nothing' then the whole operation would short circuit and 'Nothing'
would be returned. If the guard fails, then 'Nothing' is returned.

------
porges
My favourite example of a language with 'automatic functorization' is
Icon/Unicon. All functions, when applied to generators, themselves become
generators.

So e.g. `read` is a generator that reads lines from the input, `write` writes
a line to output, and the composition of them, `write(read())`, is a generator
that copies lines from input to output. To run the generator, you can write
`while write(read())`.

------
rukednous
> What would our languages look like if we made them shape polymorphic? If we
> moved in that direction it would be like baking the Composite Design Pattern
> deeply into language machinery. It could be very powerful.

In addition to APL and J, that would look a lot like SQL as well.

~~~
dragonwriter
SQL uses explicit mapping with syntax that is more like list comprehension
syntax in general purpose languages than function application syntax; it
doesn't use implicit mapping.

(Though SQL often uses nullable values rather than a collection-like type for
the use case where Scala uses Option types -- so the specific example used at
the head of the article might look similar in SQL, but not because SQL is an
example of the general approach the article is discussing.)

------
ygra
R also doesn't distinguish between applying an operation to a scalar or a
vector.

------
farginay
Erik Meijer points to this as research in this area:
[http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.146....](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.146.5411)

------
rian
This is already the case in Haskell. Look up functor

~~~
quatrevingts
No, fmap is still an explicit operation.

~~~
hopp_check
There should be some degree of explicitness so that when you look at the code
a year later it doesn't look like voodoo magic.

