Hacker News new | past | comments | ask | show | jobs | submit login
Making Map Operations Implicit in Programming (silvrback.com)
14 points by farginay on Dec 17, 2013 | hide | past | favorite | 23 comments



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

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?


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.


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.


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.


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.


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?


With do notation, it becomes:

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


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.


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.


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.


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.


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...). 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.


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.


This is somewhat similar to the List monad in Haskell:

http://en.m.wikibooks.org/wiki/Haskell/Understanding_monads/...

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).


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.


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())`.


> 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.


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.)


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


Erik Meijer points to this as research in this area: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.146....


This is already the case in Haskell. Look up functor


No, fmap is still an explicit operation.


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.




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: