A platform can depend on a Model
, that is specified by the app. For example the wasm4 and the kingfisher platform do this.
To import the Model
, the platform requires
the Model. It is specified in the headers: requires { Model } { main : _ }
.
Is it currently possible for a module inside the platform (other then main.roc
) to import the Model? As far as I can see, the required
-header does not work. It is also not possible to import main.Model
.
What I am trying to do is to create a Task, that takes the Model
as an argument. For example updateModel : Model -> Task {} [UpdateFailed]
. For this to work at the moment, the hosted module has to use the model to create the Effect.
Is this currently possible?
@Agus Zubiaga Is this something, that will be possible with the new module headers of platform modules?
Yeah, I don’t think that’s quite possible. Can you show me an example where you’d use updateModel
? There might be another way to get to your desired functionality
So here is the draft code. I did not update the host, just the roc part: https://github.com/ostcar/kingfisher/compare/task
The effect is defined here: https://github.com/ostcar/kingfisher/compare/task#diff-2f519f2cdd03e9c119f1f1da69ec5681b2d6e762a2d059d0cd49c788fc520640R27-R31
The Task is defined here: https://github.com/ostcar/kingfisher/compare/task#diff-2f519f2cdd03e9c119f1f1da69ec5681b2d6e762a2d059d0cd49c788fc520640R27-R31
An example, using it, is here: https://github.com/ostcar/kingfisher/compare/task#diff-81772da4aabf8aca762cb72425207ba62abd414dece0c57719f8a87574002a38R49
Right. This is how I'd do it instead:
handleRequest : Request, Model -> Task (Model, Response) _
handleRequest = \request, model ->
when request.method is
Post ->
newModel =
if List.isEmpty request.body then
"World"
else
request.body
|> Str.fromUtf8
|> Result.withDefault "invalid body"
Task.ok (newModel, {
body: newModel |> Str.toUtf8,
headers: [],
status: 200,
})
_ ->
Task.ok (model, {
body: "Hello $(model)\n" |> Str.toUtf8,
headers: [],
status: 200,
})
Your task simply returns the new model in addition to the response
The Task
type itself doesn't have to care about the type of your Model
It can be a requirement of the handleRequest
function
If you split a request handler into multiple functions, you have to thread the Model
through, but that's also the case with the updateModel
API because you want to use the most recent one to apply edits on top of
I don't know if there's a reason you'd want to truly update the model in the middle of your handler. One of the benefits of not doing so is that you'd get an "automatic rollback" if the task fails.
and if you still need to update the Model
when the task fails, you can use Task.onErr
to do so
General side question: Why do we have requires for this? Can't it just be a type variable in the platform?
I also thought about it. One part of the idea was, to unify handleReadRequest
and handleWriteRequest
. It is not possible for a read request to update the model. In the case of my Task example, the updateModel
-Task would return an error on a GET-Request. Task (Model, Response) _
would only be possible on a handleWriteRequest
.
Another part of the idea is, that the host knows, if the model has changed or not. For example on a POST request, on a unknown path, the server should return a 404
and will (probably) not change the model. The host can not know, if the model has not changed. So the host has to do all is "model-save-stuff". So the type should be more like Task ([NotChanged, Changed Model], Response) _
. A Task would be nicer.
But I think that beside of my use case there might be a need in other cases as well. It seems strange, that you have functions in your platform/main.roc
, that you can not put into another file/module. Maybe there is another platform, that does more stuff on the roc side. Why should this platform has to have to have all of its code in platform/main.roc
. This is even more relevant, that it is currently not possible to export a function from main.roc
. As far as I know, an application can only import functions from modules in platform, an not from the main.roc
.
Brendan Hansknecht said:
General side question: Why do we have requires for this? Can't it just be a type variable in the platform?
What is a type variable in the platform? I imitated, what the wasm4
platform did. How would this type variable look in the wasm4 platform?
I read in another thread, that we have module parameters now. I am really exited to here this.
Will it be possible now, to give the Model
to the effect Module?
I can not find any example of the new module parameters. Can someone tell me, how the syntax looks like?
Here's the tests which show the syntax https://github.com/roc-lang/roc/tree/main/crates/cli/tests/module_params
Is it correct, that the hosted modules still use the old module header?
hosted Effect
exposes [...]
imports []
Is it possible to use module params on them? If not, is there a workaround or will this be possible in the future?
Module params are only for values and functions, not types. They are not a solution to this problem.
The main motivations for them are to allow packages to perform effects without depending on a specific platform and for effects to be simulated in tests.
By explicitly providing effects to a module, you can also easily tell everything it might do, and you can sandbox/adapt them by writing some code in the middle.
I believe #compiler development > sending unsized values to the host is the most recent discussion on a solution to what you’re describing
I read through the topic about sending unsized values to the host, but I don't get, how this could help me.
For me, it does not matter, if I send Model
or Box Model
. My problem is, that there is currently no syntax in roc, to define an Task with a generic argument. At least, as far as I can see.
Is this something, that will be possible in the future? I though I had to wait for module params to solve this. Is there another thing, I have to wait for? For example the effect interpreter, because with it, Tasks do not have to be defined inside an "hosted" module, but can be defined in the main.roc file?
I haven’t had time to read through the whole thing, but I think the idea is that Model
would just become a type variable you can send to the host (as long as its Boxed, so its type is statically known)
I am not so familiar with the term type variable
. What term would you use, that Model
currently is?
If Model
would get a type variable
, what would be the syntax to get it inside the hosted
module? Would it then be possible to use as a module parameter?
Oh, it just means a generic parameter (lowercase name) in an annotation, like:
updateModel : model -> Task model []
model
is a type variable.
You probably don’t need to return it back, but you get the point
Like I said before, I’m not up to date with this discussion, so I’m not sure what your use case is and whether it’ll be supported. I recommend bring it up in that topic.
Thank you. I will to this
I still not fully get it. The idea from @Brendan Hansknecht sounds good, but I don't think it fully solves my case.
I want to make another example: Take the basic-webserver. The example in the README says, that you can use the server.init
function, to set up a database connection. But what if this database connection has a timeout? Then you need a way to create a new database connection. For this, it would be nice, if basic-webserver would expose a Task like updateModel : Model-> Task {} {}
. When the connection expires, the application can create a new connection (like it did with server.init
) and then send the new connection to the host, so it is used on future calls to server.respond
.
If basic-webserver would get a feature request like this. How would you implement this updateModel
Task?
I don't think it is possible to do it with
Funcs model: {
init: {} -> Task model []
update : model -> Task model []
}
Becaues there also is the server.respond
function with this definition: respond : Http.Request, Model -> Task Http.Response [ServerErr Str]_
. I don't see, how you could tell roc, that the Model
in respond
has to have the same type as the model
in Funcs
.
Is there a way to solve this in current roc or in a planed future of roc?
Ah, I see what you are pointing out. I think that is dealt with by effect interpretters
In the effect interpretter world, all effects are actually tags that unify together
So updateModel : Model-> Task {} {}
would really be [UpdateModel (Box model) (Box (Result {} {} -> ...))]
. It would be one part of a greater tag that all effect build up together.
With roc today, that can not be done safely.
As such, it must be dealt with by the core platform api and not tasks
So in basic webserver, either special concrete type effects would be needed outside of model all together, or respond
would need to return an updated model
In the effect interpretter world, where can you define this Effect-Tag? Because with today's module syntax, the symbol Model
only exists in platform/main.roc. To define your example of UpdateModel
, it would also have to stay in the main.roc file. With today's syntax, it would be impossible, to define it somewhere else.
Is this the plan or should the module syntax change so it is possible to expose Model
to other modules?
If so, then do I really have to wait for the effect interpreter, or only for the update on the module syntax? Because if Model
could be imported from today's hosted
module, it would be possible to create the updateModel
Task without the effect interpreter.
Oh sure, today with some import fixes and a concrete Model
I think it should theoretically work.
I think in the future a concrete Model
wouldn't be allowed unless the platform defined the Model
type. It would have to be a type variable model
.
I'm not fully sure what our plan will be around defining the effect tag. But I know this is a case we will want to support.
I am not sure, what you mean with concrete Model
. Is that a Model where the type is known at compile?
What is not possible is, that basic-webserver's respond would return an updated model. Since respond is called in parallel. If you call respond N-times, it would return N different Models and therefore open N new connections to the database.
I can accept, that roc will not allow a Task, that accept a Model
in the foreseeable future. But I still think, it would be a useful feature.
Platforms host it built before the app, so it cannot know the size or shape of the Model as that is defined in the app.
So to the platform, the Model is abstract and not concrete.
Last updated: Jul 05 2025 at 12:14 UTC