Open reganbaucke opened 4 years ago
map
is for collections; Some
isn't really a collection, so this seems like an odd interface.
One might argue that map is a function for transforming a 'normal' function to a function 'with a context'. For instance:
add_one(x) = x + 1
contextual_add_one(x) = map(add_one,x)
contextual_add_one([2,4]) == [3, 5] #true
contextual_add_one(Some(5)) == Some(6) #true
Of course in Julia, our usual 'context' is values contained within collections such as arrays or sets, but these need not necessarily be the only cases.
Based on what precedent or principle? It seems like a departure from what map
means normally.
Many languages do pun on this (e.g. C# linq, swift)
This principle is taken seriously in Haskell (i.e. they don't take it as a pun), "functor" is a "type class" on which fmap
can be applied. Collections, the Maybe
type, Either
and many other types are instances of this type class. To be a "functor" type, fmap
must respect two simple laws (in Julia syntax): fmap(identity, x) == x
(when x
is a functor), and composition: fmap(f∘g, x) == fmap(f, fmap(g, x))
, or in other words, fmap(f∘g, x) == (fmap(f)∘fmap(g))(x)
, where fmap(f) = x -> fmap(f, x)
.
The notion of "value with context" is often used in tutorials on functors or monads, but not all Haskell experts agree that this is a good way to explain it. Basically, a functor represent a value with context, and fmap
applies a function to this value, the implementation keeps track of the context. A collection is functor (you get the value by indexing), a function is a functor (you get the value by calling the function), Union{Some{T},Nothing}
is a functor ("you may have a value"), etc.
Although there are many different interpretations, thinking about map
in this way is based upon a mathematical principle from category theory. It does not in any way preclude/impact how map
might be understood in Julia as performing 'element-wise' operations on a collection.
This concept has been found to be useful in languages that support a 'functional' style such as Haskell, OCaml, Scala etc.
Basically, if a 'parameterized' type has an implementation of map
that obeys certain laws, then that parameterized type is called a 'functor'. From this point, many useful programming abstractions can be made. The implementation of map
for Option
shown above is the standard one from these aforementioned languages.
x-ref: #9446 and especially https://github.com/JuliaLang/julia/pull/9446#issuecomment-68502187 and http://joeduffyblog.com/2016/02/07/the-error-model/#non-null-types which was referenced later.
I suppose going functor route would require coordination with the map API on Dict #5794?
If we supported f?(x)
to propagate nothing
/missing
, it would make sense to have it return Some(f(x.value))
for Some
arguments instead of using map(f, x)
(which is indeed a common approach in many languages which treat nullables as containers).
I'm experimenting some of these concepts while learning how to work with monads. Take a look at https://github.com/tk3369/MonadFunctions.jl
I build an entire package to have clean monad-like julia types which interact nicely https://github.com/JuliaFunctional/DataTypesBasic.jl
the monadic interface is defined in https://github.com/JuliaFunctional/TypeClasses.jl
My learning back then is that map
is the best function to be used for functor map (e.g. SparseVector behaves functorial and is not creating a normal dense Vector).
However instead of Some{T}
and Nothing
, which has a special meaning in Julia, which is not necessarily well-suited for monadic code, the DataTypesBasic introduce Identity{T}
and Const{T}
.
Take a look at the packages - it so much nicer to work with in monadic style.
Base currently supports a basic implementation of the Option type. As it is currently implemented, Base defines a
Some
asThis is all that is necessary for the
Some
type, but other facilities are lacking. Withinjulia/base/some.jl
, it is mentioned that this is for use in the type union defined asUnion{Some{T},Nothing}
. I propose that this type union should be given a type alias:const Option{T} = Union{Some{T},Nothing}
.Options
are useful in themselves, but a lot of their power comes from their ability to be 'mapped' into. This is not currently implemented in Julia. To finally give this feature its minimal completion, I propose that themap
function ought to have the following implementation on these types: