How to set DataType within function factory


#1

Hello, I am trying to think about type stability in a function that accepts models from different modules. I thought I might be able to set the DataType accepted by functions produced by a factory dynamically, but it’s not doing what I thought it would:

julia> function foo(A)
       myT = typeof(A)
       function bar{myT}(b::myT)
         return b^2
       end
       return bar
       end
foo (generic function with 1 method)

julia> bar = foo(1.0)
bar (generic function with 1 method)

julia> bar(2.0)
4.0

julia> bar(1) #I expect this to crash due to Int64
1

#2

Try this:

julia> function foo{T}(A::T)
              
              function bar(b::T)
                return b^2
              end
              return bar
              end
foo (generic function with 1 method)

julia> foo(1.0)(1)
ERROR: MethodError: no method matching (::#bar#1{Float64})(::Int64)
Closest candidates are:
  bar{T}(::T) at REPL[1]:4

Your definition is like defining a function like f{Int}(a::Int) = ...; in there Int is just a typevariable and has nothing to do with the Int datatype.


#3

Thanks, I can see how that caters to the question I asked, but I had oversimplified.
What I was thinking about was more like this:

julia> function foo(m::Module)
       model = m.initialise()
       modeltype = typeof(model)
       function dosomething{modeltype}(model::modeltype)
           return true
       end
       return dosomething
       end
foo (generic function with 1 method)

julia> module SomeModel
       type Model
         a::Float64
         b::String
       end
       function initialise()
         return Model(1.0, "hello")
       end
       end
SomeModel

julia> dosomething = foo(SomeModel)
dosomething (generic function with 1 method)

julia> mymodel = SomeModel.initialise()
SomeModel.Model(1.0,"hello")

julia> dosomething(mymodel)
true

julia>dosomething(2.0) #was hoping this would crash.
true

I could be going about this all wrong, but I was hoping to make a module that could take models implemented in a bunch of other modules (with a common interface) and write type stable code for them.


#4

I think you’re going about this wrong. You’re trying to make

model = m.initialise()

work. That’s probably due to familiarity with object-oriented programming? If you just did

initialize(model)

and wrote different dispatches for initialize, then you’d have a lot less trouble. Or even for this case, initialize should be part of the constructor? Instead of each type being in a different module, you should just name them differently all in the same module, or use a type parameter like Model{:this_model} (symbols are accepted directly), and then dispatch on the symbol. Then for dosomething, you should just be adding dispatches to a dosomething function (or use call-overloading).

This just looks like trying to fit the wrong design into Julia and trying to circumvent multiple-dispatch.


#5

Thanks for the feedback. I’m sure I am going about it wrong as the aim is modest but I’ve not got there! (But not caused by OOP, at least I don’t think so)

you should just name them differently all in the same module

This is the solution I was trying to avoid because the concept I’m trying to automate in foo would be generic across a bunch of models, so I don’t want to unnecessarily hardcode in the names of modules.

I think I get where you’re going though, going to have another crack later.


#6

Thanks again for the feedback (found a solution after looking at the page on type parameters). Here’s a solution that has the desired effect. That is, I can make a model using module with functions that accept any type written to conform to an abstract interface. Without having to import the concrete versions into the model using source code. [gee that sounds abstract…]

module MetaModelModule

  export MetaModel, dostuff!

  abstract MetaModel

  function dostuff!(model::MetaModel, data::Any) model end

end #MetaModelModule


module TargetModelModule
 
  importall MetaModelModule

  export TargetModel, dostuff!

  type TargetModel <: MetaModel
    x::Float64
  end

  function dostuff!(model::TargetModel, data::Float64)
    model.x += data
    return model
  end

end #TargetModelModule


module ModelUsingModule

  importall MetaModelModule

  function dosomething{M <: MetaModel}(model::M)

   dostuff!(model, 1.0)

  end

end

And using this:

julia> include("modmod.jl")
ModelUsingModule

julia> using TargetModelModule

julia> model = TargetModel(0.0)
TargetModelModule.TargetModel(0.0)

julia> ModelUsingModule.dosomething(model)
TargetModelModule.TargetModel(1.0)

julia> ModelUsingModule.dosomething("hey")
ERROR: MethodError: no method matching dosomething(::String)

Not investigated the performance implications (if any) of this.


#7

I’ve been watching this thread since the beginning, trying to understand its problem. Here is my take at it.

The general answer to the topic’s title is generated functions (an advanced topic), as:

These have the capability to generate specialized code depending on the types of their arguments

However, most of the problems around the types of a function’s arguments can be solved “simply” by multiple dispatch, where Julia handles the generation automatically (sometimes guided by parameters), while performing dynamic JIT compilation. The various code posted throughout this thread apparently tries to solve such a problem, so the title of the topic could be changed to something more relevant, e.g. “A case of intermodule interface”.

Accordingly, the latest version of the code gives a solution based solely on multiple dispatch, all function generation being handled on demand by Julia, not by user code. And given that:
function dosomething{M <: MetaModel}(model::M)
isn’t different in practice than:
function dosomething(model::MetaModel)
the remaining parameter can be removed for an overall simple code result.


#8

It really isn’t. Generated functions aren’t allowed to emit the kind of closure code that the original post was trying produce. There’s no need to suggest a more complex solution to a question that wasn’t asked. There’s nothing that a (valid) generated function can compute that an ordinary function can’t easily also compute. The other answers above already answered the original question about why that code didn’t work as expected and how to modify it.

Modules don’t have any performance penalty :slight_smile:. Everything is always inside some module (even if that module is the outermost Main one), and creating submodules can be useful for avoiding naming-collisions, as you found in your solution above.


#9

There was no suggestion and it’s not even about needs. This isn’t a computer service in a Q&A format, it is interaction between humans. But I didn’t see that kind of treatment in other users and I’ve had plenty of it and in an orchestrated form, not just from @jameson. This is from Julia community’s standards:

Ask yourself if a comment or statement might make someone feel unwelcomed or like an outsider.

I’m obviously an outsider. I will think if it’s worthy my time anymore.

UPDATE: It’s not worthy.


#10

Thanks, interesting points folks.

My personal policy is to try and ask stupid questions only. Smart questions can usually be solved by reading the docs or raising an issue on some repo. I reach out when I don’t know quite what I’m looking for, and understand that sometimes people get it, and sometimes not :wink:


#11

It is an unwritten rule that if anyone suggest using a @generated function then jameson will say that it is not necessary. Just get used to it :slight_smile: