首先,我在 Julia 中创建了以下
Option
类型的尝试:
# building an Option-type
abstract type MyNothing end
struct None{T} <: MyNothing end
Option = Union{Some{T},None{T}} where {T}
我想构建它,使其能够忠实地模仿 Option 在 Scala 中的工作方式(以及 Maybe 在 Haskell 中的工作方式)。具体来说,我想确保
None
的 Option
部分保留有关 Some
可能持有的类型的信息。我不想陷入像空指针一样的 None
的陷阱(因此,首先使用 Option 的全部意义!)。
所以我的第一个问题是,有没有更好/更干净的方法来构造
Option
?
我的下一个问题是,在
fmap
的实现中,我必须评估传递给它的函数以确定适当的返回类型。我尝试使用Base.return_types
,但它太脆弱了。当我使用它时,很容易丢弃 Any
作为类型,这会破坏我的实现。
这是
fmap
的函数定义:
# implementation of Functor for Option
function fmap(X::Functor{Option}, obj, fun)
if typeof(obj) <: None
par_type_in = param_type(obj)
par_type_out = outtype(par_type_in, fun)
None{par_type_out}()
else
obj |> something |> fun |> Some
end
end
一些解释:
param_type
提取对象“持有”的通用参数类型outtype
返回函数的返回类型(给定输入类型和函数)outtype
发挥作用,我使用方法 default
创建了一个特征,该方法返回特定类型的默认值(谢谢你的想法,Rust!)。鉴于这是一个函数式编程主题,我并没有忘记我可以等效地创建一个 Monoid
特征。这引出了我的主要问题 - 有没有更好的方法来保持类型稳定性?正如我之前提到的,使用
Base.return_types
太不可靠了。我目前的方法有效,但感觉有点复杂。此外,当我指定的默认值是馈送到 fmap
的函数的边缘情况时,我当前的方法就会崩溃。
以下是一些示例函数调用:
julia> x = Some(7)
Some(7)
julia> f = t -> string(6*t)
#3 (generic function with 1 method)
julia> fmap(x,f)
Some("42")
julia> y = None{Int64}()
None{Int64}()
julia> fmap(y,f)
None{String}()
请注意,实现正确跟踪
None
的“包含”类型。
我的尝试的“最小”工作版本隐藏在 Go 游乐场中(就像保存和共享代码的快速简便的方法):
我期待 Julia 中会有/应该有一个更好、更可靠的内置方法来确定函数的返回类型。正如我之前提到的,我尝试使用
Base.return_types
但失败了。我最终采取的方法相当复杂,需要评估函数以确定返回类型。为了实现这一目标,我创建了一个 Default
特征,以便我可以为各种类型指定默认值。
我对代码增加的复杂性感到不满。我希望我只是迟钝,并且有更好/更干净的方法来实现相同的目标。
我想补充一下,以下堆栈溢出帖子与此相关,但并没有完全解决我想要的详细问题。
您的问题可能是因为 Julia 对多重分派的关注不需要编译器跟踪返回类型的知识来将方法识别为唯一,只需要函数参数即可。
为了确保您的返回类型位于函数的类型签名中,您可以像 Julia 的 print() 一样,不从可能返回 None 的函数中返回任何内容,而是通过某种对返回值的引用来返回值函数的签名。例如 fmap 可以变成
ret = [Option...(specify types here)]
function fmap(X::Functor{Option}, obj, fun, ret)
...
end
然后将 ret 的内容替换为函数返回时的返回值。这让 Julia 将您的返回类型合并到其函数签名中,这似乎就是您想要的。