[LINQ] cons, car, cdr
От: SE Украина  
Дата: 16.10.08 11:38
Оценка:
Вчера меня озадачили вопросом:
А как в .NET/C# реализовать знакомые всем функциональщикам примитивы cons, car и cdr. Причем вычисления должны быть линивыми, по возможности не типизированными.
Не обязательно LINQ, просто это первое что пришло в голову.

Вот пример на Scheme взятый из SICP, 2.1 Introduction to Data Abstraction:

(define x (cons 1 2))
(define y (cons 3 4))
(define z (cons x y))
(car (car z))
1
(car (cdr z))
3


Вот то, что получилось у меня на С#

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqCons
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = from value in new List<int> { 1, 2 } select value;
            var y = from value in new List<int> { 3, 4 } select value;
            var z = from value in new List<IEnumerable<int>> { x, y } select value;
            Console.WriteLine(z.First().First()); // Prints 1
            Console.WriteLine(z.Last().First()); // Prints 3
        }
    }
}

Но как-то оно коряво? и не уверен, что лениво. Эксперты, помогите, плз
Re: [LINQ] cons, car, cdr
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.10.08 12:18
Оценка:
Здравствуйте, SE, Вы писали:

SE>Вчера меня озадачили вопросом:

SE>А как в .NET/C# реализовать знакомые всем функциональщикам примитивы cons, car и cdr. Причем вычисления должны быть линивыми, по возможности не типизированными.
SE>Не обязательно LINQ, просто это первое что пришло в голову.

car — .First(), cdr — Skip(1), cons не существует ибо нету кортежей.
Re[2]: [LINQ] cons, car, cdr
От: Lloyd Россия  
Дата: 16.10.08 12:45
Оценка:
Здравствуйте, gandjustas, Вы писали:

SE>>Не обязательно LINQ, просто это первое что пришло в голову.


G>car — .First(), cdr — Skip(1), cons не существует ибо нету кортежей.


причем тут кортежи?
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Re[3]: [LINQ] cons, car, cdr
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.10.08 13:18
Оценка: -3
Здравствуйте, Lloyd, Вы писали:

L>Здравствуйте, gandjustas, Вы писали:


SE>>>Не обязательно LINQ, просто это первое что пришло в голову.


G>>car — .First(), cdr — Skip(1), cons не существует ибо нету кортежей.


L>причем тут кортежи?

cons создает кортеж.
Re[4]: [LINQ] cons, car, cdr
От: Lloyd Россия  
Дата: 16.10.08 13:23
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>>>car — .First(), cdr — Skip(1), cons не существует ибо нету кортежей.


L>>причем тут кортежи?

G>cons создает кортеж.

Тогда First и Skip — неправильные ответы, т.к. они работают с последовательностями.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Re[2]: [LINQ] cons, car, cdr
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.10.08 13:27
Оценка: 5 (1) -1
Здравствуйте, gandjustas, Вы писали:

G>Здравствуйте, SE, Вы писали:


SE>>Вчера меня озадачили вопросом:

SE>>А как в .NET/C# реализовать знакомые всем функциональщикам примитивы cons, car и cdr. Причем вычисления должны быть линивыми, по возможности не типизированными.
SE>>Не обязательно LINQ, просто это первое что пришло в голову.

G>car — .First(), cdr — Skip(1), cons не существует ибо нету кортежей.

На самом деле обманул.
car — первый элемент кортежа, cdr — второй.
Так как в C# кортежей нет, то и аналогов нет.

Списки в lisp\scheme состоят из вложенных кортежей, поэтому применительно к спискам car — "голова", а cdr — "хвост".
В C# аналогично работают .First() и .Skip(1)
Re[4]: [LINQ] cons, car, cdr
От: SE Украина  
Дата: 16.10.08 13:28
Оценка:
Здравствуйте, gandjustas, Вы писали:

SE>>>>Не обязательно LINQ, просто это первое что пришло в голову.


G>>>car — .First(), cdr — Skip(1), cons не существует ибо нету кортежей.


L>>причем тут кортежи?

G>cons создает кортеж.

Ну вот цель как раз хотя бы эмулировать в .NET кортежи, если уж их не имеется.
Re: [LINQ] cons, car, cdr
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 16.10.08 14:00
Оценка: 10 (1) +2
Здравствуйте, SE, Вы писали:

SE>А как в .NET/C# реализовать знакомые всем функциональщикам примитивы cons, car и cdr. Причем вычисления должны быть линивыми, по возможности не типизированными.


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

cons : (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1));
car : seq => seq.First()
cdr : seq => seq.Skip(1).First()
... << RSDN@Home 1.2.0 alpha 4 rev. 1111 on Windows Vista 6.0.6001.65536>>
AVK Blog
Re: [LINQ] cons, car, cdr
От: Spiceman  
Дата: 16.10.08 14:17
Оценка: 47 (2)
Здравствуйте, SE, Вы писали:

SE>Вчера меня озадачили вопросом:

SE>А как в .NET/C# реализовать знакомые всем функциональщикам примитивы cons, car и cdr. Причем вычисления должны быть линивыми, по возможности не типизированными.
SE>Не обязательно LINQ, просто это первое что пришло в голову.

SE>Вот пример на Scheme взятый из SICP, 2.1 Introduction to Data Abstraction:


В той же книге есть пример как сделать пару с помощью функций. Вот так оно выглядит на Lisp:
(define (cons x y)
(define (dispatch m)
   (cond ((= m 0) x)
      ((= m 1) y)
      (else (error "Аргумент не 0 или 1 -- CONS" m))))
    dispatch)
(define (car z) (z 0))
(define (cdr z) (z 1))


А вот так оно выглядит на C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LinqCons
{
    class Program
    {
        static object cons(object x, object y)
        {
            Func<int, object> f = (m) => { return m == 0 ? x : y; };
            return f;
        }
        static object car(object z)
        {
            return ((Func<int, object>)z)(0);
        }
        static object cdr(object z)
        {
            return ((Func<int, object>)z)(1);
        }
        static void Main(string[] args)
        {
            object x = cons(1, 2);
            object y = cons(3, 4);
            object z = cons(x, y);
            x = car(cdr(z)); // 3
        }
    }
}


PS. Сейчас как раз читаю эту книгу — класс!
Re[2]: [LINQ] cons, car, cdr
От: SE Украина  
Дата: 16.10.08 14:23
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>cons : (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1));

AVK>car : seq => seq.First()
AVK>cdr : seq => seq.Skip(1).First()

Спасибо
А теперь как бы еще повторить пример из SICP, который я привел в исходном собщении. Очевидно, что не хватает понятия пары в противовес последовательности.
Поясню: вложенный cons в Схеме дает ((1,2)(3,4)), в Линке (1,2,3,4). Соответсвенно, первый элемент во второй паре в Схеме — 3, в Линке на указанных аналогах операций — 2
Re[3]: [LINQ] cons, car, cdr
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.10.08 14:50
Оценка:
Здравствуйте, SE, Вы писали:

SE>Здравствуйте, AndrewVK, Вы писали:


AVK>>cons : (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1));

AVK>>car : seq => seq.First()
AVK>>cdr : seq => seq.Skip(1).First()

SE>Спасибо

SE>А теперь как бы еще повторить пример из SICP, который я привел в исходном собщении. Очевидно, что не хватает понятия пары в противовес последовательности.
SE>Поясню: вложенный cons в Схеме дает ((1,2)(3,4)), в Линке (1,2,3,4). Соответсвенно, первый элемент во второй паре в Схеме — 3, в Линке на указанных аналогах операций — 2

Наверное так:

var x = new[] {1,2};
var y = new[] {3,4};
var z = new[] {x,y};
//Ну дальше понятно
Re[2]: [LINQ] cons, car, cdr
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.10.08 16:38
Оценка: +1 -1
Здравствуйте, gandjustas, Вы писали:

G>car — .First(), cdr — Skip(1), cons не существует ибо нету кортежей.


Каких еще кортежей? Конс — это конструктор связанного списка. И вообще — это инструкции Лиспа по работе со связанными списками. Вот только Лисп на их базе все структуры данных строит. А в дотнете их даже нет. Вместо них используется IEnumerable<T> и итераторы ака yield.

Так что прямой ответ — нет таких фишек в дотнете и его библиотеках. Ну, а замена и правда может быть реализована на базе .First(), Skip(1) и итераторах. Вот только по жизни достаточно более высокоуровневых функций ака linq.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [LINQ] cons, car, cdr
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.10.08 16:40
Оценка: +2 -1
Здравствуйте, gandjustas, Вы писали:

G>cons создает кортеж.


cons создает элементы списка. nil — это пустой списко, а cons — конструктор элемента списка. Его задача добавить новый элемент в конец существующего списка. Кортежей в Лиспе вообще нет, так как Лисп язык динамически типизированный.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [LINQ] cons, car, cdr
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 16.10.08 16:42
Оценка:
Здравствуйте, SE, Вы писали:

SE>А теперь как бы еще повторить пример из SICP, который я привел в исходном собщении. Очевидно, что не хватает понятия пары в противовес последовательности.


В линке есть анонимные типы

SE>Поясню: вложенный cons в Схеме дает ((1,2)(3,4)), в Линке (1,2,3,4).


Тот аналог cons, который привел я, дает IEnumerable<Ienumerable<T>>, т.е. как и в лиспе. А чтобы привести его ко второму виду, нужно позвать SelectMany().
Ну а аналог приведенного тобой кода, в том числе и с учетиом ленивости, будет как то так:
using System;
using System.Collections.Generic;
using System.Linq;

static class Program
{
    static IEnumerable<T> Cons<T>(T x, T y)
    {
        yield return x;
        yield return y;
    }

    static T Car<T>(this IEnumerable<T> src)
    {
        return src.First();
    }

    static T Cdr<T>(this IEnumerable<T> src)
    {
        return src.Skip(1).First();
    }

    static void Main()
    {
        var x = Cons(1, 2);
        var y = Cons(3, 4);
        var z = Cons(x, y);
        Console.WriteLine(z.Car().Car());
        Console.WriteLine(z.Cdr().Car());
    }
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1111 on Windows Vista 6.0.6001.65536>>
AVK Blog
Re: [LINQ] cons, car, cdr
От: Аноним  
Дата: 16.10.08 16:44
Оценка: 5 (1)
Здравствуйте, SE, Вы писали:

SE>А как в .NET/C# реализовать знакомые всем функциональщикам примитивы cons, car и cdr. Причем вычисления должны быть линивыми, по возможности не типизированными.


В общем-то нужны просто туплы:
    public class Cons<TCar, TCdr>
    {
        public TCar car;
        public TCdr cdr;

        public Cons(TCar car, TCdr cdr)
        {
            this.car = car;
            this.cdr = cdr;
        }

        public TCar Car
        {
            get { return this.car; }
        }

        public TCdr Cdr
        {
            get { return this.cdr; }
        }
    }

     // Добавим вспомогательных функций чтобы было похоже на Scheme-ный синтаксис

        public static Cons<TCar, TCdr> Cons<TCar, TCdr>(TCar car, TCdr cdr)
        {
            return new Cons<TCar, TCdr>(car, cdr);
        }

        public static TCar Car<TCar, TCdr>(Cons<TCar, TCdr> cons)
        {
            return cons.Car;
        }

        public static TCdr Cdr<TCar, TCdr>(Cons<TCar, TCdr> cons)
        {
            return cons.Cdr;
        }


        static void Main(string[] args)
        {
            var x = Cons(1, 2);
            var y = Cons(3, 4);
            var z = Cons(x, y);

            Console.WriteLine(Car(Car(z)));
            Console.WriteLine(Car(Cdr(z)));

            // Структуры в SICP-пе бывают довольно "древовдными"...
            var p = Cons(x, z);
            Console.WriteLine(Car(Cdr(Cdr(p))));
        }


Ленивость можно изобразить так же как оно сделано в SICP:
    public class Lazy<T>
    {
        Func<T> calc;

        public Lazy(Func<T> calc)
        {
            this.calc = calc;
        }

        public T Force()
        {
            return calc();
        }
    }

   // Такие же модифицированные "stream"-операции

        public static Cons<TCar, Lazy<TCdr>> ConsStream<TCar, TCdr>(TCar car, Lazy<TCdr> cdr)
        {
            return new Cons<TCar, Lazy<TCdr>>(car, cdr);
        }

        public static TCar CarStream<TCar, TCdr>(Cons<TCar, Lazy<TCdr>> cons)
        {
            return cons.Car;
        }

        public static TCdr CdrStream<TCar, TCdr>(Cons<TCar, Lazy<TCdr>> cons)
        {
            return cons.Cdr.Force();
        }

  // Определим и Stream тада уж (это, в общем-то синоним Cons<TCar, Lazy<TCdr>>, но добавим IEnumerable чтобы LINQ-ом можно было обрабатывать)
    public class Stream<T> : Cons<T, Lazy<Stream<T>>>, IEnumerable<T>
    {
        public Stream(T current, Lazy<Stream<T>> next)
            : base(current, next)
        {
        }

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            var current = this;
            while(current != null)
            {
                yield return this.Car;
                if (this.Cdr != null)
                    current = current.Cdr.Force();
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }

        public static Stream<int> EnumerateInterval(int from, int to)
        {
            return from <= to ?
                new Stream<int>(from, new Lazy<Stream<int>>(() => EnumerateInterval(from + 1, to))) :
                null;
        }

        static void Main(string[] args)
        {
            Console.WriteLine(EnumerateInterval(1, 5).Sum());

            var interval = EnumerateInterval(1, int.MaxValue);
            Console.WriteLine(interval.Take(10).Sum());
        }


Получается конечно же просто переложение Scheme-вской имплементации ленивости — т.е. абсолютно неэффективное на С#
Re[2]: [LINQ] cons, car, cdr
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.10.08 16:50
Оценка: 14 (2)
Здравствуйте, AndrewVK, Вы писали:

AVK>Вот что меня всегда рабодало в тру фанкшнал языках, так это понятные названия функций и переменных.


AVK>cons : (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1));

AVK>car : seq => seq.First()
AVK>cdr : seq => seq.Skip(1).First()

Лисп как бы к фунциональным языкам имеет только то отношение, что он их породил. "тру фанкшнал" они никогда не был и никогда не будет.

Что до совершенно дурацких названий вроде car/cdr, тут свою роль с играла техника. Лисп ведь когда изобретался, то никакой терминологии еще не было. За-то была конкретная машина в который были две аппаратные инструкции car и cdr. Эти инструкции как раз позволяли реализовать (в одну инструкцию) извлечение значения первого элемента списка и получение ссылки на хвост списка. Ну, и товарищам ничего умнее в голову не пришло, чем тупо использовать название машинных команд для данных операций. "cons" имеет более разумное название, так как это функция консолидации, т.е. cons — это сокращение. Ее суть — добавление элемента в начало списка.

Так что все разумно. Ну, а в других ФЯ зачастую операции работы со списками имеют собственный синтаксис, так что в названиях вообще не нуждаются. Например, в Немерле получение головы списка производится или через свойство Head или с помощью паттерн-матчинга "firstElem :: _". Почти та же фигня во всех ML-клонах.

В прочем, с названиями в ФЯ действительно задница. Но она скорее наблюдается в программах на ФЯ, так как они зачастую пишутся в "математическом стиле" с однобуквенными, абстрактными именами переменных.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: [LINQ] cons, car, cdr
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 16.10.08 16:54
Оценка:
Здравствуйте, <Аноним>, Вы писали:

Ты это всерьез?
... << RSDN@Home 1.2.0 alpha 4 rev. 1111 on Windows Vista 6.0.6001.65536>>
AVK Blog
Re: [LINQ] cons, car, cdr
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.10.08 17:12
Оценка: 15 (1)
Здравствуйте, SE, Вы писали:

SE>Вот пример на Scheme взятый из SICP, 2.1 Introduction to Data Abstraction:


SE>
SE>(define x (cons 1 2))
SE>(define y (cons 3 4))
SE>(define z (cons x y))
SE>(car (car z))
SE>1
SE>(car (cdr z))
SE>3
SE>


В C# нет полных аналогов списков Лиспа. Так что полный аналог создать трудно. Точнее если ты реализуешь однонаправленный свяазанный список, то получишь полную копию (ну, может шума синтаксическго по больше будет). Но в принципе аналогом тут можно считать работу с последовательностями.
Напиши аналог cons из Лиспа и твои примеры будут выглядеть точно так же (с поправкой на синтаксис C#):
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
  public static IEnumerable<T> Cons<T>(T value1, T value2)
  {
    yield return value1;
    yield return value2;
  }

  static void Main()
  {
    var x = Cons(1, 2);
    var y = Cons(3, 4);
    var z = Cons(x, y);

    Console.WriteLine(z.First().First());
    Console.WriteLine(z.Last().First());
  }
}

Выводит точно так же:
1
3
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [LINQ] cons, car, cdr
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.10.08 17:16
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>
AVK>    static T Cdr<T>(this IEnumerable<T> src)
AVK>    {
AVK>        return src.Skip(1).First();
AVK>    }
AVK>


Небольшая поправочка. Cdr в Лиспе возвращает хвост списка. Просто в приведенном примере в хвосте остается только один элемент. Так что реализовывать его надо просто как Skip(1).
Проблема еще в том, что в Лиспе при выводе на консоль списка выводятся его значении, а в дотнете дурацкое имя класса итератора. Так что еще нужно писать свой аналог принта.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [LINQ] cons, car, cdr
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 16.10.08 17:32
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, gandjustas, Вы писали:


G>>cons создает кортеж.


VD>cons создает элементы списка. nil — это пустой списко, а cons — конструктор элемента списка. Его задача добавить новый элемент в конец существующего списка.


(cons x y), где x и y не nil, что создает? Список? А где его конец? Можно ли для него применить fold?
Тем не менее это вполне корректное выражение и создает корректный результат.

VD>Кортежей в Лиспе вообще нет, так как Лисп язык динамически типизированный.

Разве это мешает кортежам?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.