GES233

GES233

Pattern can never match the type in DAG Scheduler build function

Hi everyone,

I’m currently building QyCore, a lightweight open-source DAG task scheduler and execution engine written in Elixir. The origin goal is to create a backend for node-based editors similar to ComfyUI, as shown in this thread earlier.

I’ve hit a wall with a persistent Dialyzer error that I can’t seem to resolve, even after cleaning builds and explicitly defining specs.

The Context

I have a Scheduler.build/2 function that validates a Recipe struct and returns {:ok, context} or {:error, reason}. It works perfectly at runtime, all tests passed, but Dialyzer is convinced that the {:ok, …} path is impossible.

The Error

When checking Executor.Async and Executor.Serial(Scheduler.build/2’s downstream), Dialyzer complains:

lib/qy_core/executor/async.ex:17:13:pattern_match
The pattern can never match the type.

Pattern:
{:ok, _ctx}

Type:

  {:error,
   {:cyclic, [any()]} | {:missing_inputs, map()} | {:option_validation_failed, [any()]}}
________________________________________________________________________________
...
________________________________________________________________________________
lib/qy_core/executor/serial.ex:15:13:pattern_match
The pattern can never match the type.

Pattern:
{:ok, _ctx}

Type:

  {:error,
   {:cyclic, [any()]} | {:missing_inputs, map()} | {:option_validation_failed, [any()]}}


________________________________________________________________________________

This implies that my Scheduler.build/2 function (shown below) is inferred to only return {:error, …}.

The Code

defmodule QyCore.Scheduler do
  # ...

  @spec build(Recipe.t(), initial_params()) ::
          {:ok, Context.t()} | {:error, term()}
  def build(%Recipe{} = recipe, initial_params) do
    # ... (initial map preparation) ...
    initial_keys = Map.keys(initial_map)

    # Dialyzer seems to think this `with` block never reaches the `do` block
    with :ok <- validate_step_option(recipe.steps),
         :ok <- Recipe.Graph.validate(recipe.steps, initial_keys) do
      do_build(recipe, initial_map)
    else
      {:error, reason} -> {:error, reason}
    end
  end

  # I added an explicit spec here hoping to fix it, but it didn't work.
  @spec validate_step_option([Step.t()]) :: :ok | {:error, {:option_validation_failed, list()}}
  defp validate_step_option(steps) do
    errors =
      steps
      |> Enum.with_index()
      |> Enum.reduce([], fn {step, idx}, acc ->
         # ... validation logic (returns list of errors) ...
         # If valid, `acc` remains []
         # If validator not exist, no error appended
      end)

    case errors do
      [] -> :ok
      _ -> {:error, {:option_validation_failed, errors}}
    end
  end

  defp do_build(recipe, initial_map) do
    # ... logic ...
    {:ok, %Context{...}}
  end

  # ...
end

What I’ve tried

  1. mix do clean, compile multiple times.
  2. Added explicit @spec to the private function validate_step_option/1.
  3. Verified that Recipe.Graph.validate/2 (in another module) has a spec that includes :ok as a return type.

It feels like Dialyzer’s success typing analysis determines that validate_step_option (or Graph.validate) can never return :ok, thus marking the do_build call as unreachable code.

Has anyone encountered this specific behavior where Dialyzer ignores an explicit :ok path in a reducer?

Any insights would be appreciated! Also, if you are interested in DAG scheduling in Elixir, feel free to check out the repo structure.

Thanks!


P.S. Warings were ignored by configure .dialyzer_ignore.exs. Hovewer, It seems trigger warning within ALL downstream functions which invoked Scheduler.build/2.

As anticipated, I want to leverage the community’s resources after showcasing the demo to create a more production-ready, fault-tolerant and Elixit-like Executor. But it seems necessary to manually declare that alarms should be ignored in all relevant modules of the new application.

Is there a more-elegant way to solve?

Marked As Solved

GES233

GES233

Problem solved, by refactoring executor’s API.

Now executor will receive a Scheduler.Context struct and execute work flow.

defmodule Orchid.Executor do
  @moduledoc """
  Executor behavoir.
  """

  @type executor :: module()
  @type executor_opts :: keyword()

  @type response :: {:ok, [Orchid.Param.t()]} | {:error, term()}

  @callback execute(Orchid.Scheduler.Context.t(), executor_opts()) ::
              response()
end

I also change the application’s name from qy_core into orchid, because it’s project-agnostic.

It also released on hex.pm, and I translated some comments and documents into English. This is a lightweight library, and its library has only about ~1k lines of code(exclude comments and test).

Anyway, thank you for your viewing, especially the group member who helped me in the elixir chat group.

Also Liked

dimitarvp

dimitarvp

Thank you for translating the README! I have checked your project at the time of your original post and bounced off when I saw it was in Chinese. Now I’ll check it out.

Where Next?

Popular in Questions Top

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New

We're in Beta

About us Mission Statement