Re[11]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 28.11.13 12:41
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

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


И? Все равно ты не получишь число, пока не применишь должное число аргументов, хоть бы и в цикле. Язык со статическими типами заставит тебя сделать примерно то же, и проверит при этом, что цикл правильный. Фактически, и там и там типы могут зависеть от рантайм значений, разница лишь в том, что язык со статической типизацией заставляет с ними обращаться корректно.
Re[11]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 28.11.13 12:45
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>А если вот так:

ВВ>main = print (fun [1,2,3] 1 2 3 4, fun [1,2,3,4,5] 10 20 30 40 50)

ВВ>Сгенерирует ли компилятор ошибку и что это будет за ошибка?


Да, конечно, в этом весь смысл статической проверки типов.
Говорит:
Type checking .\add.idr
add.idr:23:When elaborating right hand side of main:
fun [1,2,3] 1 2 3 does not have a function type (Int)
Re[12]: Неправильное введение в функциональное программирование
От: Воронков Василий Россия  
Дата: 28.11.13 13:02
Оценка:
Здравствуйте, D. Mon, Вы писали:

DM>Здравствуйте, Воронков Василий, Вы писали:


ВВ>>А если вот так:

ВВ>>main = print (fun [1,2,3] 1 2 3 4, fun [1,2,3,4,5] 10 20 30 40 50)

ВВ>>Сгенерирует ли компилятор ошибку и что это будет за ошибка?

DM>Да, конечно, в этом весь смысл статической проверки типов.
DM>Говорит:
DM>Type checking .\add.idr
DM>add.idr:23:When elaborating right hand side of main:
DM>fun [1,2,3] 1 2 3 does not have a function type (Int)

Вот, собственно, интересно — откуда компилятор узнал, что "fun [1,2,3]"? У нас же по легенде это рантайм значение? Закрадывается подозрение, что Ирдис просто достаточно умен для того, чтобы обращаться с константами как с константами.
А если усложнить задачу:


fun (x::xs) = fun' x xs
                where fun' a [] = \y -> y + a
                      fun' a (x::xs) = \y -> fun' (y + a + x) xs

saturate x f = if f is Fun then saturate x (f x) else f

process [] = []
process [x] = [x]
process (x::xs) = saturate x (fun xs) :: process xs

process [1..10]
Re[13]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 28.11.13 13:51
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>А если усложнить задачу


Легко. Только вместо хранения тэга внутри динамически типизированного значения f, у нас f имеет тип sumType n для некоторого n, и чтобы осмысленно с ним работать придется передать и само n. В остальном очень похоже:
saturate : (n:Nat) -> Int -> sumType n -> Int
saturate Z x f = f
saturate (S k) x f = saturate k x (f x)

process : List Int -> List Int
process [] = []
process [x] = [x]
process (x::xs) = saturate (length xs) x (fun xs) :: process xs

Проверяем:
Type checking .\add.idr
*add> process [1..10]
[63,68,70,69,65,58,48,35,19,10] : List Int
Re[13]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 28.11.13 15:44
Оценка: 9 (1)
Здравствуйте, Воронков Василий, Вы писали:

ВВ> У нас же по легенде это рантайм значение?


Чтобы развеять путаницу, возьмем поистине рантайм значение — будем читать список из аргументов командной строки.

fun : (xs : List Int) -> sumType (length xs)
fun xs = adder (length xs) (sum xs)

saturate : (n:Nat) -> Int -> sumType n -> Int
saturate Z x f = f
saturate (S k) x f = saturate k x (f x)

convertArgs : List String -> List Int
convertArgs [] = []
convertArgs (x::xs) = map cast xs

main : IO ()
main = do 
  as <- getArgs
  let xs = convertArgs as
  let f = fun xs                      -- вот тут тип f зависит от длины прочитанного списка
  print $ saturate (length xs) 100 f  -- но это не проблема

Вызываем из командной строки
add.exe 1 2 3
получаем ответ
306

При этом если мы последнюю строчку заменим на
print $ saturate (length xs + 1) 100 f
то компилятор ругнется:
add.idr:35:When elaborating right hand side of main:
Can't unify
        sumType (Prelude.List.length xs)
with
        sumType (Prelude.List.length xs + 1)

Т.е. таки проверяет понаписанное на корректность.
Re[14]: Неправильное введение в функциональное программирование
От: Воронков Василий Россия  
Дата: 28.11.13 15:51
Оценка:
Здравствуйте, D. Mon, Вы писали:

Ну да, легко. Отложенная типизация в действии =)
А ты попробуй передать список через командную строку или сформировать его на основе пользовательского ввода. Ведь на самом деле здесь же нет зависимости типа функции от *рантайм* значений, правда?
Re[15]: Неправильное введение в функциональное программирование
От: AlexRK  
Дата: 28.11.13 16:02
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>А ты попробуй передать список через командную строку или сформировать его на основе пользовательского ввода. Ведь на самом деле здесь же нет зависимости типа функции от *рантайм* значений, правда?


От значений нет, конечно. Если список будет из командной строки, то компилятор наверняка просто потребует наличие чего-то типа "if (list.length == 4)" перед его использованием.
Re[14]: Неправильное введение в функциональное программирование
От: Воронков Василий Россия  
Дата: 28.11.13 16:09
Оценка:
Здравствуйте, D. Mon, Вы писали:

Ну это пример такой получился удобный, который работает для списка любой длины; главное, чтобы length xs было именно длиной списка. А вот тут, я так понимаю, ругнется сразу:

fun [1..arg] 1 2 3


arg соответственно из командной строки.
Re[15]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 28.11.13 16:13
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Ну да, легко. Отложенная типизация в действии =)


Никакой отложенной.

ВВ>А ты попробуй передать список через командную строку или сформировать его на основе пользовательского ввода.


Уже.

ВВ>Ведь на самом деле здесь же нет зависимости типа функции от *рантайм* значений, правда?


Не правда. Зависимые типы реально зависят от рантайм значений. И делают это статически. Такой вот коан.
Re[15]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 28.11.13 16:19
Оценка: +1
Здравствуйте, Воронков Василий, Вы писали:

ВВ> А вот тут, я так понимаю, ругнется сразу:

ВВ>fun [1..arg] 1 2 3
ВВ>arg соответственно из командной строки.

Да, ведь и без запуска видно, что программа содержит баг: при arg меньше 3 будет фигня. А программы с багами не компилируются в приличных языках, для этого статическая типизация и нужна.
Re[16]: Неправильное введение в функциональное программирование
От: Воронков Василий Россия  
Дата: 28.11.13 16:21
Оценка:
Здравствуйте, D. Mon, Вы писали:

DM>Здравствуйте, Воронков Василий, Вы писали:

ВВ>>Ну да, легко. Отложенная типизация в действии =)

DM>Никакой отложенной.


Мне кажется, это она и есть.

ВВ>>А ты попробуй передать список через командную строку или сформировать его на основе пользовательского ввода.

DM>Уже.
ВВ>>Ведь на самом деле здесь же нет зависимости типа функции от *рантайм* значений, правда?

DM>Не правда. Зависимые типы реально зависят от рантайм значений. И делают это статически. Такой вот коан.


Ну ты сам-то не понимаешь, что это невозможно?
Я просто пример привел такой удачный, что Идрис смог построить зависимость от длины списка. В итоге смог доказать корректность saturate статически, без всякой зависимости от рантайма.
Re[17]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 28.11.13 16:59
Оценка: 6 (1)
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Мне кажется, это она и есть.


Что именно ты называешь отложенной типизацией и как она может работать в идрисе?

ВВ>>>Ведь на самом деле здесь же нет зависимости типа функции от *рантайм* значений, правда?

DM>>Не правда. Зависимые типы реально зависят от рантайм значений. И делают это статически. Такой вот коан.
ВВ>Ну ты сам-то не понимаешь, что это невозможно?

Это еще как возможно. Вот еще простой пример:
module Main
import System

myType : Bool -> Type  -- тип, зависящий от значения
myType False = Int
myType True = String

create : (n:Bool) -> myType n   -- создадим значение, тип которого зависит от переданного аргумента
create False = 22
create True = "zebra"

useValue : (p:Bool) -> myType p -> String  -- тип второго аргумента зависит от рантайм значения первого
useValue False v = show $ 20 + v           -- если это инт, складываем с 20 и переводим в строку
useValue True v = "a " ++ v                -- если это строка, склеиваем с другой строкой

main : IO ()
main = do
  args <- getArgs          -- читаем аргументы командной строки, рантайм значение
  let p = length args > 1  -- передано ли что-то? рантайм значение
  let v = create p         -- тип v зависит от рантайм значения p, это или Int или String 
  print $ useValue p v     -- передаем значение типа, который мы не знаем статически, но точно можем обработать все равно


ВВ>Я просто пример привел такой удачный, что Идрис смог построить зависимость от длины списка. В итоге смог доказать корректность saturate статически, без всякой зависимости от рантайма.


Это и есть важнейшее свойство таких языков — компилируются лишь те программы, для которых доказана корректная работа на любых данных. Типы могут быть внутри самые разные и выбираться динамически в рантайме, но какие бы они не были, все должно сойтись, все случаи корректно рассмотрены и обработаны, и вот эта полнота рассмотрения статически доказана.
Re[18]: Неправильное введение в функциональное программирование
От: Воронков Василий Россия  
Дата: 28.11.13 19:25
Оценка: -1
Здравствуйте, D. Mon, Вы писали:

DM>Здравствуйте, Воронков Василий, Вы писали:

ВВ>>Мне кажется, это она и есть.
DM>Что именно ты называешь отложенной типизацией и как она может работать в идрисе?

Функция типизируется в момент первого применения. В общем это не только для зависимых типов полезно. Полиморфные функции во многих языках есть.
ВВ>>>>Ведь на самом деле здесь же нет зависимости типа функции от *рантайм* значений, правда?
DM>>>Не правда. Зависимые типы реально зависят от рантайм значений. И делают это статически. Такой вот коан.
ВВ>>Ну ты сам-то не понимаешь, что это невозможно?

DM>Это еще как возможно. Вот еще простой пример:

DM>
DM>module Main
DM>import System

DM>myType : Bool -> Type  -- тип, зависящий от значения
DM>myType False = Int
DM>myType True = String

DM>create : (n:Bool) -> myType n   -- создадим значение, тип которого зависит от переданного аргумента
DM>create False = 22
DM>create True = "zebra"

DM>useValue : (p:Bool) -> myType p -> String  -- тип второго аргумента зависит от рантайм значения первого
DM>useValue False v = show $ 20 + v           -- если это инт, складываем с 20 и переводим в строку
DM>useValue True v = "a " ++ v                -- если это строка, склеиваем с другой строкой

DM>main : IO ()
DM>main = do
DM>  args <- getArgs          -- читаем аргументы командной строки, рантайм значение
DM>  let p = length args > 1  -- передано ли что-то? рантайм значение
DM>  let v = create p         -- тип v зависит от рантайм значения p, это или Int или String 
DM>  print $ useValue p v     -- передаем значение типа, который мы не знаем статически, но точно можем обработать все равно
DM>


Здесь нет зависимости от рантайм значений, ведь эта программа доказывается статически. "Доказывается статически" означает, что возможный диапазон значений нам известен в компайл тайм, и мы можем доказать, что ни при каких случаях не выйдем из этого диапазона. Откуда здесь взяться зависимости от рантайма? Тут ее нет

А вот тут есть:

open core monad io

calc max acc n 
  | x < max = calc max x
  | else = Some x
  where x = n + acc

iterate f = do
  putStrLn "Need value:"
  x <- readAny
  match f x with
        Some x = mkIO x
        x = iterate x

do
  putStrLn "Max:"
  max <- readAny
  x <- iterate $ calc max 0
  putStrLn $ show x



Running module test3

Max:
42
Need value:
29
Need value:
11
Need value:
123
163

Session ended. Session taken: 00:00:08.4393606


DM>Это и есть важнейшее свойство таких языков — компилируются лишь те программы, для которых доказана корректная работа на любых данных. Типы могут быть внутри самые разные и выбираться динамически в рантайме, но какие бы они не были, все должно сойтись, все случаи корректно рассмотрены и обработаны, и вот эта полнота рассмотрения статически доказана.


"Зависимость от рантайм-значений" означает "Зависимость от значений, который становятся *известны* только в рантайме". А для того, чтобы программа была доказуема статически, ее нужно свести к набору значений, который известен уже в период компиляции. Никаких "нежданчиков" там быть не может. Так что это, мягко говоря, не одно и то же
Re: Неправильное введение в функциональное программирование
От: Evgeny.Panasyuk Россия  
Дата: 28.11.13 20:38
Оценка:

f = fun [1,2,3]
f 1 2 3
//Вывод:
//12


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


Код возможен. Но, очевидно, для runtime данных проверка будет в runtime:
#include <initializer_list>
#include <iostream>
#include <cstddef>
#include <cassert>
#include <vector>
using namespace std;

struct Variadic
{
    size_t n;
    template<typename ...Args>
    void operator()(Args ...args)
    {
        assert(n == sizeof...(args) );
    }
};

Variadic fun(vector<int> x)
{
    return {x.size()};
}

int main()
{
    vector<int> x{1,2,3};
    fun(x)(1,2,3); // Runtime OK
    cout << "OK" << endl;
    fun(x)(1); // assert fail
}
Re: Неправильное введение в функциональное программирование
От: Evgeny.Panasyuk Россия  
Дата: 28.11.13 21:08
Оценка:

Ответ прост. Мы считаем ее чистой, потому что списки в функциональных языках сравниваются не ссылочно (как, к примеру, массивы в C#), а структурно. Поэтому результаты двух вызовов этой функции эквивалентны, что несложно проверить в GHCi:

Equivalence != Equality

Вовсе нет. Давайте представим, что массивы в C# сравниваются именно структурно. Таким образом, два массива с одинаковым набором элементов будут считаться равными. И тогда мы сможем написать такой код:

var arr1 = new int[] { 1,2,3 };
var arr2 = new int[] { 1,2,3 };

if (arr1 == arr2)
  Console.WriteLine("arr1 и arr2 это один и тот же массив.");

arr1[0] = 10;

if (arr1 != arr2)
  Console.WriteLine("arr1 и arr2 это разные массивы.");


В результате выведется:

arr1 и arr2 это один и тот же массив.
arr1 и arr2 это разные массивы.

Как же так? Мгновение назад массивы были одинаковыми, а теперь они разные?

Как вы понимаете, во всем виновата «изменчивая природа» массивов. Так как вы можете поменять значения «по месту» (в отличие от связных списков в Хаскелле, любая операция над которыми приводит к созданию нового списка), то структурная эквивалентность уже не годится.


Неверно
Тот факт, что функция возвращает изменяемые объекты (но равные!) — не лишает её purity. Важно именно равенство возвращаемых значений.
Во многих примерах чистых функций, значение результата можно изменить:
double sin(double);
// ...
auto &&x = sin(0.0);
x = 0.11;


Кстати, вот тут Александр Степанов даёт определение равенству:

[...]
Definition: Two objects are equal if their corresponding parts are equal (applied recursively), including remote parts (but not comparing their addresses), excluding inessential components, and excluding components which identify related objects.
Once we have identified the parts of an object which must be tested for equality, we know from the earlier discussion that at least those parts must be copied by copy constructors or assignment operators. In particular, these operators must make copies of remote parts, rather than simply copying the pointers to them.
Now let us return to part of the logicians’ definition of equality. Recall that we would like the following statement to be true:

x == y ⇒ ∀ “reasonable” function foo, foo(x)==foo(y)

It is necessary to limit our expectations to some subset of possible functions foo.
For instance, this statement will not hold for the “address-of” function applied to distinct objects with equal values, nor will it hold for any other function which distinguishes between individual objects rather than between their values.
Considering an example will demonstrate some of the challenges facing a designer of generic components.
[...]

Re: Неправильное введение в функциональное программирование
От: Evgeny.Panasyuk Россия  
Дата: 28.11.13 22:58
Оценка:

К тому же в большинстве популярных объектно-ориентированных языков, включая C#, понятие алгебраический тип попросту отсутствует.


Справедливости ради стоит отметить, что в C++1998 tagged union прекрасно реализуется в виде библиотеки:
#include <boost/variant.hpp>
#include <iostream>
#include <ostream>

using namespace std;
using namespace boost;


struct Cellphone { bool touch_screen; };
struct Laptop { double screen_size; };

typedef variant<Cellphone, Laptop> Product;

struct Print: static_visitor<>
{
    void operator()(const Cellphone &x) const
    {
        cout << "Cellphone, touch_screen:" << x.touch_screen << endl;
    }
    void operator()(const Laptop &x) const
    {
        cout << "Laptop, screen_size:" << x.screen_size << endl;
    }
};

int main()
{
    Cellphone phone = {true};
    Laptop laptop = {11.1};
    
    Product x = phone;
    apply_visitor(Print(), x);
    x = laptop;
    apply_visitor(Print(), x);
}
Причём можно реализовать и "полиморфные варианты", а-ля Boost.Any, Boost.TypeErasure, и смешивать их как угодно с closed variant'ами или с обычным ООП. Например:
variant<ConcreteClass1, ConcreteClass2, function<void()>, unique_ptr<IAbstract>> x;


А в языке D он есть в стандартной библиотеке std.VariantN
Re[19]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 29.11.13 04:02
Оценка: +1
Здравствуйте, Воронков Василий, Вы писали:

DM>>Что именно ты называешь отложенной типизацией и как она может работать в идрисе?

ВВ>Функция типизируется в момент первого применения.

Звучит как немерлизм, ни в одном нормальном языке такого нет (чтобы именно первого). Формально это ересь, тип функции тут указан уже при определении, а потом лишь используется. Но если речь просто про полиморфизм, то да, тут одна из его форм, параметризованный тип.

DM>>

useValue : (p:Bool) -> myType p -> String  -- тип второго аргумента зависит от рантайм значения первого
useValue False v = show $ 20 + v           -- если это инт, складываем с 20 и переводим в строку
useValue True v = "a " ++ v                -- если это строка, склеиваем с другой строкой

  let p = length args > 1  -- передано ли что-то? рантайм значение
  let v = create p         -- тип v зависит от рантайм значения p, это или Int или String 
  print $ useValue p v     -- передаем значение типа, который мы не знаем статически, но точно можем обработать все равно


ВВ>Здесь нет зависимости от рантайм значений, ведь эта программа доказывается статически. "Доказывается статически" означает, что возможный диапазон значений нам известен в компайл тайм, и мы можем доказать, что ни при каких случаях не выйдем из этого диапазона. Откуда здесь взяться зависимости от рантайма? Тут ее нет


Ну что значит нет? Значение v тут один раз создается и передается. Какого оно типа? Зависит от того, как мы программу запустили, что ей передали. Это и есть зависимость от рантайма в чистом виде, а не твои странные определения.

ВВ>"Зависимость от рантайм-значений" означает "Зависимость от значений, который становятся *известны* только в рантайме".


Ровно так и есть же в этом примере. Есть зависимость типа от булевого значения, истина там или ложь — будет известно только в рантайме.

BB>А для того, чтобы программа была доказуема статически, ее нужно свести к набору значений, который известен уже в период компиляции. Никаких "нежданчиков" там быть не может. Так что это, мягко говоря, не одно и то же


Да, "набор" значений должен быть известен статически, но он может быть бесконечным. В примере выше, где список читался извне, тип функции зависел от длины списка, который мог быть сколь угодно длинным (если закрыть глаза на технические ограничения ОС). Т.е. в зависимости от переданных данных могло получиться значение типа
Int
Int -> Int
Int -> Int -> Int
Int -> Int -> Int -> Int
...
хоть Int -> ... 100500 раз -> Int

Но ты прав, что обо всех этих множествах мы должны быть способны рассуждать статически, именно это зависимые типы и позволяют, описать рантайм зависимость статически. "Нежданчиков" там быть не может быть, потому что любой настоящий "нежданчик" это баг, это то, что твоя программа не способна обработать и упадет или выдаст мусор. Такие баги-нежданчики статическая типизация уничтожает. Незачем позволять компилировать программы с такими явными багами.
Re[2]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 29.11.13 04:11
Оценка: -1
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Кстати, вот тут Александр Степанов даёт определение равенству:

EP>Definition: Two objects are equal if their corresponding parts are equal (applied recursively), including remote parts (but not comparing their addresses), excluding inessential components, and excluding components which identify related objects.
EP>Once we have identified the parts of an object which must be tested for equality, we know from the earlier discussion that at least those parts must be copied by copy constructors or assignment operators. In particular, these operators must make copies of remote parts, rather than simply copying the pointers to them.

Это похоже на С++ головного мозга, а не определение. Вот несколько значений, какие из них равны и почему?
a = \x -> 2 * x
b = \x -> 2 * x
c = \x -> x * 2
d = \x -> a x
e = \x -> x + x
Re[2]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 29.11.13 04:21
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

BB>>К тому же в большинстве популярных объектно-ориентированных языков, включая C#, понятие алгебраический тип попросту отсутствует.

EP>Справедливости ради стоит отметить, что в C++1998 tagged union прекрасно реализуется в виде библиотеки

К сожалению, это лишь жалкое подобие, потому что при поддержке алгебраиков в языке важно не только умение их конструировать, но и паттер-матчинг, в том числе вложенный:
has55 :: [Maybe Int] -> Bool
has55 [] = False
has55 (Just 5 : Just 5 : rest) = True
has55 (_ : rest) = has55 rest

На вызовах визиторов такое делать не шибко неудобно, мягко говоря.
Re[3]: Неправильное введение в функциональное программирование
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 29.11.13 04:30
Оценка:
Errata. В посте выше читать:
"паттерн-матчинг"
"не шибко удобно"
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.