Создание типа Maybe в Perl 6 - PullRequest
       22

Создание типа Maybe в Perl 6

8 голосов
/ 09 марта 2019

У меня есть много функций, которые могут не работать, но также имеют тип возвращаемого значения, определенный в их сигнатуре.Поскольку мне нравится, когда это возможно, определять типы переменных, я хочу определить для этого подмножество Maybe.Я придумал следующее:

subset Maybe is export of Mu where Mu | Failure;

Проблема в том, что Failure - это подкласс Mu, так что это будет соответствовать чему угодно и всему, когда я действительно хочу иметь возможностьсоответствовать одному определенному типу наряду с Failure динамически.Моей следующей мыслью было создание параметризованной роли для использования в качестве типа, поскольку я не хочу создавать подмножества для каждого отдельного типа, который также может быть Failure.Я представляю, что это выглядит примерно так:

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

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

my Maybe[Int] $foo = foo;

Только я понятия не имею, что мне нужно добавить к роли, чтобы сделать эту работу.Можно ли создать такую ​​роль?Если нет, есть ли другой способ создать тип, который будет делать то, что я хочу?

Ответы [ 3 ]

9 голосов
/ 09 марта 2019

Типы Perl6 уже являются типами Maybe.

Просто Perl6 набрал нули в отличие от большинства других языков с типами Maybe.


Это Maybe[Int] переменная:

my Int $a;
my Int:_ $a; # more explicit

Это содержит определенный Int:

my Int:D $a = …; # must be assigned because the default is not “definite”

Это содержит ноль Int:

my Int:U $a;

Обратите внимание, что Failure является подтипом Nil, поэтому даже подпрограммы, для которых указан тип возврата, могут их вернуть.
(Nil отличается от null или nil от других языков.)

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

my $foo = foo; # $foo contains the failure object

Nil действительно тип общего мягкого отказа. При присвоении переменной она просто сбрасывает ее на значение по умолчанию.

my Int $foo = 1;

$foo = Nil;

say $foo.perl; # Int
my Int:D $bar is default(42) = 1;

$bar = Nil

say $bar.perl; # 42

Типичное значение по умолчанию совпадает с типом.

my Int $foo;

say $foo.VAR.default.perl; # Int

Конкретный мягкий сбой будет возвращать объект типа

sub foo ( --> Int ){
  Bool.pick ?? 1 !! Int
}

Вот почему я сказал, что Nil - это « generic » программный сбой.


Обычно, если вы определяете тип переменной, вы хотите, чтобы она была этого типа. Поэтому ваш код должен немедленно пожаловаться, если он получает что-то другого типа.

Есть лучшие способы справиться с Failure.

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

with foo() -> Int:D $foo {
  … # use $foo here
} else -> $fail {
  … # use $fail here
}

Это работает, потому что 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)
  }
}

Или просто определенный оператор или //, если вам все равно, что это за ошибка.

my Int:D $foo = foo() // 1;

Вы можете даже использовать это, чтобы превратить Failure в Nil.

my Int:D $foo is default(42) = foo() // Nil;

Если вы действительно хотите подмножество, возможно, с ошибкой, я думаю, это должно работать:

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;

В настоящее время он не работает.

(Обратите внимание, что вы не должны иметь дело с Mu, если вам не нужно специально иметь дело с типами и значениями, которые находятся вне типа Any; например, Junction и IterationEnd.)

Что-то еще, что, вероятно, также должно работать:

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

my Maybe-Failure[Int] $foo;

Похоже, что он не работает по той же причине, что и другая.


Другой способ - создать новый тип класса, например subset.
То есть subset использует MOP, отличный от остальных классов в Perl6.

7 голосов
/ 10 марта 2019

TL; DR См. Собственный ответ @ Kaiepi для решения. Но каждый не родной тип в P6 уже автоматически представляет собой расширенный обнуляемый тип, который похож на расширенный тип Maybe. Так что это тоже нужно обсудить. Чтобы помочь структурировать мой ответ, я притворюсь, что это проблема XY , хотя это не так.

Решение Y

Я хочу определить Maybe subset, чтобы использовать для этого

См. Ответ @ Kaiepi.

Все не родные типы P6 уже схожи с типами Maybe

Решение subset излишне для того, что wikipedia определяет как тип Maybe, который сводится к:

None [или] исходный тип данных

Оказывается, что все неродные типы P6 уже похожи на расширенный тип Maybe.

Улучшение в том, что (P6 эквивалент a) None знает, с каким исходным типом данных он был связан:

my Int $foo;
say $foo        # (Int) -- akin to an (Int) None

Решение X

У меня есть много функций, которые могут не работать, но также есть тип возвращаемого значения, определенный в их сигнатуре.

Как вы, вероятно, знаете, если только не действует use fatal;, P6 сознательно позволяет подпрограммам возвращать сбои, даже если есть проверка типа возврата, которая явно не разрешает их. (A subset проверка типа возврата может явно отклонить их.)

Таким образом, учитывая, что проверка типа возврата Foo автоматически превращается во что-то похожее на subset с предложением where Failure | Foo, понятно, что вы решили приспособить это, создав соответствующее подмножество, чтобы вы могли принять результат при присвоении переменной.

Но, как мы надеемся, ясно из предыдущего обсуждения, может быть, лучше использовать встроенный аспект системы типов P6, который похож на типы Maybe.

A Nil может использоваться для обозначения так называемой доброкачественной ошибки. Таким образом, следующее работает для обозначения сбоя (как вы хотите сделать в некоторых из ваших подпрограмм) и устанавливают получающую переменную равной None (или, скорее, расширенному эквиваленту P6 единицы):

sub foo (--> Int) { Nil }
my Int $bar = foo;
say $bar; # (Int)

Таким образом, вы можете заменить вызовы на fail на return Nil (или просто Nil).

Можно представить себе прагму (называемую, скажем, failsoft), которая превращает все Failure с в доброкачественную неудачу Nil с:

use failsoft;
sub foo (--> Int) { fail }
my Int $bar = foo;
say $bar; # (Int)

Обнуляемые типы

Введение в Википедии о типах Maybe также гласит:

Отличное, но связанное понятие ... называется обнуляемыми типами (часто выражается как A?).

Самый близкий к P6 эквивалент синтаксиса Int?, используемый некоторыми языками для выражения обнуляемого Int, просто Int без знака вопроса. Ниже приведены допустимые ограничения типов:

  • Int - эквивалент P6 обнуляемого Int или Maybe Int

  • Int:D - эквивалент P6 ненулевого значения Int или Just Int

  • Int:U - P6 эквивалент Int ноль или (Int) None

(:D и :U называются смайликами типа по очевидной причине.:))

Продолжая, Википедия Обнуляемые типы страница говорит:

В статически типизированных языках обнуляемый тип имеет тип [a Maybe] (в терминах функционального программирования), тогда как в динамически типизированных языках (где значения имеют типы, а переменные - нет) ), эквивалентное поведение обеспечивается наличием единственного нулевого значения .

В P6:

  • Значения имеют типы - но переменные тоже.

  • Типы P6 похожи на расширенный тип Maybe (как объяснено выше) или на расширенный обнуляемый тип, где имеется столько значений None s или "null", сколько типов вместо того, чтобы иметь только одиночное None или нулевое значение.

(Итак, является ли P6 статически типизированным языком или динамически типизированным языком? Это на самом деле Помимо статического против динамического и вместо этого статического и динамического.)

Продолжение:

Примитивные типы, такие как целые числа и логические значения, обычно не могут быть нулевыми, но соответствующие типы, допускающие значения NULL (NULL, целые и NULL, соответственно) могут также принимать значение NULL.

В P6 все не собственные типы (например, тип с произвольной точностью Int) похожи на расширенные типы Maybe / nullable.

Напротив, все нативные типы (например, int - все строчные буквы) являются ненулевыми типами - то, что Википедия называет примитивными типами. Они не могут быть нулевыми или None:

my int $foo;
say $foo;    # 0
$foo = int;  # Cannot unbox a type object (int) to int

Наконец, возвращаясь к странице википедии Maybe:

Основное различие между [возможно] типами и обнуляемыми типами состоит в том, что [возможно] типы поддерживают вложение (Maybe (Maybe A) ≠ Maybe A), тогда как обнуляемые типы не (A?? = A?).

Встроенные типы P6 не поддерживают вложение таким образом без использования подмножеств. Таким образом, тип P6, в то время как сродни с расширенным типом Maybe, на самом деле является просто расширенным типом, допускающим обнуление.

6 голосов
/ 10 марта 2019

Ответ Брэда Гилберта указал мне правильное направление, в частности:

Другой способ - создать новый тип класса, например, подмножество. То есть подмножество использует другое MOP, чем остальные классы в Perl6.

Решение, которое я придумал, таково:

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
...