Pattern Matching в PascalABC.NET

Pattern Matching - это больше прерогатива функциональных языков программирования. Опять-таки, Pattern Matching есть в Haskell.

В последнее время Pattern Matching проникает и в языки вроде C#:

Pattern Matching напоминает расширенный case по типам.

Вот пример для начала:

type
  Line = class
  end;
  Rectangle = auto class
    X,Y,Width,Height: real;
  end;
  Circle = auto class
    X,Y,Radius: real;
  end;
  
begin
  var l := new List<Object>;
  l.Add(new Line);
  l.Add(new Circle(10,10,5));
  l.Add(new Rectangle(10,10,20,10));
  foreach var x in l do
    match x with
  Line(var ll): Println('Line S =',0);
  Circle(var c): Println('Circle S =',c.Radius*c.Radius*Pi);
  Rectangle(var r): Println('Rectangle S =',r.Width*r.Height);
    end;
end.  

Здесь в полиморфном списке вычисляются площади фигур. Переменная x в зависимости от динамического типа деконструируется в переменную соответствующего типа (Line, Circle или Rectangle). И затем находится площадь.

2 лайка

Однако, вкусняшка!

1 лайк

Оказывается, ещё можно делать так:

begin
  match new object with
    integer(i) when i > 5: ;
  end;
end.
1 лайк

И так:

 type
   Person = auto class
    
     name: string;
     age: integer;
     
     procedure Deconstruct(var name: string; var age: integer);
     begin
       name := self.name;
       age := self.age;
     end;
     
   end;

begin
  var p := new Person('Петр', 25);
  if p is Person(var name, var age) then
    Println(name, age);
end. 

Кстати, @Admin это нормально что между var name и var age можно ставить и запятую и точку с запятой?

Можно, пока я отвечу? Это совершенно нормально. Синтаксис языка Паскаль требует отделять формальные параметры в списке заголовка подпрограммы запятой, либо точкой с запятой. Запятая разделяет параметры, имеющие один тип и способ вызова. Если тип и/или способ вызова разный, требуется разделитель “точка с запятой”. При этом синтаксис не запрещает и каждый параметр отделять точкой с запятой - может быть, так программисту больше нравится)).

procedure p1(a,b:byte) := exit;

begin
  p1(0;0);//Ошибка: Встречено ';', а ожидалось ')'
end.

Я писал, если Вы обратите внимание, о

А Вы использовали при вызове в фактических.

Это ведь именно описание. Как в любом блоке. Сравните:

begin
  var a: integer;
  var b: integer
end.

и

begin
  var a, b: integer;
end.

Допустимы оба способа описания. Так и в подпрограммах.

2 лайка

Нормально. Можно даже var не ставить. Воспринимайте это как одну или несколько секций формальных параметров. Конечно полной аналогии нет, поэтому тут просто проектное решение, интуитивно нормальное.

Кстати в автоклассе деконструктор генерируется по полям ровно такой. Так что его можно не писать

2 лайка

И так, что же у нас нового… В примерах в коммите есть вот что:

begin
  var a := Arr(1, 9, 8, 7, 2, 3, 4, 5); 

  // Расширенный is
  if a is [1, .., var x, _, 5] then
    Println(x);

  // match .. with
  match a with
    [1, 9, 8, _, 2]: print(1);
    [.., var y, var x]: print(x + y);
    [_, .., _]: print(3);
  end;
end. 

_, похоже, значит что не важно какой там элемент.
.. - а это явно пропуск нескольких элементов.




type
  CardInfo = auto class
  public 
    cardNumber: string;
    cv: integer;
  end;

  Person = class

  public 
    name: string;
    age: integer;
    card: CardInfo;

    constructor(name: string; age: integer; card: CardInfo);
    begin
      self.name := name;
      self.age := age;
      self.card := card;
    end;

    procedure Deconstruct(var name: string; var age: integer; var card: CardInfo);
    begin
      name := self.name;
      age := self.age;
      card := self.card;
    end;
  end;

begin
  var p1 := new Person('Вася', 11, new CardInfo('12345671', 321));
  var p2 := new Person('Петя', 12, new CardInfo('12345672', 322));
  var p3 := new Person('Маша', 13, new CardInfo('12345673', 323));
  var personArr := Arr(p1, p2, p3);

  // Расширенный is
  if personArr is [Person(name1, 11, CardInfo(_, cv)), _, Person(name2, 13, _)] then 
    println(name1, cv, name2);

  // match .. with
  match personArr with
    [_, _, Person('Вася', age, _)]: print(age);
    [var p, .., Person('Маша', _, _)]: print((p as Person).name);
    [..]: print(1);
  end;
end. 

Значит, так же, известно что алгоритм рекурсивный, и работает для полей элементов которые были обработаны.
Дальше ещё видно что в классе Person могут быть поля, для которых тоже можно применить Pattern Matching вида [].
+ я ещё покопался в самом коде коммита, и там явно видно, алгоритм таки рекурсивный.

Первый вопрос:
Почему print((p as Person).name);, а не print(p.name);? Уже ведь известно что p это Person. Если это какое то ограничение - почему? Или же это просто пример такой, можно и print(p.name); писать?




type
  CardInfo = auto class
  public 
    cardNumber: string;
    cv: integer;
  end;

  Person = class
    name: string;
    age: integer;
    card: List<CardInfo> := new List<CardInfo>();

    constructor(name: string; age: integer; card: List<CardInfo>);
    begin
      self.name := name;
      self.age := age;
      self.card := card;
    end;

    procedure Deconstruct(var name: string; var age: integer; var card: List<CardInfo>);
    begin
      name := self.name;
      age := self.age;
      card := self.card;
    end;
  end;

begin
  var cards := new List<CardInfo>();
  cards.Add(new CardInfo('12345671', 321));
  cards.Add(new CardInfo('12345672', 322));
  cards.Add(new CardInfo('12345673', 323));
  cards.Add(new CardInfo('12345674', 324));

  var p := new Person('Вася', 21, cards);

  // match .. with
  match p with
    Person('Петя', _, _): print('Петя');
    Person('Вася', _, [_, _, var x]): print(x);
    Person(name, _, [CardInfo(_, 321), .., CardInfo(_, 324)]): print(name);
  end;
end. 

А вот и [] внутри другого деконструктора, содержащий ещё 1 деконструктор внутри.




begin
  var a := 3.14;
  match a with
    1.0, 2.0, 3.0: Println(1);
    3.13, 3.14, 3.15, 3.16: print(2);
    123.0: print(3);
  end;
end.

case здорового человека)))
Вот только прямое сравнение на равенство 2 значений типа real напрягает, так же нельзя делать!
Можно как то так сделать?:

begin
  var a := 3.14;
  match a with
    3.13..3.15: print(1);
    else print(2);
  end;
end.

Второй вопрос:
@Admin вы вроде ещё когда то говорили про такой синтаксис, ещё до того как первый паттерн мачинг был сделан:

begin
  var a := 3.14;
  var res := match a with
    1.0, 2.0, 3.0: 1;
    3.13, 3.14, 3.15, 3.16: 2;
    123.0: 3;
  end;
  res.Println;
end.

Что насчёт этого? Стоит ли ещё ждать, или может оно уже работает с этим коммитом?




begin
  match (1, 2, 'string') with
    (1, _, var x): Println(x);  
    (1, _, 'strin'): print(2);
    (_, _, 'string'): print(3);
  end;
end.

Деструктор кортежа… Ну, я бы удивился если бы его небыло))




type
  CardInfo = auto class
  public 
    cardNumber: string;
    cv: integer;
  end;

  Person = class
    name: string;
    age: integer;
    card: CardInfo;

    constructor(name: string; age: integer; card: CardInfo);
    begin
      self.name := name;
      self.age := age;
      self.card := card;
    end;

    procedure Deconstruct(var name: string; var age: integer; var card: CardInfo);
    begin
      name := self.name;
      age := self.age;
      card := self.card;
    end;
  end;

begin
  var a := new Person('Вася', 11, new CardInfo('12345678', 324));

  // Расширенный is
  if a is Person(_, var age, CardInfo(_, var cv)) then Println(age, cv);

  // match .. with
  match a with
    Person('Петя', 12, CardInfo('12345678', var cv)): Println(cv); 
    Person('Вася', _, CardInfo(cardNum, 324)): Println(cardNum);
    Person(_, _, CardInfo(_, x)): Println(x);
  end;
end. 

Конечно, для is-var это всё тоже пашет.




И 1 интересный код из тестов:

begin
  var a := Arr(1, 9, 8, 7, 2, 3, 4, 5); 
  var c: procedure := () -> begin
    match a with
      [.., 4, var x]: assert(x = 5);
    end;
  end;

  c;
end.

Вау, match дружащий с лямбдами!
Правда, тест то плохой, ибо если по какой либо причине match в лямбде будет заменятся чем то что никогда не выполняется - assert(false) никогда не произойдёт и тест пройдёт успешно. Лучше сделать так:

begin
  var a := Arr(1, 9, 8, 7, 2, 3, 4, 5);
  var res := 0;
  var c: procedure := () -> begin
    match a with
      [.., 4, var x]:
      begin
        assert(x = 5);
        res := 1;
      end;
    end;
  end;

  c;
  assert(res = 1);
end.

19 сообщений перенесены в тему Болталка PascalABC.NET

9 сообщений перенесены в новую тему: MrFresnel и его критика PascalABC.NET