【问题标题】:How do i use with statement in Elixir?如何在 Elixir 中使用 with 语句?
【发布时间】:2020-07-02 04:00:20
【问题描述】:

我有一个使用此代码使用其他所有功能的主要功能:

File.read!(file_path)
|> Sanitizer.sanitize_source()
|> IO.inspect(label: "\nSanitizer ouput")
|> Lexer.scan_words()
|> IO.inspect(label: "\nLexer ouput")
|> Parser.parse_program()
|> IO.inspect(label: "\nParser ouput")
|> CodeGenerator.generate_code()
|> Linker.generate_binary(assembly_path

但是任何时候函数返回一个错误,其余的都会导致程序崩溃。我的老师告诉我改用with 来解决这个问题,在阅读了文档后我想出了这个:

with {:ok, contentF} <- File.read!(file_path)
         sanitizedList when is_list(sanitizedList) <- Sanitizer.sanitize_source(contentF)
         {:ok, _sMessage} <- IO.inspect(sanitizedList, label: "\nSanitizer output")
         lexedList when not is_tuple(errorLex) <- Lexer.scan_words(sanitizedList)
         {:ok, _sMessage} <- IO.inspect(lexedList, label: "\nLexer output")
         parsedAST when not is_tuple(errorPar) <- Parser.parse_program(lexedList)
         {:ok, _sMessage} <- IO.inspect(parsedAST, label: "\nParser output")
         {:ok, codeAssembly} <- CodeGenerator.generate_code(parsedAST)
         :ok <- Linker.generate_binary(codeAssembly, assembly_path)
    do
      {:ok, "compilation complete"}
    else
      error -> {:error, "error: couldn't compile the file" <> file_path}
    end

根据我的阅读,这至少应该可以编译,但它给了我错误

(CompileError) lib/nqcc.ex:32: missing :do option in "with"
    lib/nqcc.ex:28: (module)

我不确定为什么,我确实有一个do 声明。

如果有帮助,这就是每个函数都应该返回的内容:

Sanitizer.sanitize_source() 应该返回一个元组列表

Lexer.scan_words() 应该返回一个元组列表或{:error, "message"}

Parser.parse_program() 应该返回一个 AST 或 {:error, "message"}

【问题讨论】:

    标签: elixir


    【解决方案1】:

    TL;DR: 错误是由with/1 子句之间缺少逗号引起的。此外,File.read!/1 返回内容或引发;要与 {:ok, _} 元组进行模式匹配,您应该使用 File.read/1(没有 bang。)通过这两个更正,代码应该可以工作。


    foo 应该返回 result{:ok, error} 元组。

    这是一个设计缺陷,使您无法以惯用、易读的方式使用with/1。无论该函数是否具有一元行为,都应该求助于返回 {:ok, result}{:error, reason} 元组。检查File.read/1 以获得灵感。

    因此,无论SanitizerLexerParser 是否属于您,请更新各自的函数以在这两种情况下返回元组。如果没有,请提供如下所示的帮助器

    defp idiomatize({:error, reason}), do: {:error, reason}
    defp idiomatize(result), do: {:ok, result}
    

    虽然您显然可以使用守卫实现此结果,但代码很快就会变得不可读和不支持。使用上面的助手,您可能会这样做

    with {:ok, contentF} <- File.read(file_path),
         {:ok, sanitizedList} <- idiomatize(Sanitizer.sanitize_source(contentF)),
         IO.inspect(sanitizedList, label: "\nSanitizer output"),
         {:ok, lexedList} <- idiomatize(Lexer.scan_words(sanitizedList)),
         IO.inspect(lexedList, label: "\nLexer output"),
         {:ok, parsedAST} <- idiomatize(Parser.parse_program(lexedList)),
         IO.inspect(parsedAST, label: "\nParser output"),
         {:ok, codeAssembly} <- CodeGenerator.generate_code(parsedAST),
         :ok <- Linker.generate_binary(codeAssembly, assembly_path)
        do
          {:ok, "compilation complete"}
        else
          {:error, error} ->
            {:error, "error: couldn't compile the file" <> file_path}
    end
    

    【讨论】:

      【解决方案2】:

      首先,您应该查看with/1 的文档。

      在 Elixir 中,函数通常需要变体,一个以感叹号结尾(如 File.read!/1)会引发错误,另一个没有感叹号(如 File.read/1)会在成功时返回 {:ok, result} 元组或 {:error, error} 元组失败。

      要有效地使用with,最好使用不带感叹号的版本,返回错误元组而不是引发错误。一旦你有了它,你就可以匹配成功的 {:ok, result} 元组。如果有错误,它将返回{:error, error} 元组。

      如果没有完整的功能实现,我无法测试您的代码,但它应该看起来像这样(不要忘记行尾的逗号!):

      with {:ok, contentF} <- File.read(file_path),
           {:ok, sMessage} <- Sanitizer.sanitize_source(contentF),
           {:ok, lexedList} <- Lexer.scan_words(sanitizedList),
           {:ok, parsedAST} <- Parser.parse_program(lexedList),
           {:ok, codeAssembly} <- CodeGenerator.generate_code(parsedAST),
           :ok <- Linker.generate_binary(codeAssembly, assembly_path) do
        {:ok, "compilation complete"}
      end
      

      如果您想调用IO.inspect 进行调试:

      with {:ok, contentF} <- File.read(file_path),
           {:ok, sMessage} <- Sanitizer.sanitize_source(contentF),
           IO.inspect(sMessage, label: "\nSanitizer ouput"),
           {:ok, lexedList} <- Lexer.scan_words(sanitizedList),
           IO.inspect(lexedList, label: "\nLexer ouput"),
           {:ok, parsedAST} <- Parser.parse_program(lexedList),
           IO.inspect(parsedAST, label: "\nParser ouput"),
           {:ok, codeAssembly} <- CodeGenerator.generate_code(parsedAST),
           :ok <- Linker.generate_binary(codeAssembly, assembly_path) do
        {:ok, "compilation complete"}
      end
      

      编辑:如果你不能编辑原始函数并且想使用守卫匹配它,它应该是这样的(同样,如果不能测试它就很难判断):

      with {:ok, contentF} <- File.read(file_path),
           sanitizedList when is_list(sanitizedList) <- Sanitizer.sanitize_source(contentF),
           IO.inspect(sanitizedList, label: "\nSanitizer output"),
           lexedList when not is_tuple(errorLex) <- Lexer.scan_words(sanitizedList),
           IO.inspect(lexedList, label: "\nLexer output"),
           parsedAST when not is_tuple(errorPar) <- Parser.parse_program(lexedList),
           IO.inspect(parsedAST, label: "\nParser output"),
           {:ok, codeAssembly} <- CodeGenerator.generate_code(parsedAST),
           :ok <- Linker.generate_binary(codeAssembly, assembly_path) do
        {:ok, "compilation complete"}
      end
      

      【讨论】:

      • 遗憾的是它没有用。我收到警告The pattern {'ok', _sanitizedList@1} can never match the type [binary()]。如果我忽略这一点,我会收到类似的 lexer、parser 和 codeGenerator 警告。
      • 那是因为您需要编写返回 {:ok, result} 元组的 Sanitizer.sanitize_source 等版本。
      • 难道不能用守卫来匹配吗?
      • @GiezyRamírez “我收到警告 The pattern [...]”——你应该清楚地了解 dialyzer(静态代码分析工具,不影响编译)之间的区别无论如何)和编译过程本身。警告来自透析器,你最好从简单的编译开始,错误信息会更简洁。稍后,当一切正常时,您可能会求助于使用透析器检查代码。
      • @GiezyRamírez 如果答案有帮助,您应该将其标记为正确并(可选)在提出与原始问题无关的进一步无关问题之前对其进行投票。如果您对其他任何事情有疑问,请随时发布另一个问题。 SO 的目标不是帮助您,而是创建知识库,这就是为什么您必须遵循规则:将正确的问题标记为正确并且不要一次问一堆不相关的问题。跨度>
      猜你喜欢
      • 1970-01-01
      • 2017-07-12
      • 1970-01-01
      • 2015-04-26
      • 1970-01-01
      • 1970-01-01
      • 2022-06-13
      • 2014-04-11
      • 1970-01-01
      相关资源
      最近更新 更多