UProLa

Неокріпші думки

Композиція функцій. MD5 .NET

with 2 comments

Нещодавно я згадував про те, як сучасні програмісти ставляться до функціональних мов програмування. Я був досить правий: один прямо мені сказав “фууу, ефшарп, навіщо?”, інші просто кліпнули очима і забули. Ну не асоціюється у людей функціональне з приємним і корисним. Що ж, спробую показати саме “вкусняшки”, котрі дає нам функціональний стиль написання програм.

Виклик функції

Давайте поменше слів, побільше коду. Ось функція, що робить MD5 хеш за допомогою стандартних .NET процедур.

public static string GenerateBase64HashFrom1251(string s)
{
	byte[] b1 = Encoding.GetEncoding(1251).GetBytes(s);

	MD5CryptoServiceProvider h1 = new MD5CryptoServiceProvider();
	byte[] b2 = h1.ComputeHash(b1);

	return Convert.ToBase64String(b2); 
}

Як видно, функція проста як дошка і прошарений C#-програміст переписав би її навіть так, для простоти:

public static string GenerateBase64HashFrom1251(string s)
{
	var b1 = Encoding.GetEncoding(1251).GetBytes(s);
	var h1 = new MD5CryptoServiceProvider();
	var b2 = h1.ComputeHash(b1);
	return Convert.ToBase64String(b2); 
}

де var – об’явлення змінної, тип якої визначить сам компілятор (нам впадлу набирати назву довжелезного класу або дивитись в документацію, який тип повертає конкретна функція)

Починаючий C# або Пітон програміст взагалі перепише функцію в один рядок:

public static string GenerateBase64HashFrom1251(string s)
{
    return Convert.ToBase64String(new MD5CryptoServiceProvider().ComputeHash(Encoding.GetEncoding(1251).GetBytes(s))); 
}

Але одразу ж вернеться до попередніх варіантів, бо однорядоковий спосіб зовсім нечитабельний.

Пайпи

Досвічені Лінукс-користувачі знають про механізм перенаправлення потоків і про механізм пайпів. Вище згадана функція хешування дуже просто була би записана в пайп-нотації:

s | Encoding.GetEncoding(1251).GetBytes | new MD5CryptoServiceProvider().ComputeHash | Convert.ToBase64String

Ми значення “s” передаємо на вхід функції GetBytes (яка приймає на вхід один параметр – рядок), результат функції (масив байтів) передаємо на вхід ComputeHash, а уже захешований результат кодуємо у Base64 і повертаємо результат – рядок.

Я часто використовую пайпи у лінуксі, особливо у комбінації з grep:

– ps ax | grep chrom – перевірити, чи живий процес chromium-a
– du -a | grep fff – знайти файл або папку *fff*

Так ось, виявляється, що пайп можна реалізувати у функціональних мовах програмування, а у F# він уже присутній для користування. Давайте приведу наш приклад на F#

    let GenerateBase64HashFrom1251 (input:string) =
        input
        |> Encoding.GetEncoding(1251).GetBytes
        |> (new MD5CryptoServiceProvider()).ComputeHash
        |> Convert.ToBase64String

Порівняйте це з попередніми варіантами. В порівнянні з першими ми створили менше нових змінних (вірніше, компілятор то створить ці змінні, але ми ними не оперували), записали менше дужок, майже не заморочувались з типами. Я тепер щиро шкодую, що даного синтаксису немає в C#.

Хоч як це не дивно, але такий пайп-синтаксис можливо реалізувати на Пітоні без модифкацій інтерпретатора. Це справді чудовий шматок Пітон-дзену, раджу подивитись код модуля pipe – всього лиш 7 рядків!

Композиція

Проте вищенаведений пайп-синтаксис – це ще не композиція. Давайте заглибимось на зовсім трошки в F#, а саме об’явлення і виклик функцій.

let someFunc a b = a + b
let result = someFunc 40 2

У першому рядку ми визначили функцію, яка приймає на вхід два параметри, у другому – ми її викликали і записали результат виконання у змінну (насправді, незмінну, але то вже інша історія). Варто відмітити, що між визначенням змінної і визначенням функції різниця тільки у кількості вхідних параметрів.

А ще, не потрібно ставити дужки навколо параметрів. Спочатку це здається дивним і не зрозумілим – “а як тоді відрізнити функцію від параметру?” Відповідь: а ніяк, і в цьому весь прикол!

let someFunc a b = a b

Це еквівалентно функції на Пітоні

def someFunc (a, b): a(b)

Фактично, щоб код компілювався, першим параметром потрібно передавати саме функцію:

let result = someFunc (fun x -> x*2) 21

або на Пітоні

result = someFunc(lambda x: x*2, 21)

За допомогою даної нотації і реалізовані пайпи в F#

let (|>) input func = func input

Запис оператора в дужках – визначення нової для нього логіки роботи, а оскільки цей оператор інфіксний, то ми маємо:

data |> DoSomethingWithData

еквівалентно DoSomethingWithData(data). І, як ви уже помітили, цих пайпів можна ставити скільки завгодно!

Власне композиція

Композиція записується схожим способом:

let (>>) func1 func2 data = func2 (func1 data)

Ми просто додали ще один параметр (розширили визначення пайпу) і отримали композицію функцій. Спочатку може здаватись, що ніяких переваг ми не оримаємо.

Але це не так. Оператор композиції – це найпростіший приклад функції вищого порядку (цебто, функції яка генерує функції). Давайте глянемо на використання.

let mega = super >> duper

Ми створили змінну (як дуже навіть незмінна, але це все ще досі інша історія =)), яка є результатом композиції функцій super і duper. Але де тут генерація функції, ми ж просто заповнили змінну? Правильно, ми створили змінну функціонального типу! Наша змінна тепер є функцією і для отримання результату потрібно передати їй якесь значення:

let result = mega inputData

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

let result = (super >> duper) inputData

Тепер, думаю, зрозуміло, чому у визначенні оператора композиції саме 3 параметри, а не 2. Якщо досі не зрозуміло, то ось, що “конкретно” відбувається в середині:

let result = duper (super inputData)

Щоб побачити відмінність між пайпами і композиціями, давайте перепишемо функцію mega через пайпи:

let mega input =
    input
    |> super
    |> duper
let result = mega inputData

Як бачите, при визначенні функції через композицію, нам не потрібно навіть визначати параметри функції! І ось як буде виглядати наша початкова функція хешування при реалізації через композиції:

    let GenerateBase64HashFrom1251 : string -> string =
        Encoding.GetEncoding(1251).GetBytes
        >> (new MD5CryptoServiceProvider()).ComputeHash
        >> Convert.ToBase64String

У першому рядку ми прямо вказали, що ми створюємо нову функцію, що приймає на вхід рядок і видає на вихід рядок. А всі наступні рядки – декларативний опис перетворень вхідних даних у вихідні.

Погодьтесь, щось у цьому є =)

Примітки

  • По-перше, повинен сказати, що подальшу інформацію про композиції функцій вам доведеться шукати самому на російсько- та англомовних ресурсах. Можете навіть не пробувати дивитись статтю в укрвікі – там жопа і матан. А я привів нормальний приклад, нехай і простий, зате він реально використовується.
  • По-друге, повніше про композиції розповісти, мабуть, неможливо. Варто тільки відмітити, що композиції можна реалізувати на багатьох (у тому числі і не функціональних) мовах, наприклад в тому ж Пітоні, але “вкусняшками” їх відчуваєш тільки у функціональних. Гляньте приклад реалізації композиції на пітоні, вкусняшкою це не назвеш, на відміну від F#.
  • кругом, де я пишу F#, можна сміло розуміти це як ML з .NET бібліотеками. Нічого саме F#-специфічного я не використовую і всі приклади будуть працювати у тому ж O`Caml
  • Порадувала композиція у Форті
    : mega  duper super ;

    Форт такий функціональний, ага.

  • Сміливо задавайте свої питання – у міру знань буду відповідати
  • Written by danbst

    Вересень 8, 2011 at 01:33

    Відповідей: 2

    Subscribe to comments with RSS.

    1. Відчуття дежавю, F# такий Гаскель)

      Офтоп: я теж довго користувався ps ax | grep, поки не дізнався, що є такі речі, як pgrep та pkill🙂

      Dmytro Sirenko

      Вересень 9, 2011 at 23:49

      • Дежавю зрозуміло, адже і хаскель і F# мають спільного предка.

        Про pgrep дякую, але Ви уже третій мені це рекомендуєте, важко перевчитись =)

        danbst

        Вересень 10, 2011 at 00:05


    Залишити відповідь

    Заповніть поля нижче або авторизуйтесь клікнувши по іконці

    Лого WordPress.com

    Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

    Twitter picture

    Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

    Facebook photo

    Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

    Google+ photo

    Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

    З’єднання з %s

    %d блогерам подобається це: