触发了Julia类型系统的错误?

最近接触了Julia,觉得Julia的类型系统很强大。以前写过Haskell,就想把Haskell的东西搬过来,看看Julia能不能实现。
因为Julia中有类型变量,所以今天试着写了一个ST单子(其实我就想看一下Julia的类型变量到底和Haskell是不是一样,类型变量会不会逃逸出来),不想触发了一个编译器错误,代码是这样的:

struct STMonad{A,B}
	function STMonad()
		new{S,Ref{S}}() where S
	end
end

function runST(stm::STMonad{S,T},pho::T) where S where T
//一些其他东西,不影响
end

function userRunST(pho::T) where T
	stm = STMonad()
	runST(stm::STMonad{S,T},pho::T) where S
end

不管这个代码有没有实现ST单子(好吧,我承认应该是没有),主要思想就是STMonad内部构造器创造了一个类型为 {S,Ref{S}} 的STMonad值,这个值由runST运行,同时runST还要接受一个类型为T的值,可以推导出T为Ref{S}的类型,但是S是一个不定的类型变量,所以类型S就逃逸了出来,所以应该是不安全的。
总之,在构造STMonad的实例时(就是调用STMonad()的时候),编译器报错了,但是我不清楚到底是什么原因导致的,也不知道之前有没有人报告过这个错误:

Please submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.
Exception: EXCEPTION_ACCESS_VIOLATION at 0x6b5cf117 -- jl_new_structv at /home/Administrator/buildbot/worker/package_win64/build/src\datatype.c:774
in expression starting at no file:0
jl_set_nth_field at /home/Administrator/buildbot/worker/package_win64/build/src\datatype.c:889 [inlined]
jl_new_structv at /home/Administrator/buildbot/worker/package_win64/build/src\datatype.c:781
Type at .\REPL[9]:3
userRunST at .\REPL[16]:2
unknown function (ip: 000000001D94153A)
jl_fptr_trampoline at /home/Administrator/buildbot/worker/package_win64/build/src\gf.c:1829
jl_apply_generic at /home/Administrator/buildbot/worker/package_win64/build/src\gf.c:2182
do_call at /home/Administrator/buildbot/worker/package_win64/build/src\interpreter.c:324
eval_value at /home/Administrator/buildbot/worker/package_win64/build/src\interpreter.c:428
eval_stmt_value at /home/Administrator/buildbot/worker/package_win64/build/src\interpreter.c:363 [inlined]
eval_body at /home/Administrator/buildbot/worker/package_win64/build/src\interpreter.c:682
jl_interpret_toplevel_thunk_callback at /home/Administrator/buildbot/worker/package_win64/build/src\interpreter.c:799
unknown function (ip: FFFFFFFFFFFFFFFE)
unknown function (ip: 00000000112B2F8F)
unknown function (ip: FFFFFFFFFFFFFFFF)
jl_toplevel_eval_flex at /home/Administrator/buildbot/worker/package_win64/build/src\toplevel.c:787
jl_toplevel_eval_in at /home/Administrator/buildbot/worker/package_win64/build/src\builtins.c:622
eval at .\boot.jl:319
jl_apply_generic at /home/Administrator/buildbot/worker/package_win64/build/src\gf.c:2182
eval_user_input at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.0\REPL\src\REPL.jl:85
macro expansion at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.0\REPL\src\REPL.jl:117 [inlined]
#28 at .\task.jl:259
jl_fptr_trampoline at /home/Administrator/buildbot/worker/package_win64/build/src\gf.c:1829
jl_apply_generic at /home/Administrator/buildbot/worker/package_win64/build/src\gf.c:2182
jl_apply at /home/Administrator/buildbot/worker/package_win64/build/src\julia.h:1536 [inlined]
start_task at /home/Administrator/buildbot/worker/package_win64/build/src\task.c:268
Allocations: 3927324 (Pool: 3926665; Big: 659); GC: 7

对Julia也不是特别熟悉,不过既然出Bug了那应该就不只是我的问题了。不知道有没有能够实现一下这个STMonad,感觉Julia的类型系统很强,应该做得到。
新人第一次问问题,请各位高手多加体谅与帮助!

struct STMonad{A,B}
	function STMonad()
		new{S,Ref{S}}() where S
	end
end

没用过Haskell,也不知道什么是ST单子,但仅从Julia来看,这里定义了一个outer-only constructor STMonad(), 而new无法推导出S是什么,导致崩溃的原因应该是where的位置不对,一般用于函数定义,而不是调用。


另 1.0 命令行无法复现错误,直接执行也没有问题

你用的平台是?

我看他用的是cygwin,我在win 10 cmd以及debian的wsl下都能复现

Win10 + Julia 1.0
powershell 直接执行也没有问题

julia> versioninfo()
Julia Version 1.0.0
Commit 5d4eaca0c9 (2018-08-08 20:58 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.0 (ORCJIT, broadwell)
struct STMonad{A,B}
	function STMonad()
		new{S,Ref{S}}() where S
	end
end

function runST(stm::STMonad{S,T},pho::T) where S where T
# 一些其他东西,不影响
end

function userRunST(pho::T) where T
	stm = STMonad()
	runST(stm::STMonad{S,T},pho::T) where S
end

输出

julia> struct STMonad{A,B}
           function STMonad()
                   new{S,Ref{S}}() where S
                       end
                       end

julia>

julia> function runST(stm::STMonad{S,T},pho::T) where S where T
       # 一些其他东西,不影响
       end
runST (generic function with 1 method)

julia>

julia> function userRunST(pho::T) where T
           stm = STMonad()
               runST(stm::STMonad{S,T},pho::T) where S
               end
userRunST (generic function with 1 method)

julia>

补充:

inner constructor的常见写法是new后面不加{}, 此例中:

struct STMonad{S,T<:Ref{S}}
	STMonad{S,T}() where {S,T<:Ref{S}} = new()
        # 也等价于
        STMonad{S,T}() where {S,T<:Ref{S}} = new{S,T}()
end

julia> STMonad{Int,Ref{Int}}   # isconcretetype(STMonad{Int,Ref{Int}}) => true
STMonad{Int64,Ref{Int64}}

julia> STMonad{Int,Ref{Int}}()  # concrete type可以实例化
STMonad{Int64,Ref{Int64}}()

julia> STMonad{S,Ref{S}} where S  # 这是UnionAll
STMonad{S,Ref{S}} where S

julia> STMonad{S,Ref{S}}()   # UnionAll不是concrete type, 无法实例化
ERROR: UndefVarError: S not defined
Stacktrace:
 [1] top-level scope at none:0

julia> STMonad{S,Ref{S}}() where {S}
ERROR: UndefVarError: S not defined
Stacktrace:
 [1] (::Type{STMonad{S,Ref{S}}})() at ./REPL[1]:4
 [2] top-level scope at none:0

outer-only constructor由于构造函数的signature没有{},所以必须写在new后面,请参考文档这个例子 https://docs.julialang.org/en/latest/manual/constructors/#Outer-only-constructors-1

但如果STMonad没有输入参数,那如何推断出S是什么呢?

得调用一下 STMonad()

我在master上试了也是一样崩溃,可以给Julia报个issue,这个地方至少应该报error,不应该直接崩溃。

我可以回答你这最后一个问题,这个S的类型是不需要知道的,因为这个S是一个用不到的类型(不会用到这个值),它存在的唯一意义,就是保证类型的安全,所以要让它一直保持unionall(这就是为什么我要把它设计为无参数的,就是为了保证S不会被推导出来)不过我想的不一定能实现的了。

@Gnimuc 提 issue 了 deprecated syntax cause a crash in Julia 1.0 · Issue #29145 · JuliaLang/julia

熟悉 Julia / Haskell 可以去留言讨论,我两边都不熟,就只能提 issue 了。


0.6.4 没有问题

julia> struct STMonad{A, B}
           function STMonad()
               new{S, Ref{S}}() where S
           end
       end

WARNING: deprecated syntax "inner constructor STMonad(...) around REPL[1]:3".
Use "STMonad{A,B}(...) where {A,B}" instead.

julia> STMonad()
ERROR: MethodError: no method matching STMonad()

这么神奇??但为什么显示no methodError呢??

因为旧语法 STMonad 定义的是 STMonad{A,B} 的方法, 不是 STMonad 的. STMonad{Int,Int}() 才可以调用你定义的这个函数.

你想要构造的类型是 STMonad{S,Ref{S}} where {S}, 这不是个可以构造的类型. STMonad{Tuple{S,Ref{S} where {S}} 是个可以存在的类型.

2 个赞

哦,对,抽象类型不能实例化的,但是含有类型变量的类型本身是值,可以用作UnionAll的值,我懂了。谢谢!

已修复 Invalid syntax cause a crash in Julia 1.0 · Issue #29145 · JuliaLang/julia

1 个赞