Julia: proposal: map(f, ::Some{T})

Created on 28 Oct 2019  路  10Comments  路  Source: JuliaLang/julia

Base currently supports a basic implementation of the Option type.
As it is currently implemented, Base defines a Some as

struct Some{T}
    value::T
end

This is all that is necessary for the Some type, but other facilities are lacking.
Within julia/base/some.jl, it is mentioned that this is for use in the type union defined as Union{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 the map function ought to have the following implementation on these types:

function map(f::Function, option::Some{T}) where T
    Some(f(option.value))
end

function map(f::Function, option::Nothing) 
    nothing
end

All 10 comments

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鈭榞, x) == fmap(f, fmap(g, x)), or in other words, fmap(f鈭榞, x) == (fmap(f)鈭榝map(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

Was this page helpful?
0 / 5 - 0 ratings