在Perl 6中创建Maybe类型

Kai*_*epi 8 types perl6 raku

我有很多可能会失败的函数,但是在其签名中定义了一个返回类型。由于我喜欢尽可能地定义变量的类型,因此我想定义一个Maybe子集用于此目的。我想到的是:

subset Maybe is export of Mu where Mu | Failure;
Run Code Online (Sandbox Code Playgroud)

这个的问题Failure是的子类Mu,因此当我真正想要的是能够Failure动态地匹配一种特定类型时,它将匹配所有内容。我的下一个想法是创建一个用作类型的参数化角色,因为我不想为也可以是的每个单个类型创建子集Failure。我想象它看起来像这样:

role Maybe[::T] {
    # ...
}

sub foo(--> Int) { rand < 0.5 ?? 1 !! fail 'oops' }

my Maybe[Int] $foo = foo;
Run Code Online (Sandbox Code Playgroud)

只有我不知道要完成这项工作需要添加什么角色。是否可以创建这样的角色?如果不是,是否还有另一种方法可以创建类型以执行所需的操作?

Bra*_*ert 9

Perl6类型已经是Maybe类型。

仅仅是Perl6键入了null,这与大多数其他具有Maybe类型的语言不同。


这是Maybe[Int]变量:

my Int $a;
my Int:_ $a; # more explicit
Run Code Online (Sandbox Code Playgroud)

这有一个确定的Int

my Int:D $a = …; # must be assigned because the default is not “definite”
Run Code Online (Sandbox Code Playgroud)

它为null Int

my Int:U $a;
Run Code Online (Sandbox Code Playgroud)

请注意,这Failure是的子类型Nil,因此即使指定了返回类型的子例程也可以返回它们。
Nil不像nullnil来自其他语言。)

sub foo ( --> Int:D ) { Bool.pick ?? 1 !! fail 'oops' }

my $foo = foo; # $foo contains the failure object
Run Code Online (Sandbox Code Playgroud)

Nil实际上是一种通用的软故障。当分配给变量时,它只是将其重置为默认值。

my Int $foo = 1;

$foo = Nil;

say $foo.perl; # Int
Run Code Online (Sandbox Code Playgroud)
my Int:D $bar is default(42) = 1;

$bar = Nil

say $bar.perl; # 42
Run Code Online (Sandbox Code Playgroud)

典型的默认值与类型相同。

my Int $foo;

say $foo.VAR.default.perl; # Int
Run Code Online (Sandbox Code Playgroud)

一个特定的软故障将是返回一个类型对象

sub foo ( --> Int ){
  Bool.pick ?? 1 !! Int
}
Run Code Online (Sandbox Code Playgroud)

这就是为什么我说的Nil是“ 通用 ”软故障。


通常,如果要定义变量的类型,则希望它具有该类型。因此,如果代码中包含其他类型的内容,则应立即进行投诉。

有更好的方法来处理Failure

sub foo(--> Int:D ) { rand < 0.5 ?? 1 !! fail 'oops' }

with foo() -> Int:D $foo {
  … # use $foo here
} else -> $fail {
  … # use $fail here
}
Run Code Online (Sandbox Code Playgroud)

之所以Failure可行,是因为始终将自己视为未定义。

您也可以将其与 when

given foo() {
  when Int:D -> Int:D $foo {
    … # use $foo here
  }
  when Failure:D -> Failure:D $fail {
    # a DEFINITE Failure value
    # (`DEFINITE` is different than `defined`.)
  }
  default {
    … # handle unexpected values (can be removed if not needed)
  }
}
Run Code Online (Sandbox Code Playgroud)

或者,//如果您不在乎它是哪种类型的失败,则只需定义或运算符。

my Int:D $foo = foo() // 1;
Run Code Online (Sandbox Code Playgroud)

您甚至可能希望使用它来将a Failure转换为Nil

my Int:D $foo is default(42) = foo() // Nil;
Run Code Online (Sandbox Code Playgroud)

如果您真的想要一个可能失败的子集,我认为这应该可行:

sub Maybe-Failure ( Any:U ::Type ) {
  anon subset :: of Any where Type | Failure
}

my constant Maybe-Int = Maybe-Failure(Int);

# note that the type has to be known at compile-time for this line:
my Maybe-Int $foo = foo;
Run Code Online (Sandbox Code Playgroud)

不过目前无法使用。

(请注意,Mu除非您需要专门处理类型之外的类型和值,否则您不应该进行处理Any;例如JunctionIterationEnd。)

还有一些可能也应该起作用的事情是:

my class Maybe-Failure {
  method ^parameterize ( $, Any:U ::Type ) {
    anon subset :: of Any where Type | Failure
  }
}

my Maybe-Failure[Int] $foo;
Run Code Online (Sandbox Code Playgroud)

这似乎由于与另一个原因相同的原因而失败。


另一种方法是创建一种新的类,例如subset
那就是subset使用与Perl6中其余类不同的MOP。


rai*_*iph 7

TL; DR有关解决方案,请参见@Kaiepi自己的答案。但是P6中的每个非本机类型都已经自动成为一种增强的可为空类型,类似于增强的Maybe类型。因此,这也需要讨论。为了帮助构造我的答案,我将假装这是一个XY问题,即使并非如此。

解Y

我想定义一个Maybe subset用于此

参见@Kaiepi的答案。

所有非本机P6类型都已经类似于Maybe类型

subset解决方案是大材小用什么维基百科定义为Maybe类型,其归结为:

None [或]原始数据类型

事实证明,所有非本机P6类型都已经类似于增强的Maybe类型。

增强之处在于(相当于P6)None知道它与之配对的原始数据类型:

my Int $foo;
say $foo        # (Int) -- akin to an (Int) None
Run Code Online (Sandbox Code Playgroud)

解X

我有很多可能会失败的函数,但是在其签名中定义了一个返回类型。

如您所知,除非use fatal;有效,否则P6会故意让例程返回失败,即使存在不明确允许它们的返回类型检查。(subset返回类型检查可以显式拒绝它们。)

因此,考虑到返回类型检查Foo会自动转换为类似于subset带有where Failure | Foo子句的a ,可以理解的是,您认为可以通过创建匹配的子集来适应这种情况,以便在分配给变量时可以接受结果。

但是,从前面的讨论中可以清楚地看到,最好使用类似于Maybe类型的P6类型系统的内置方面。

一个Nil可以被用来指示什么叫做良性失败。因此,以下工作可指示失败(如您希望在某些例程中所做的那样)并将接收变量设置为None(或等同于增强的P6):

sub foo (--> Int) { Nil }
my Int $bar = foo;
say $bar; # (Int)
Run Code Online (Sandbox Code Playgroud)

所以,一个选择是,你替换调用failreturn Nil(或只Nil)。

可以想象一种将所有s 降级为良性失败s的杂(例如failsoft):FailureNil

use failsoft;
sub foo (--> Int) { fail }
my Int $bar = foo;
say $bar; # (Int)
Run Code Online (Sandbox Code Playgroud)

可空类型

关于Maybe类型的Wikipedia简介还说:

一个独特但相关的概念...被称为空的类型(通常表示为A?)。

Int?某些语言用来表示可Int为空的语法所使用的最接近的P6 简单地是Int,没有问号。以下是有效的类型约束:

  • Int-P6等同于可为空IntMaybe Int

  • Int:D-P6等同于不可为空IntJust Int

  • Int:U-P6等于Intnull或(Int) None

:D并且:U有明显的原因被称为类型笑脸。:))

维基百科的Nullable类型页面继续说:

在静态类型的语言中,空的类型是[Maybe]类型(按功能编程术语而言),而在动态类型的语言(其中值具有类型,而变量没有),则通过具有单个null值来提供等效的行为。。

在P6中:

  • 值有类型-变量也有类型。

  • P6类型类似于增强Maybe型(如上所述)或增强的可空类型,其中Nones或“ null”值与类型一样多,而不仅仅是一个None或空值。

(因此,P6是静态类型的语言还是动态类型的语言?它实际上超越了静态动态,而是静态动态。)

继续:

基本类型(例如整数和布尔值)通常不能为null,但是相应的可为null的类型(分别为可为null的整数和可为null的布尔值)也可以采用该NULL值。

在P6中,所有非本机类型(如任意精度Int类型)都类似于增强的Maybe / nullable类型。

相反,所有本机类型(例如int-所有小写字母)都是非空类型-维基百科称之为原始类型。它们不能为null或None

my int $foo;
say $foo;    # 0
$foo = int;  # Cannot unbox a type object (int) to int
Run Code Online (Sandbox Code Playgroud)

最后,返回维基百科Maybe页面:

[也许]类型和可空类型之间的核心区别在于[也许]类型支持嵌套(Maybe (Maybe A) ? Maybe A),而可空类型不支持嵌套(A?? = A?)

P6的内置类型在不使用子集的情况下不支持这种嵌套。因此,P6类型类似于增强Maybe类型,实际上只是增强的可为空类型。


Kai*_*epi 6

布拉德·吉尔伯特的答案为我指明了正确的方向,尤其是:

另一种方法是创建一种新的类,例如子集。那就是子集使用与Perl6中其余类不同的MOP。

我想出的解决方案是这样的:

use nqp;

class Maybe {
    method ^parameterize(Mu:U \M, Mu:U \T) {
        Metamodel::SubsetHOW.new_type:
            :name("Maybe[{T.^name}]"),
            :refinee(nqp::if(nqp::istype(T, Junction), Mu, Any)),
            :refinement(T | Failure)
    }
}

my Maybe[Int] $foo = 1;
say $foo; # OUTPUT: 1
my Maybe[Int] $bar = Failure.new: 2;
say $bar.exception.message; # OUTPUT: 2
my Maybe[Int] $baz = 'qux'; # Throws type error
Run Code Online (Sandbox Code Playgroud)