mauro3 / Traits.jl

Exploration of traits in Julia
Other
39 stars 6 forks source link

Using Traits.jl to define Monads #3

Open tonyhffong opened 9 years ago

tonyhffong commented 9 years ago

I'm intrigued by this statement under to ponder

Sometimes it would be good to get at type parameters, for instance for Arrays and the like

which is one of the requirements to define a monad.

What do you think are the challenges in trying to do this?

One can think of Nullable{T}, Array{T}, and many others in terms of monads so the possibilities are quite exciting.

mauro3 commented 9 years ago

I have tried a few time but never understood monads... Anyway, there might be progress on accessing type parameters coming: https://github.com/JuliaLang/julia/issues/8974#issuecomment-68648607

Note that at the moment you can get at the type parameters using associated types:

tpar1(t::Type) = t.parameters[1]
@traitdef Indexable{X} begin
    Y = tpar1(X)
    getindex(X, Any) -> Y
    setindex!(X, Y, Any) -> X
end

However, that would require that all indexable types have their element type as first parameter, so maybe better:

@traitdef Indexable{X} begin
    Y = eltype(X)
    getindex(X, Any) -> Y
    setindex!(X, Y, Any) -> X
end

It would be cool if you could piece together a monad example using Traits, please submit a pull request!

(I also edited @tonyhffong's original post to make the citing clearer)

tonyhffong commented 9 years ago

Interesting. I definitely want to play with it some more. Conceptually, though, a monad always need a type parameter, so ideally we should do something like this:

@traitdef Monad{X{Y}} begin
    mreturn(Y) -> X{Y}
    bind( X{Y}, FuncFullSig{ Y, X{Z} } ) -> X{Z}
end

Assuming we have for FuncFullSig:

immutable FuncFullSig{TI,TO} # a function that is f(x::TI)::TO
    f::Function
end

As you can see, mreturn's call signature is a bit funky already. Directly it doesn't reference X at all.

Let's take Nullable as a hypothetical case. So we need to define somehow:

mreturn{T;Monad{Nullable{T}}}(x::T) = Nullable{T}(x)
function bind{T;Monad{Nullable{T}}}(x::Nullable{T}, f::FuncFullSig{T,Nullable{Z}} ) 
    if isnull(x)
        return Nullable{Z}()
    else
        return f.f( x.value ) # ideally, we could have a try block here too.
    end
end

We can chain all nullable functions even if their arguments do not understand Nullable.

Disclaimer: I'm struggling with how the dispatch should really work in this case. So all these are hypothetical.

For completeness, I've also come across an experimental package https://github.com/pao/Monads.jl from @pao.

tonyhffong commented 9 years ago

https://github.com/mauro3/Traits.jl/pull/4

eschnett commented 8 years ago

Regarding monads: I find the bind function, while convenient for Haskell's >>= syntax, more difficult to understand than the following basically equivalent definition of a monad:

@traitdef Monad{M{T}} begin
    munit(T) -> M{T}
    fmap(F, M{T}) -> M{U}
    mjoin(M{M{T}}) -> M{T}
end

where fmap is identical to Base.map. Its first argument f::F should have the type T -> U, but I don't know how to express this in Julia or in Traits.jl. Maybe writing simply M as return type of fmap would suffice, since a monad is something like a fancy container, and you have e.g. Array{Int} <: Array.