Что такое катаморфизм и можно ли его реализовать в C # 3.0? - PullRequest
26 голосов
/ 13 октября 2008

Я пытаюсь узнать о катаморфизмах, и я прочитал статью Википедии и первые пару сообщений в серии тем для F # на Внутри F # блог.

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

Можно ли показать, что это сделано в C #, с использованием оператора Aggregate LINQ или другого метода более высокого порядка?

Ответы [ 5 ]

28 голосов
/ 13 октября 2008

LINQ Aggregate() только для IEnumerables. Катаморфизмы в целом относятся к схеме свертывания для произвольного типа данных. Таким образом, Aggregate() означает IEnumerables, что FoldTree (ниже) означает Trees (ниже); оба являются катаморфизмами для их соответствующих типов данных.

Я перевел часть кода в части 4 серии на C #. Код ниже. Обратите внимание, что эквивалентный F # использовал три символа меньше (для аннотаций параметров общего типа), тогда как этот код C # использует более 60. Это свидетельствует о том, почему никто не пишет такой код в C # - слишком много аннотаций типов. Я представляю код на случай, если он поможет людям, которые знают C #, но не F #, поиграть с этим. Но код в C # настолько плотный, что его очень сложно понять.

Учитывая следующее определение для двоичного дерева:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

class Tree<T>   // use null for Leaf
{
    public T Data { get; private set; }
    public Tree<T> Left { get; private set; }
    public Tree<T> Right { get; private set; }
    public Tree(T data, Tree<T> left, Tree<T> rright)
    {
        this.Data = data;
        this.Left = left;
        this.Right = right;
    }

    public static Tree<T> Node<T>(T data, Tree<T> left, Tree<T> right)
    {
        return new Tree<T>(data, left, right);
    }
}

Можно складывать деревья и, например, измерить, если два дерева имеют разные узлы:

class Tree
{
    public static Tree<int> Tree7 =
        Node(4, Node(2, Node(1, null, null), Node(3, null, null)),
                Node(6, Node(5, null, null), Node(7, null, null)));

    public static R XFoldTree<A, R>(Func<A, R, R, Tree<A>, R> nodeF, Func<Tree<A>, R> leafV, Tree<A> tree)
    {
        return Loop(nodeF, leafV, tree, x => x);
    }

    public static R Loop<A, R>(Func<A, R, R, Tree<A>, R> nodeF, Func<Tree<A>, R> leafV, Tree<A> t, Func<R, R> cont)
    {
        if (t == null)
            return cont(leafV(t));
        else
            return Loop(nodeF, leafV, t.Left, lacc =>
                   Loop(nodeF, leafV, t.Right, racc =>
                   cont(nodeF(t.Data, lacc, racc, t))));
    }

    public static R FoldTree<A, R>(Func<A, R, R, R> nodeF, R leafV, Tree<A> tree)
    {
        return XFoldTree((x, l, r, _) => nodeF(x, l, r), _ => leafV, tree);
    }

    public static Func<Tree<A>, Tree<A>> XNode<A>(A x, Tree<A> l, Tree<A> r)
    {
        return (Tree<A> t) => x.Equals(t.Data) && l == t.Left && r == t.Right ? t : Node(x, l, r);
    }

    // DiffTree: Tree<'a> * Tree<'a> -> Tree<'a * bool> 
    // return second tree with extra bool 
    // the bool signifies whether the Node "ReferenceEquals" the first tree 
    public static Tree<KeyValuePair<A, bool>> DiffTree<A>(Tree<A> tree, Tree<A> tree2)
    {
        return XFoldTree((A x, Func<Tree<A>, Tree<KeyValuePair<A, bool>>> l, Func<Tree<A>, Tree<KeyValuePair<A, bool>>> r, Tree<A> t) => (Tree<A> t2) =>
            Node(new KeyValuePair<A, bool>(t2.Data, object.ReferenceEquals(t, t2)),
                 l(t2.Left), r(t2.Right)),
            x => y => null, tree)(tree2);
    }
}

Во втором примере другое дерево реконструируется иначе:

class Example
{
    // original version recreates entire tree, yuck 
    public static Tree<int> Change5to0(Tree<int> tree)
    {
        return Tree.FoldTree((int x, Tree<int> l, Tree<int> r) => Tree.Node(x == 5 ? 0 : x, l, r), null, tree);
    }

    // here it is with XFold - same as original, only with Xs 
    public static Tree<int> XChange5to0(Tree<int> tree)
    {
        return Tree.XFoldTree((int x, Tree<int> l, Tree<int> r, Tree<int> orig) =>
            Tree.XNode(x == 5 ? 0 : x, l, r)(orig), _ => null, tree);
    }
}

И в этом третьем примере для рисования используется складывание дерева:

class MyWPFWindow : Window 
{
    void Draw(Canvas canvas, Tree<KeyValuePair<int, bool>> tree)
    {
        // assumes canvas is normalized to 1.0 x 1.0 
        Tree.FoldTree((KeyValuePair<int, bool> kvp, Func<Transform, Transform> l, Func<Transform, Transform> r) => trans =>
        {
            // current node in top half, centered left-to-right 
            var tb = new TextBox();
            tb.Width = 100.0; 
            tb.Height = 100.0;
            tb.FontSize = 70.0;
                // the tree is a "diff tree" where the bool represents 
                // "ReferenceEquals" differences, so color diffs Red 
            tb.Foreground = (kvp.Value ? Brushes.Black : Brushes.Red);
            tb.HorizontalContentAlignment = HorizontalAlignment.Center;
            tb.VerticalContentAlignment = VerticalAlignment.Center;
            tb.RenderTransform = AddT(trans, TranslateT(0.25, 0.0, ScaleT(0.005, 0.005, new TransformGroup())));
            tb.Text = kvp.Key.ToString();
            canvas.Children.Add(tb);
            // left child in bottom-left quadrant 
            l(AddT(trans, TranslateT(0.0, 0.5, ScaleT(0.5, 0.5, new TransformGroup()))));
            // right child in bottom-right quadrant 
            r(AddT(trans, TranslateT(0.5, 0.5, ScaleT(0.5, 0.5, new TransformGroup()))));
            return null;
        }, _ => null, tree)(new TransformGroup());
    }

    public MyWPFWindow(Tree<KeyValuePair<int, bool>> tree)
    {
        var canvas = new Canvas();
        canvas.Width=1.0;
        canvas.Height=1.0;
        canvas.Background = Brushes.Blue;
        canvas.LayoutTransform=new ScaleTransform(200.0, 200.0);
        Draw(canvas, tree);
        this.Content = canvas;
        this.Title = "MyWPFWindow";
        this.SizeToContent = SizeToContent.WidthAndHeight;
    }
    TransformGroup AddT(Transform t, TransformGroup tg) { tg.Children.Add(t); return tg; }
    TransformGroup ScaleT(double x, double y, TransformGroup tg) { tg.Children.Add(new ScaleTransform(x,y)); return tg; }
    TransformGroup TranslateT(double x, double y, TransformGroup tg) { tg.Children.Add(new TranslateTransform(x,y)); return tg; }

    [STAThread]
    static void Main(string[] args)
    {
        var app = new Application();
        //app.Run(new MyWPFWindow(Tree.DiffTree(Tree.Tree7,Example.Change5to0(Tree.Tree7))));
        app.Run(new MyWPFWindow(Tree.DiffTree(Tree.Tree7, Example.XChange5to0(Tree.Tree7))));
    }
}    
11 голосов
/ 13 октября 2008

Я больше читаю, в том числе исследовательскую работу Micorosft по функциональному программированию с катаморфизмами ("бананами") , и кажется, катаморфизм просто ссылается на любую функцию, которая берет список и обычно разбивает его на одно значение (IEnumerable<A> => B), например Max(), Min(), и в общем случае Aggregate(), все это будет катаморфизмом для списков ,

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

4 голосов
/ 14 января 2014

Ответ Брайана в первом абзаце правильный. Но его пример кода не отражает того, как можно решить подобные проблемы в стиле C #. Рассмотрим простой класс node:

class Node {
  public Node Left;
  public Node Right;
  public int value;
  public Node(int v = 0, Node left = null, Node right = null) {
    value = v;
    Left = left;
    Right = right;
  }
}

С этим мы можем создать дерево в основном:

var Tree = 
    new Node(4,
      new Node(2, 
        new Node(1),
        new Node(3)
      ),
      new Node(6,
        new Node(5),
        new Node(7)
      )
    );

Мы определяем универсальную функцию сгиба в пространстве имен Node:

public static R fold<R>(
  Func<int, R, R, R> combine,
  R leaf_value,
  Node tree) {

  if (tree == null) return leaf_value;

  return 
    combine(
      tree.value, 
      fold(combine, leaf_value, tree.Left),
      fold(combine, leaf_value, tree.Right)
    );
}

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

Теперь вместо записи:

public static int Sum_Tree(Node tree){
  if (tree == null) return 0;
  var accumulated = tree.value;
  accumulated += Sum_Tree(tree.Left);
  accumulated += Sum_Tree(tree.Right);
  return accumulated; 
}

Мы можем написать

public static int sum_tree_fold(Node tree) {
  return Node.fold(
    (x, l, r) => x + l + r,
    0,
    tree
  );
}

Элегантный, простой, проверенный тип, ремонтопригодный и т. Д. Простой в использовании Console.WriteLine(Node.Sum_Tree(Tree));.

Легко добавить новую функциональность:

public static List<int> In_Order_fold(Node tree) {
  return Node.fold(
    (x, l, r) => {
      var tree_list = new List<int>();
      tree_list.Add(x);
      tree_list.InsertRange(0, l);
      tree_list.AddRange(r);
      return tree_list;
    },
    new List<int>(),
    tree
  );
}
public static int Height_fold(Node tree) {
  return Node.fold(
    (x, l, r) => 1 + Math.Max(l, r),
    0,
    tree
  );
}

F # побеждает в категории краткости для In_Order_fold, но этого следует ожидать, когда язык предоставляет специальные операторы для создания и использования списков.

Резкое различие между C # и F #, по-видимому, связано с использованием в F # замыканий, которые действуют как неявные структуры данных для запуска оптимизации хвостового вызова. Пример в ответе Брайана также учитывает оптимизацию в F # для уклонения от реконструкции дерева. Я не уверен, что C # поддерживает оптимизацию хвостового вызова, и, возможно, In_Order_fold можно было бы написать лучше, но ни один из этих пунктов не имеет значения при обсуждении того, насколько выразительным является C # при работе с этими катаморфизмами.

При переводе кода между языками вам необходимо понять основную идею техники, а затем реализовать ее в терминах языковых примитивов.

Возможно, теперь вы сможете убедить своих коллег по C # более серьезно относиться к складкам.

3 голосов
/ 28 января 2010

Я понимаю, что это обобщение сгибов (то есть отображение структура многих значений в одном значение, включая список значений для другой список).

Я бы не сказал одно значение. Он отображает его в другую структуру.

Может быть, пример прояснит, скажем, суммирование по списку.

foldr (\ x -> \ y -> x + y) 0 [1,2,3,4,5]

Теперь это уменьшится до 15. Но на самом деле это можно рассматривать как отображение на чисто синтаксическую структуру 1 + 2 + 3 + 4 + 5 + 0. Просто язык программирования (в приведенном выше случае haskell) знает, как уменьшить вышеупомянутую синтаксическую структуру до 15.

По сути, катаморфизм заменяет один конструктор данных другим. В случае приведенного выше списка,

[1,2,3,4,5] = 1: 2: 3: 4: 5: [] (: оператор cons, [] ноль элемента) приведенный выше катаморфизм заменен на + и [] на 0.

Может быть обобщено для любых рекурсивных типов данных.

2 голосов
/ 20 июля 2012

У Брайана была отличная серия постов в его блоге. Также у Channel9 было хорошее видео . Для .Aggregate () нет синтаксического сахара LINQ, поэтому имеет ли значение определение метода LINQ Aggregate или нет? Идея, конечно, та же. Складывание над деревьями ... Сначала нам нужен Node ... возможно, можно использовать Tuple, но это более понятно:

public class Node<TData, TLeft, TRight>
{
    public TLeft Left { get; private set; }
    public TRight Right { get; private set; }
    public TData Data { get; private set; }
    public Node(TData x, TLeft l, TRight r){ Data = x; Left = l; Right = r; }
}

Затем в C # мы можем создать рекурсивный тип, даже это необычно:

public class Tree<T> : Node</* data: */ T, /* left: */ Tree<T>, /* right: */ Tree<T>>
{
    // Normal node:
    public Tree(T data, Tree<T> left, Tree<T> right): base(data, left, right){}
    // No children:
    public Tree(T data) : base(data, null, null) { }
}

Теперь я процитирую часть кода Брайана с небольшими изменениями в стиле LINQ:

  1. В C # Fold называется Aggregate
  2. LINQ-методы - это методы расширения, для которых элемент является первым параметром с ключевым словом this.
  3. Цикл может быть приватным

...

public static class TreeExtensions
{
    private static R Loop<A, R>(Func<A, R, R, Tree<A>, R> nodeF, Func<Tree<A>, R> leafV, Tree<A> t, Func<R, R> cont)
    {
        if (t == null) return cont(leafV(t));
        return Loop(nodeF, leafV, t.Left, lacc =>
                Loop(nodeF, leafV, t.Right, racc =>
                cont(nodeF(t.Data, lacc, racc, t))));
    }    
    public static R XAggregateTree<A, R>(this Tree<A> tree, Func<A, R, R, Tree<A>, R> nodeF, Func<Tree<A>, R> leafV)
    {
        return Loop(nodeF, leafV, tree, x => x);
    }

    public static R Aggregate<A, R>(this Tree<A> tree, Func<A, R, R, R> nodeF, R leafV)
    {
        return tree.XAggregateTree((x, l, r, _) => nodeF(x, l, r), _ => leafV);
    }
}

Теперь, использование вполне в стиле C #:

[TestMethod] // or Console Application:
static void Main(string[] args)
{
    // This is our tree:
    //     4 
    //  2     6 
    // 1 3   5 7 
    var tree7 = new Tree<int>(4, new Tree<int>(2, new Tree<int>(1), new Tree<int>(3)),
                            new Tree<int>(6, new Tree<int>(5), new Tree<int>(7)));

    var sumTree = tree7.Aggregate((x, l, r) => x + l + r, 0);
    Console.WriteLine(sumTree); // 28
    Console.ReadLine();

    var inOrder = tree7.Aggregate((x, l, r) =>
        {
            var tmp = new List<int>(l) {x};
            tmp.AddRange(r);
            return tmp;
        }, new List<int>());
    inOrder.ForEach(Console.WriteLine); // 1 2 3 4 5 6 7
    Console.ReadLine();

    var heightTree = tree7.Aggregate((_, l, r) => 1 + (l>r?l:r), 0);
    Console.WriteLine(heightTree); // 3
    Console.ReadLine();
}

Мне все еще нравится F #.

...