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?

Trending in Questions Top

jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
silverdr
Using Phoenix.LiveView.TagEngine as an EEx.Engine is deprecated! To compile HEEx, use Phoenix.LiveView.TagEngine.compile/2 instead. Sta...
New
saveman71
Hello ! We want new/edit form pages to POST/PUT to their own URL rather than the resources REST defaults (post /things, put /things/:id)...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
michallepicki
I am using Oban and occasionally, shortly after a deployment, a handful of jobs can fail because of dependency on other parts of the syst...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New

We're in Beta

About us Mission Statement