![]() |
Технология Клиент-Сервер 2007'1 |
||||||
|
C# 3.0 (в настоящее время доступный в предварительной версии) – часть проекта LINQ [1]. Цель этого проекта состоит в том, чтобы внедрить лучшую поддержку работы с данными в господствующие языки программирования общего назначения, созданные в Microsoft для платформы .NET (C# и VB.Net).
Большинство идей, реализованных в C# 3.0, уже доступно в (главным образом) исследовательских и экспериментальных языках, ранее созданных в Microsoft Research. Два самых интересных языка, разработанных в Microsoft Research - Cw [2] и F# [3].
F# – это функциональный язык, основанный на ML (фактически он основан на языке OCaml, который добавляет несколько возможностей к Standard ML). Функциональные языки очень отличаются от императивных языков (большинство широко используемых языков – C++, Java и C# – императивны). Самый большой вклад F# состоит в том, что он показывает, как функциональные языки могут быть скомпилированы для .NET-рантайма (CLR), потому что .NET CLR был первоначально разработан для выполнения кода, написанного на императивных языках. Другая цель F# - способность к взаимодействию с другими языками на платформе .NET. Частью исследований, связанных с F#, является проект ILX [6], который показывает, как можно расширить .NET-рантайм, чтобы обеспечить лучше поддержку функциональных языков (например, функций как первоклассных значений).
Большинство нововведений C# 3.0 появилось под влиянием идей функционального программирования:
Пожалуй, все здесь, за исключением функций как первоклассных значений, не имеет непосредственного отношения к функциональному программированию, но действительно появилось или получило распространение в функциональных языках программирования – прим.ред.
Большинство этих возможностей, введенных в C# 3.0, очень ограничено по сравнению с их реализациями в F# и других функциональных языках, но очень интересно наблюдать, как возрастает важность концепций функционального программирования, и как от этого выигрывают не-функциональные языки.
Cw (читается Си-Омега) – это основанный на C# язык, расширяющий его в двух наиболее важных областях. Первая область – это лучшая поддержка работы со структурированными данными (XML) и реляционными данными (базы данных).
Вторая область – поддержка конструкций параллелизма как части языка. В большинстве широко используемых языков программирования поддержка параллелизма реализуется в виде библиотеки классов. Благодаря включению в язык таких конструкций программы становятся более удобочитаемыми (позволяя лучше выразить намерения программиста), и больше работы может быть выполнено компилятором автоматически.
В целом C# 3.0 и проект LINQ вдохновлены первой частью расширений Cw, и расширения синтаксиса в C# 3.0 очень близки к концепциям, появившимся в Cw. Самое большое различие состоит в том, что C# 3.0 обеспечивает лучшую расширяемость, позволяющую разработчикам обеспечить механизм для работы с различными источниками данных. Эта расширяемость является возможностью языка, а не компилятора, как в случае Cw. С другой стороны, некоторые расширения Cw были упрощены в C# 3.0 (например, анонимные типы, которые будут упомянуты позже), и я приведу примеры того, что можно сделать в Cw, но нельзя в C# 3.0.
Основная цель C# 3.0 – упростить работу с данными и позволить гораздо проще общаться с источниками реляционных данных (БД). В C# 3.0 это реализуется за счет расширяемости, а не простого добавления нескольких специальных конструкций. Благодаря новым конструкциям языка можно писать такие выражения:
var query = from c in db.Customers where City == "London" orderby c.ContactName; select new { CustomerID, ContactName }; |
Это выражение похоже на SQL-запрос. Это достигается благодаря появившимся в языке операторам запросов (from, where, select, orderby и некоторым другим). Такие операторы – это синтаксический сахар, упрощающий написание запросов, они отображаются на нижележащие функции, выполняющие проекцию, фильтрацию или сортировку (функции Select, Where, OrderBy).
Возможность передавать функции другим функциям (точнее, ссылки на функции) была в C# с самого начала. Однако в C# 1.х эта возможность была реализована крайне неудобно. Функцию нужно было явно оборачивать в делегат, и можно было передавать ссылки только на методы. В C# 2.0 появилось две возможности: неявно преобразовывать имя функции к совместимому с ней делегату, что позволило передавать функции в более естественном виде, и возможность объявлять анонимные методы (функции), то есть объявлять функции непосредственно в выражении, где их требуется использовать. Такие функции могут использовать переменные, объявленные в том же контексте выше (эта возможность называется замыканиями, closures). Уже это сделало применение функционального стиля в C# более естественным. Однако синтаксис анонимных методов оказался громоздким, что в сочетании с необходимостью использовать оператор return приводило к объемному и неэстетичному коду. В C# 3.0 было решено добавить новую конструкцию – лямбда, которая, по сути, является тем же самым, что и анонимные методы, но за счет автоматического вывода типов для параметров и необязательности применения оператора return ее синтаксис очень компактен и удобен для передачи небольших выражений другим функциям в качестве параметра. Удивительно, как небольшое изменение синтаксиса может привести к изменению восприятия некоторой возможности.
Как вы можете видеть, этот запрос возвращает только CustomerID и ContactName из более сложной структуры Customer. Не обязательно явно объявлять новый класс, который содержит только два этих члена, потому что C# 3.0 позволяет разработчикам использовать анонимные типы. Также не требуются объявлять тип переменной запроса, поскольку использование ключевого слова var заставляет C# автоматически выводить тип.
Исходно идея встраивания доступа к данным в язык программирования общего назначения появилась в исследовательском проекте Cw в Microsoft Research. Интегрированные в Cw возможности доступа к данным включают работу с БД и структурированными XML-данными. Проект LINQ в основном основан на Cw, но есть и некоторые отличия.
На самом деле подобные эксперименты проводились уже достаточно давно в таких языках, как Haskell, и некоторые участвовавшие в их создании разработчики перешли работать в Microsoft. Так что Cw стал развитием идей, родившихся в функциональных языках. – прим.ред.
Возможности, имеющиеся и в C# 3.0, и в Cw, включают анонимные типы (в Cw они не ограничены локальной областью видимости), локальный вывод типа и операторы запросов. Одна из концепций, отсутствовавших в Cw – это расширяемость через деревья выражений. В Cw нельзя было написать собственную реализацию источника данных, который выполнял бы запросы к другим данным, находящимся в памяти. Следующий код показывает, как выглядит в Cw работа с БД (это очень похоже на предыдущий пример C# 3.0):
query = select CustomerID, ContactName
from db.Customers
where City == "London"
order by ContactName;
|
Проект Cw еще будет упомянут в других разделах статьи, поскольку многие возможности C# исходно появились в Cw, причем там они были мощнее.
Язык программирования, о котором можно сказать, что в нем функции являются первоклассными сущностями, – это язык, в котом функции могут создаваться в процессе исполнения программ, храниться в структурах данных, передаваться в качестве параметров другим функциям и возвращаться в качестве значений других функций. Wikipedia.org
Поддержка функций как первоклассных значений необходима для функционального стиля программирования. Например, функции, работающие в Haskell со списками (map, foldl и т.д…) – это функции высшего порядка, что означает, что они принимают функции в качестве параметра.
Приведенная выше цитата суммирует, что такое функции первого класса, и в чем состоят преимущества языков, поддерживающих эту возможность. Более точный перечень всего, что должен поддерживать язык, чтобы рассматривать функции как первоклассные объекты, можно найти в работе «Concepts in Programming Languages» [5]. Согласно этой книге, язык содержит функции первого класса, если функции могут быть:
Пункты 2 и 3 выполняются во многих языках, включая С/C++, где можно передавать как аргумент указатель на функцию. В первой версии C# можно было использовать делегаты, которые можно легко описать как типобезопасные указатели на функции; однако ни C/C++, ни первая версия C# не позволяли объявлять функции в любой области видимости, в частности, в теле другой функции (или метода).
Сначала я расскажу, как работа с функциями реализована в F#, смешанном императивно-функциональном языке для платформы .NET. Я выбрал язык F#, поскольку на его примере видно, что можно реализовать возможности функциональных языков на платформе .NET. Сперва я покажу некоторые возможности, уникальные для F# (по сравнению с остальными .NET-языками):
// ‘add’ – это объявление локальной функции // с помощью лямбда-выражения (анонимной функции) // Синтаксис fun <список параметров> -> <выражение> // в F# описывает безымянную лямбда-функцию let add = (fun x y -> x + y);; // Объявление локальной функции (более краткая версия, // но результат тот же, что и в предыдущем случае) let add x y = x + y;; // Currying – создает функцию, прибавляющую к параметру 10. // Объявление функции путем частичного задания // списка параметров другой функции. // Этот пример задает один из параметров значением 10, оставляя второй // свободным (несвязанным). Это приводит к появлению новой безымянной функции, // прибавляющей значение 10 к своему аргументу. // Получившаяся безымянная функция присваивается переменной add10, которую // в дальнейшем можно использовать как имя функции let add10 = add 10;; |
Первое, что показывает этот пример, – в F# функции это значение, имеющее собственный тип, такое же, как любое другой. Это демонстрирует первый пример функции add, являющейся просто глобальной переменной, чье значение – это функция, созданная с использованием лямбда-выражения. Второй пример показывает упрощенный синтаксис объявления глобальных функций (но единственное отличие от первой версии функции add – это синтаксис!).
Следующий пример показывает currying, операцию, которая берет функцию и связывает ее первый параметр с указанным значением. В данном случае параметры – это функция add (принимающая два параметра типа int и возвращающая значение типа int) и константа 10. Результат – это функция add10, принимающая один параметр типа int и возвращающая int. Следующие примеры показывают случаи использования первоклассных функций, которые я позже покажу и на C#:
// передача функций другим функциям let words = ["Hello"; "world"] in iter (fun str -> Console.WriteLine(str); ) words; // Возврат функции let compose f g = (fun x -> f (g x));; |
Первый пример сперва объявляет список, содержащий строки, и затем использует функцию iter, которая вызывает функцию, передаваемую ей в качестве первого параметра, для каждого элемента списка.
Второй пример – это функция, возвращающая композицию из двух функций, переданных в качестве параметров. Это хороший пример возврата функции, объявленной в локальной области видимости. Второй интересный момент в этом примере – это типы функций. Явно никаких типов не объявлено, и алгоритм вывода типов выводит типы параметров и возвращаемого значения.
<...>
v
Copyright © 1994-2016 ООО "К-Пресс"