String.split - FunctionClauseError no function clause matching in String.split/3

I am having a hell of a time trying to use String.split
with this string : {#FV<mss: “Market Support Specialist”>
and the code [x|_]=String.split(str, “:”)
and I keep getting the stupidest useless error:
FunctionClauseError) no function clause matching in String.split/3

notice I am using ONLY TWO arguments, not 3, and even if I were to use the [] as a third argument, it still gives the same f%$@#%@$%^#%^$%^&*n error.

I need to extract the string “mss” but because the string is MALFORMED and is not recognized as a proper string or a tuple or a list or a map or a struct and the idiot who wrote this crap is no longer with this company, I have to use this type of coding to remove just the data I need.

then there is the issue that when I’m able to get the string out, it comes out WITHOUT the double quotes which are needed to form a proper string so adding double quotes is an issue as well.

this is an area where elixir really sucks big time… and makes the learning curve way too steep an high, so far I’ve spend over 5 F@$%@#^$#^&#$N days trying to resolve this issue where in c++ or even javascript would have taken me an hour or less…

LMAO wyldin’, 5 days on this must suck I feel for you.

That string doesn’t seem like a string for some reason. {#FV<mss: “Market Support Specialist”> - even the quotes look sus

Do an IO inspect on whatever variable you’re trying to split and paste it here with code tabs. (backticks `)

I’m up to the proverbial you know what and usually rhymes with BEHIND in IO.puts and IO.inspects and all that jazz, even when trying to use iex, I can not enter the crap into a variable so I have to escape the quotes, colon and anything else iex deems annoying but that makes the crap not the real thing as in the program. that’s how I know that when I extract the string I need, it does not have the quotes… darn it…

PS: thanks for your very quick response, it’s really appreciated, that’s anther thing, THERE AIN’T NOBODY I can ask that knows anything at work…
also what you see is what I got to work with… that’s from an IO.inspect inside the program

Can you paste in some relevant code? We have nothing to go on

for your enjoyment:

def create_dependency(%{roles: users_roles, id: id}, default_role) do
    users_roles |> TS.logger("user roles")
    %{
      "required" => ["#{id}Role"],
      "properties" => %{
        "assignee" => %{
          "enum" => [id]
        },
        "#{id}Role" => %{
          "tag" => "#{id}Role",
          "default" =>
            users_roles
            |> Enum.map(&elem(&1, 0))
            |> Enum.find(users_roles |> List.first() |> elem(0), &Kernel.==(default_role, &1)),
          "title" => "Select Assignees Role",
          "type" => "string",
          "enum" => Enum.map(users_roles, &elem(&1, 0)),
          "enumNames" => Enum.map(users_roles, &elem(&1, 1)),
          "hidden" => length(users_roles) == 1
        }
      }
    }
    |> TS.logger("@391 looking for the user roles")
  end

defmodule Recurse do
	@moduledoc false

	def loopy([h|t]) do
	  IO.puts "Header: #{inspect(h)}"
		IO.puts "Tail: #{inspect(t)}"
		extract_enum_users_roles(h)
	  loopy(t)
	end

	def loopy([]), do: IO.puts "the end"

	defp extract_enum_users_roles(user_role) do
		user_role |> TS.logger("the h arg")

		[x|_]=String.split(user_role,":")
		IO.puts x
    # role_string =
    #   user_role
    # #   |> List.Chars.to_charlist()
    # #   |> List.to_tuple()
    #   |> elem(0) |> IO.puts
    #   # |> elem(1)
    # role_string = "[\"" <> String.Chars.to_string(role_string) <> "\"]"
    # role_string = role_string <> <<34>>
	#  role_string |> Atom.to_string()
  end


the line with the String.split(user_role, “:”) is the one giving me the error…

the TS.logger is a function with a bunch of IO.inspects and IO.puts which I wrote to display hidden code

well Sergio, thanks for your help…

Sergio, Additional code you requested:

Header: {#FV<mss: "Market Support Specialist">, "Market Support Specialist"}
Tail: []
the h arg
inspected data:
: {#FV<mss: "Market Support Specialist">, "Market Support Specialist"}

** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in String.split/3
        (elixir 1.11.4) lib/string.ex:473: String.split({#FV<mss: "Market Support Specialist">, "Market Support Specialist"}, ":", [])
        (aspire_api 0.0.1) lib/recurse.ex:16: Recurse.extract_enum_users_roles/1
        (aspire_api 0.0.1) lib/recurse.ex:7: Recurse.loopy/1

Ok so the error is telling you here that the value you are passing to String.split is not in fact a string, so it throws an error telling you that there is no function clause defined in String.split which can handle the value you’ve passed. So then let’s look at the value which was passed, and try and figure out what it is. We can see from the error line

(elixir 1.11.4) lib/string.ex:473: String.split({#FV<mss: "Market Support Specialist">, "Market Support Specialist"}, ":", [])

That the value is {#FV<mss: "Market Support Specialist">, "Market Support Specialist"}. This is 1) not a string, 2) not valid elixir code as is (so pasting it into iex will not work), and 3) actually a collection of two items. These two items are in a collection known as a tuple, which is identified by the curly brackets {}. Inside the tuple we have two elements, the first is a bit harder to explain so let’s look at the second first. It’s pretty easy, it’s the string "Market Support Specialist". This is valid elixir so far. The first item is #FV<mss: "Market Support Specialist"> which is the part that isn’t valid elixir code. What this is, is a convention for implementing the Inspect protocol for structs that have values in them which should not be printed to the console. What you have there is a struct of the type %FV{}, which presumably means something in your codebase if you look at the aliases. The previous developer appears to have decided that there are some values in that struct which should not be captured in the logs, presumably for data privacy reasons. You can pattern match on your tuple to get the struct value and string value into separate variables and then extract whatever data you need from the struct that way.

Best of luck!

Edit: the documentation on the inspect protocol mentions this convention that I alluded to. Might be helpful to read, as well as look at the protocol implementation for your own codebase. Inspect — Elixir v1.12.3

7 Likes

Superb answer @john-goff :clap:

1 Like

@John-Goff thank you for your great response, you hit the nail right on the head.
I went back to the code and looked for the “#FV” and found the Inspect protocol implementation like so:

defimpl Inspect do
    import Inspect.Algebra

    def inspect(%FieldValue{} = fv, opts) do
      value_doc = color(Atom.to_string(fv.value) <> ":", :atom, opts)
      label_doc = color(inspect(fv.label), :string, opts)

      concat(["#FV<", value_doc, " ", label_doc, ">"])
    end
  end

since I’m new to Elixir, it still goes way over my head, but the main problem is that other folks used the struct value to replace the old table values and now they do not compare because they are not equal and this code is difficult to search for to find out where it is being used or at least I’ve not been able to find out the source where these functions are being used.

Anywho: will continue to search, but the 5 days are now going into 7 and my boss is getting rancid because i’ve not come up with an answer…

Thanks again
:slight_smile:

@John-Goff after reading you response and extracting the above section I have a question: how do you pattern match on something like this, just use the string #FV<mss: as the source to the pattern matching function? as in a case statement: case "#FV<mss: " do -> action
Because everything I’ve tried so far has not worked and have not had any luck extracting a proper string portion at all that I can use, but that’s probably because of my lack of experience with elixir.

thanks again

You do not have a string value, matching against strings will not do what you want.

As @John-Goff pointed out, you’ve got a tuple with two elements, a FieldValue struct and a string. You might match it in a case like this:

case user_role do
  {%FieldValue{value: :mss} = field_part, string_part} ->
    # here field_part and string_part are bound
end
3 Likes

Exactly as @al2o3cr said, the #FV format is not something you can match on, because as I said it is not valid elixir code. In your code, you should use the syntax for structs, which is %StructName{key: :value}, in your case specifically it’s %FieldValue{value: :mss, label: "Market Support Specialist"}. If you paste that last value into iex it should print #FV<mss: "Market Support Specialist">, because it is reading the inspect implementation and then following that when printing the variable for us to view. Try changing the atom for value and the string for label and see how that affects what’s printed. Hopefully playing with this helps you understand the relation between the two.

2 Likes

wow, @al2o3cr that is awesome.
so that means that the %FieldValue as defined in the def inspect(%FieldValue{} = fv, opts) do inspect definition is accessible in the case statements, that is great.

thanks for the info, great learning experience…

one of the comments by @John-Goff was to use pattern matching to extract strings and sections, how can I do that or better yet do you have a good link where I can read about the subject.

Thanks again, :slight_smile:

@John-Goff it gives me an error:
iex(2)> %FieldValue{value: :mss, label: "Market Support Specialist"} ** (CompileError) iex:2: FieldValue.__struct__/1 is undefined, cannot expand struct FieldValue. Make sure the struct name is correct. If the struct name exists and is correct but it still cannot be found, you likely have cyclic module usage in your code

should I be doing any aliasing?

Yes you should, you haven’t shared the full module name as far as I remember so I don’t know what it’s supposed to be, but you need to either alias or use the fully qualified name (eg %MyApp.Context.FieldValue{})

1 Like

here it is:

defmodule FieldValue do
  @enforce_keys [:module, :field, :value, :recased, :label, :rank, :active?]
  defstruct @enforce_keys

  defimpl Inspect do
    import Inspect.Algebra

    def inspect(%FieldValue{} = fv, opts) do
      value_doc = color(Atom.to_string(fv.value) <> ":", :atom, opts)
      label_doc = color(inspect(fv.label), :string, opts)

      concat(["#FV<", value_doc, " ", label_doc, ">"])
    end
  end
end

1 Like

Ah so if the full name is FieldValue then the fact that iex can’t find it is likely due to you not loading your mix project into iex. I’m assuming this is a Phoenix project? If so, you want to start iex with iex -S mix phx.server. If it is not a Phoenix project, you can do iex -S mix instead. Alternatively, if you copy and paste the full FieldValue module into iex that would also work.

1 Like

thanks, I did load the project under iex -S mix and it is the only way I work with the app. however, did not use an alias, I just typed or copied and pasted the string on to the terminal.

anywho: will pick it up later, due up for meetings…

this is very interesting and very interested in continuing this conversation…

later dude…
:slight_smile:

Spare yourself some head-banging by reading these:

https://hexdocs.pm/elixir/patterns-and-guards.html#content