【问题标题】:How do you define constants in Elixir modules?如何在 Elixir 模块中定义常量?
【发布时间】:2016-02-24 09:47:21
【问题描述】:

在 Ruby 中,如果要在类中定义常量,他们会使用全部大写来定义它们。例如:

class MyClass
  MY_FAVORITE_NUMBER = 13
end

你如何在 Elixir 中做到这一点?如果不存在这样的等价物,你如何解决 Elixir 中的幻数问题?

【问题讨论】:

    标签: elixir


    【解决方案1】:

    您可以在变量名前加上@:

    defmodule MyModule do
      @my_favorite_number 13
    end
    

    这里是docs

    【讨论】:

    • 但无法从模块外部访问@my_favorite_number
    • @lfx_cool 正确。如果你想从模块外部访问它,你可以定义一个返回其值的函数:def my_favorite_number, do: @my_favorite_number
    • 这是一个 getter 方法,如果我们有快捷方式来做到这一点,例如attr_reader :my_favorite_number
    • 我根本不会称它为“方法”。我将其称为返回值的函数。方法具有类的隐含范围;函数没有这样的隐含范围。默认情况下,方法可以访问类中存在的任何类变量。函数无法访问未传入或未在函数中定义的值。改变你的想法。
    • @OnorioCatenacci,公平地说,在这种情况下,它具有模块的隐含范围,而不是更好。对于来自 OO 的人来说,这会让你更加困惑,因为你学习了 FP,你被告知了纯函数,然后你得到了这个显然不是纯函数的示例。它不接受任何参数并返回基于模块状态的内容。虽然我喜欢 Elixir,而且我认为副作用很难避免(例如 IO),但是在 Ruby 中带有“@”之类的实例变量的模块属性会让人更加困惑。
    【解决方案2】:

    另一种定义常量的方法是我使用 wxErlang 头文件的方法。也就是说,您可以简单地定义一个为您返回常量值的单行函数。像这样:

      def wxHORIZONTAL, do: 4
      def wxVERTICAL, do: 8
      def wxBOTH, do: (wxHORIZONTAL ||| wxVERTICAL)
    

    这是来自同一源代码的另一个示例:

     # From "defs.h": wxDirection
      def wxLEFT, do: 16
      def wxRIGHT, do: 32
      def wxUP, do: 64
      def wxDOWN, do: 128
      def wxTOP, do: wxUP
      def wxBOTTOM, do: wxDOWN
      def wxNORTH, do: wxUP
      def wxSOUTH, do: wxDOWN
      def wxWEST, do: wxLEFT
      def wxEAST, do: wxRIGHT
      def wxALL, do: (wxUP ||| wxDOWN ||| wxRIGHT ||| wxLEFT)
    

    如您所见,它使根据另一个常量定义一个常量变得更容易一些。当我想在不同的模块中使用这些常量时,我​​需要做的就是在模块顶部输入require WxConstants。这使得在一个地方定义一个常量并在其他几个地方使用它变得更加容易——对 DRY 有很大帮助。

    顺便说一句,如果你好奇的话,你可以看看 repo here

    正如我所说,我添加这个答案主要是为了完整性。

    【讨论】:

    • 这很酷,但有时我想要一个作为计算值的常数,这显然会导致函数记忆stackoverflow.com/questions/35465306/… 的兔子洞。在需要时了解一下很有用。
    • 我不确定你所说的 computed_value 是什么意思——你所说的计算值和上面的 wxALL 有什么区别?
    • wxALL 是常量的 OR,而我想的是一个方法调用,比如Time.now
    【解决方案3】:

    环顾 github,我发现这对我很有吸引力。允许从其他模块访问常量。

    ModuleA
      @my_constant 23
    
      defmacro my_constant, do: @my_constant
    
    
    ModuleB
      Require ModuleA
      @my_constant ModuleA.my_constant
    

    又一次简单而迷人的灵丹妙药解决方案

    【讨论】:

    • 既然可以使用简单的函数,为什么还要创建宏?有什么具体的好处吗?
    【解决方案4】:

    也许你定义了一个常量模块文件,你可以在其中定义宏

    defmodule MyApp.Constants do
      defmacro const_a do
        quote do: "A"
      end
    end
    

    您可以通过将其导入任何其他模块来使用它

    defmodule MyApp.ModuleA do
      import MyApp.Constants
    
      def get_const_a do
        const_a()
      end 
    end
    

    好处还在于您不会产生任何运行时成本以及在匹配情况下使用它的优势

    case something do
      const_a() -> do_something
      _ -> do_something_else
    end
    

    【讨论】:

    • 老实说,这也是我找到的最佳解决方案。在模式匹配中包含常量的能力,尤其是在我定义函数时,让宏在这方面令人惊叹。
    • 为什么是qoute do 而不是do
    【解决方案5】:

    Elixir 模块可以有关联的元数据。元数据中的每个项目都称为一个属性,并通过其名称访问。您可以使用@name value 在模块内定义它。并且作为@name访问

    defmodule Example
      @site 'StackOverflow' #defining attribute
    
      def get_site do
        @site #access attribute
      end 
    end
    

    请记住,这仅适用于模块的顶层,您不能在函数定义中设置模块属性。

    【讨论】:

      【解决方案6】:

      我想补充一下我是如何开始做常量的,这类似于@Onorio Catenacci 的回答,但使用了引用:

      defmodule IbGib.Constants do
      
        @doc """
        Use this with `use IbGib.Constants, :ib_gib`
        """
        def ib_gib do
          quote do
            defp delim, do: "^"
            defp min_id_length, do: 1
            # etc...
          end
        end
      
        @doc """
        Use this with `use IbGib.Constants, :error_msgs`
        """
        def error_msgs do
          quote do
            defp emsg_invalid_relations do
              "Something about the rel8ns is invalid. :/"
            end
            # etc...
          end
        end
      
        @doc """
        When used, dispatch to the appropriate controller/view/etc.
        """
        defmacro __using__(which) when is_atom(which) do
          apply(__MODULE__, which, [])
        end
      end
      

      然后你像这样在要使用它们的模块顶部使用它:

      use IbGib.Constants, :ib_gib # < specifies only the ib_gib constants
      use IbGib.Constants, :error_msgs
      
      # ... then in some function
      Logger.error emsg_invalid_relations
      

      我了解 Phoenix 如何使用 MyApp.Web 导入/使用子句。我离 Elixir 专家还差得很远,但是通过这种方法,您可以只导入您想要的那些常量,而无需在它们前面加上任何命名空间/范围。这样,您就可以轻松挑选各个常量组。

      使用直接函数,(我认为)您必须将它们分解为多个模块,然后导入模块。

      我不知道这个与直接模块函数的优化后果,但我认为它非常简洁——尤其是在 Elixir 中“导入”事物的各种方式之间的练习(importuse、@ 987654325@, require 对于来自其他语言的初学者来说非常混乱,这是一个单一的 usingimport 语句)。

      编辑:我已将常量 def 声明更改为 defp。这是因为当多个模块import常量文件时,存在歧义冲突。将它们更改为私有范围的函数可以避免这种冲突。所以每个模块都有自己的相同常量的“私有副本”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-05-01
        • 1970-01-01
        • 2016-04-23
        • 1970-01-01
        • 1970-01-01
        • 2010-11-18
        • 2017-03-08
        相关资源
        最近更新 更多