LINUX.ORG.RU

Очепятки в книге по функциональному программированию

 


1

3

В занимательной книженции Душкина «14 занимательных эссе о языке Haskell и функциональном программировании» в разделе про комбинаторы есть такой пример кода:

data Combinator = Var String
		| App Combinator Combinator
		| Lam String Combinator
  deriving Eq
instance Show Combinator where
  show (Var x)= x
  show (App x y) = case y of
     App _ _ -> showLam x ++
		"(" ++
		show y ++
		")"
     _	     -> showLam x ++
		showLam y
    where
	  showLam l@(Lam _ _) = "(" ++
	  			show l ++
				")"
	  showLam x	      = show x
  show (Lam x e) = "\\" ++
		   x ++
		   "."
		   ++ show e
i = Var "I"
k = Var "K"
s = Var "S"

free :: String -> Combinator -> Bool              
free x (Var y)	   = x == y
free x (App e1 e2) = free x e1 || free x e2
free x (Lam y e)   = free x e

transform :: Combinator -> Combinator
transform (Var x) = Var x
transform (App x y) = App (transform x)
                          (transform y)
transform (Lam x (Var y)) | x == y
  = i transform (Lam x e)
                          | (not . free x) e
  = App k (transform e) transform (Lam x l@(Lam y e))
                          | free x e
  = transform (Lam x (transform l))
transform (Lam x (App e1 e2))
  = App (App s (transform (Lam x e1)))
        (transform (Lam x e2))
Невооруженным глазом видны ошибки (естественно hugs его жрать отказывается). Тут е черт знает откуда взялась:
transform (Lam x (Var y)) | x == y
  = i transform (Lam x e)
Тут вообще синтаксически неверно:
                          | (not . free x) e
  = App k (transform e) transform (Lam x l@(Lam y e))
Я внес вот такие изменения:
transform :: Combinator -> Combinator
transform (Var x) = Var x
transform (App x y)= App (transform x)
						 (transform y)
transform e@(Lam x (Var y)) | x == y
  = App i (transform (Lam x e))
			  | (not . free x) e
  = App k (transform e) 
transform (Lam x l@(Lam y e))  | free x e
  = transform (Lam x (transform l))
transform (Lam x (App e1 e2))
  = App (App s (transform (Lam x e1)))
	(transform (Lam x e2))
Но как человек, увидевший haskell практически вчера, я своим изменениям естественно не доверяю. Правильно ли мне удалось уловить мысль автора?

Перемещено mono из talks

★★★
transform (Lam x (Var y)) | x == y
  = i transform (Lam x e)
                          | (not . free x) e
  = App k (transform e) transform (Lam x l@(Lam y e))
                          | free x e
  = transform (Lam x (transform l))

И эти люди говорят нам, что плюсы монстрообразны:)

Stahl ★★☆
()

естественно hugs его жрать отказывается

ЕЯННП, «14 эссе...» GHC-специфичны.

Тут е черт знает откуда взялась

Аргумент как аргумент. Проследи логическую последовательность при выполнении соотв. guard'ов, всё станет понятно.

k336184
()
Ответ на: комментарий от k336184

Да, но интерпретатор говорит, что мол не знает что такое е.

LIKAN ★★★
() автор топика
Ответ на: комментарий от cvs-255

Разложение комбинатора по базису SKI.

LIKAN ★★★
() автор топика
Ответ на: комментарий от cvs-255
data Exp v = Lit v | Exp v :+ Exp v | Exp v :* Exp v deriving ( Eq, Show )

eval :: Num v => Exp v -> v
eval (Lit v) = v
eval (x :+ y) = eval x + eval y
eval (x :* y) = eval x * eval y

test = do
  let x = (Lit 1 :+ Lit 2) :* (Lit 3 :+ Lit 4)
  print x
  print $ eval x

ждём лапшу на over 100LOC из template/virtual/const/stringstream/auto.

quasimoto ★★★★
()
Ответ на: комментарий от qnikst

аудитория ждёт кода. В студию!

+1. С нетерпением жду, будучи частью этой самой аудитории.

dave ★★★★★
()
Ответ на: комментарий от quasimoto

Рябата, войны функциональников и процедурников - притча во языцах. Зачем их в очередной раз разводить? Что там по поводу моего кода, тем более с примером

q = Lam "x"
		(Lam "y"
			(Lam "z"
				(App (Var "x")
					 (App (Var "y")
						  (Var "z")))))
w = transform q
вылазит бесконечная рекурсия.

LIKAN ★★★
() автор топика
Ответ на: комментарий от cvs-255

На Си или С++ такое говно делать вообще не нужно. Так что да, изящнее - в ноль строк ровно.

anonymous
()
Ответ на: комментарий от LIKAN
infixl 6 :$
infixr 5 :?

data T v = V v | T v :$ T v | v :? T v | S | K | I deriving Show

free :: Eq v => v -> T v -> Bool
free a (V b) = a == b
free a (x :$ y) = free a x || free a y
free a (_ :? x) = free a x
free _ _ = False

toSKI :: Eq v => T v -> T v
toSKI (x :$ y) = toSKI x :$ toSKI y
toSKI (a :? x) | not $ free a x = K :$ toSKI x
toSKI (a :? V b) = if a == b then I else K :$ V b
toSKI (a :? b :? x) | free a x = toSKI $ a :? toSKI (b :? x)
toSKI (a :? x :$ y) = S :$ toSKI (a :? x) :$ toSKI (a :? y)
toSKI x = x

main :: IO ()
main = do
  let q = "x" :? "y" :? "z" :? V "x" :$ V "y" :$ V "z"
  print q
  print $ toSKI q
quasimoto ★★★★
()
Ответ на: комментарий от true_admin

Почему бы сразу не написать (1+2)*(3+4)?

return 21; же :)

Суть в том, что немножко ADT, немножко автоматического deriving, немножко pattern-matching, классов типов, параметрических типов и переписывать на C++ уже совсем нудно (даже без всякой функцональщины и фичей GHC).

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Имхо, надо сравнивать ЯП на задачах, а не «а ваши плюсы не умеют ADT». Т.е. моя претензия к подходу сравнения ЯП. В данном случае код вообще не нужен, можно сразу начинать «покажите мне pattern matching». Но это не интересно.

true_admin ★★★★★
()

Неправильно.

Здесь:

transform (Lam x (Var y)) | x == y
  = i transform (Lam x e)
                          | (not . free x) e
ошибка в отсутствующем переводе строки. Замени на
transform (Lam x (Var y)) | x == y
  = i
transform (Lam x e) | (not . free x) e

Miguel ★★★★★
()
Ответ на: комментарий от true_admin

задачу нормального уровня ради поиграться могут только студенты для курсовика сделать, так что проверять особо не на чем :)

qnikst ★★★★★
()
Ответ на: комментарий от quasimoto

ждём лапшу на over 100LOC из template/virtual/const/stringstream/auto.

stringstream

:)

yoghurt ★★★★★
()
Ответ на: комментарий от quasimoto

Почему-то вспомнилось со времен, когда я говнякал на руби:

%w[** / * + -].map{|o|String.send(:define_method,o){|n|"(#{o=='**'??^:o} #{self} #{n})"}}
puts eval gets.gsub(/\w/,'?\0').gsub ?^,'**'

Эта штука, конечно, не вычисляет их, но вот так вот переводить в одну строку - это в то время было для меня практически объектом поклонения.

cdshines ★★★★★
()

Очепятки в книге по функциональному программированию

Душкин

Там вся книга — одна большая опечатка.

buddhist ★★★★★
()
Ответ на: комментарий от quasimoto

toSKI (a :? V b) = if a == b then I else K :$ V b

toSKI (a :? V b) | a == b = I, тогда получается так же, у тебя или в книге переносы потерялись:

<   = i transform (Lam x e)
>   = i
> transform (Lam x e)

<   = App k (transform e) transform (Lam x l@(Lam y e))
>   = App k (transform e)
> transform (Lam x l@(Lam y e))
quasimoto ★★★★
()
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от anonymous

Не понимаешь смысла написания интерпретаторов для синтаксических деревьев? У тебя вообще как с когнитивными способностями?

anonymous
()

У тебя или разметка поехала, или действительно на вашем хаскеле вот так говняво нужно оформлять код?

bhfq ★★★★★
()
Ответ на: комментарий от anonymous

Это не «интерпретатор для синтаксических деревьев».

anonymous
()
Ответ на: комментарий от cvs-255

Это не алгоритм, а концепция. На C++ это скорее всего будут узлы AST как классы наследуемые от интерфейса с методами AST, типа

#include <vector>
#include <iostream>

template <typename Value>
struct Exp {
    virtual ~Exp() {}
    virtual Value eval() const = 0;
    virtual std::string show() const = 0;
};

template <typename Value>
class Lit : public Exp<Value> {
    const Value value;
public:
    Lit(Value const& value_) : value(value_) {}
    Value eval() const { return value; }
    std::string show() const { return "lit(" + std::to_string(value) + ")"; }
};

template <typename Value>
class Add : public Exp<Value> {
    using E = Exp<Value>;
    const E &x, &y;
public:
    Add(E const& x_, E const& y_) : x(x_), y(y_) {}
    Value eval() const { return x.eval() + y.eval(); }
    std::string show() const { return "add(" + x.show() + ", " + y.show() + ")"; }
};

template <typename Value>
struct Mul : public Exp<Value> {
    using E = Exp<Value>;
    const E &x, &y;
public:
    Mul(E const& x_, E const& y_) : x(x_), y(y_) {}
    Value eval() const { return x.eval() * y.eval(); }
    std::string show() const { return "mul(" + x.show() + ", " + y.show() + ")"; }
};

template <typename Value>
class GarbagePool {
    std::vector<Exp<Value>*> garbage_pool;
public:
    ~GarbagePool() { for (auto e : garbage_pool) delete e; }
    Exp<Value>& collect(Exp<Value>* e) { garbage_pool.push_back(e); return *e;   }
};

int main() {
    GarbagePool<int> gp;
    using Exp = Exp<int>;
    auto lit = [&gp](int x) -> Exp& { return gp.collect(new Lit<int>(x)); };
    auto mul = [&gp](Exp const& x, Exp const& y) -> Exp& {
        return gp.collect(new Mul<int>(x, y));
    };
    auto add = [&gp](Exp const& x, Exp const& y) -> Exp& {
        return gp.collect(new Add<int>(x, y));
    };
    auto &exp = mul(add(lit(1), lit(2)), add(lit(3), lit(4)));
    std::cout << exp.show() << " => " << exp.eval() << std::endl;
}

и тут ещё Eq нет. И Read. И пробельных строк :)

Ещё для примера — http://llvm.org/docs/tutorial, там C++ и OCaml.

А в топике функция с вложенным pattern-matching-ом + guards и перетасовыванием дерева, например строчка

transform (Lam x (App e1 e2)) = App (App s (transform (Lam x e1))) (transform (Lam x e2))

это значит, что «функция» transform «принимает» Exp<Value> const& и _если_ это не просто объект класса наследника, а объект в форме Lam(x, App(e1, e2)) где Lam и App — конструкторы классов, а x, e1 и e2 связываются с соответствующими поддеревьями, _то_ возвращается новый (динамическая аллокация та же) объект App(App(s, transform(Lam(x, e1))), transform(Lam(x, e2))). Одна проблема это эта «функция» которая «принимает» — expression problem, нужно какие-то визиторы городить, другая — арену для динамических объектов делать. Ну и с остальными строчками так же. Guards:

transform (Lam x l@(Lam y e)) | free x e = transform (Lam x (transform l))

аналогично, но _только если_ выполняется условие free x e (в AST эта переменная свободна), иначе переходим к анализу следующего case.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 2)
Ответ на: комментарий от cvs-255

А то тут не все хаскель знают

Неспециалисты обычно и не знают.

dave ★★★★★
()
Ответ на: комментарий от monk

В моём примере не было парсинга, он добавляется с помощью Read в deriving, и это не хаскель программы, а просто дефолтное текстовое представление термов ADT (show . read = TextRep, read . show = ADT).

А аст это например вот — http://www.boost.org/doc/libs/1_41_0/libs/spirit/example/qi/calc2_ast.cpp, то есть если обвешаться бустом (там variants для сумм, например), то будет лучше, но всё равно далеко от нормальных ADT + PM (!) + Guards (!), так что исходный пример из топика всё равно проблематично писать.

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

далеко от нормальных ADT + PM (!) + Guards (!)

Если задача «реализовать Haskell на шаблонах С++», то разумеется Haskell будет на порядок круче. А если конкретная задача «распарсить выражение», то примерно одинаково. На входе всё равно не в виде готового AST будет.

monk ★★★★★
()
Ответ на: комментарий от monk

Дык не было же задачи распарсить что-либо?

Были декларативные описания и работа с ADT — http://en.wikipedia.org/wiki/Algebraic_data_type#Programming_languages_with_a....

Никаких текстовых входов и выходов, тупо ADT и его функции.

А то получается как

... код типа fn foo(x, y) { a * x ^ 2 + b * y ^ 2 }

Я уверен, что на assembler это можно сделать куда изящнее.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

ADT — это инструмент. Сейчас звучит как «Не было задачи добраться в другой город. Была задача крутить педали. На велосипеде данная задача решается лучше чем на автомобиле. И даже на катамаране это можно сделать куда изящнее» :-)

monk ★★★★★
()
Ответ на: комментарий от monk

Ты че дурака-то включил? Ни в одном из исходных примеров не было парсинга, только трансформации.

anonymous
()
Ответ на: комментарий от anonymous

только трансформации.

Ну так я и на C++ могу написать

#include <iostream>

enum selector {Lit, Plus, Mult};
struct expr
{
  Selector t;
  expr *value1, *value2;
  int value;
  expr(int val) { t = Lit; value = val; }
  expr(selector _t, expr *_v1, expr *_v2) { t = _t; value1 = _v1; value2 = _v2 };
  string show() 
  { 
     case (t)
     {
        Lit: return "Lit(" + to_string(value) + ")";
        Plus: return "Plus(" + value1.show() + "," +value2.show() + ")";
        Mult: return "Mult(" + value1.show() + "," +value2.show() + ")";
     }
  }
  ~expr() { delete value1; delete value2; }
}

expr* plus(expr *a, expr* b) { new expr(Plus, a, b); }
expr* mult(expr *a, expr* b) { new expr(Mult, a, b); }
int eval(expr *a)
{
   case (a->t)
   {
      Lit: return a->value;
      Plus: return eval(a->value1) + eval(a->value2);
      Mult: return eval(a->value1) * eval(a->value2);
   }
}

int main()
{
   expr *x = new expr(Mult, new expr Plus(1, 2), new expr Plus(3, 4));
   cout << x.show() << end;
   cout << eval(x) << endl;
}
monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 2)
Ответ на: комментарий от anonymous
#include <iostream>
#include <string>

using namespace std;
enum selector {Lit, Plus, Mult};
struct expr
{
  selector t;
  expr *value1, *value2;
  int value;
  expr(int val) { t = Lit; value = val; }
  expr(selector _t, expr *_v1, expr *_v2) { t = _t; value1 = _v1; value2 = _v2; }
  ~expr() { delete value1; delete value2; }
};

ostream& operator<< (ostream& os, expr& s) {
    switch (s.t)
    {
        case Lit: return os << "Lit("  << s.value << ")";
        case Plus: return os << "Plus(" << *s.value1 << "," << *s.value2 << ")";
        case Mult: return os << "Mult(" << *s.value1 << "," << *s.value2 << ")";
    }
}

expr* plus(expr *a, expr* b) { new expr(Plus, a, b); }
expr* mult(expr *a, expr* b) { new expr(Mult, a, b); }
int eval(expr *a)
{
   switch (a->t)
   {
      case Lit: return a->value;
      case Plus: return eval(a->value1) + eval(a->value2);
      case Mult: return eval(a->value1) * eval(a->value2);
   }
}

int main()
{
   expr x(Mult, new expr(Plus, new expr(1), new expr(2)),
                new expr(Plus, new expr(3), new expr(4)));
   cout << x << endl;
   cout << eval(&x) << endl;
}
monk ★★★★★
()
Ответ на: комментарий от monk

Плохой код. Тогда уж лучше так:

enum selector {LIT, PLUS, MUL};
struct expr {
    const selector type;
    const int lit_val;
    const shared_ptr<expr> oper_val1;
    const shared_ptr<expr> oper_val2;

    static shared_ptr<expr> lit(int i)
    {
        return shared_ptr<expr>(new expr(i));
    }
    static shared_ptr<expr> plus(shared_ptr<expr> a, shared_ptr<expr> b)
    {
        return shared_ptr<expr>(new expr(PLUS, a, b));
    }

    static shared_ptr<expr> mul(shared_ptr<expr> a, shared_ptr<expr> b)
    {
        return shared_ptr<expr>(new expr(MUL, a, b));
    }

private:
    expr(int value)
        : type(LIT), lit_val(value)
    {}
    expr(selector t, const shared_ptr<expr> & value1, const shared_ptr<expr> & value2)
        : type(t), lit_val(0), oper_val1(value1), oper_val2(value2)
    {}
};

string show(const shared_ptr<expr> & a)
{
    switch (a->type) {
    case LIT:
        return "lit(" + to_string(a->lit_val) + ")";
    case PLUS:
        return "plus(" + show(a->oper_val1) + "," + show(a->oper_val2) + ")";
    case MUL:
        return "mul(" + show(a->oper_val1) + "," + show(a->oper_val2) + ")";
    }
}

int eval(const shared_ptr<expr> & a)
{
    switch (a->type) {
    case LIT:
        return a->lit_val;
    case PLUS:
        return eval(a->oper_val1) + eval(a->oper_val2);
    case MUL:
        return eval(a->oper_val1) * eval(a->oper_val2);
    }
}

int main()
{
    auto x = expr::mul(
        expr::plus(expr::lit(1), expr::lit(2)),
        expr::plus(expr::lit(3), expr::lit(4))
    );
    cout << show(x) << endl;
    cout << eval(x) << endl;
}

forCe
()
Ответ на: комментарий от forCe

В принципе, тут можно замутить и union(с учетом нового стандарта), сделать типобезопасные селекторы и пр. Вот только нужно ли?

forCe
()
Ответ на: комментарий от forCe

Если предполагаемое использование как в Haskell (построение выражений), то shared_ptr — overkill. Можно auto_ptr вместо явного деструктора, но, ИМХО, код будет идентичен.

Если предполагаемое использование «для чего угодно», то тогда лучше код quasimoto. Он в отличие от примера на Haskell расширяемый.

Про const и область видимости согласен.

А что плохого в operator<< ? По-моему более логичное соответствие Haskell'евскому Show.

monk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.