Showing posts with label fprog_ru. Show all posts
Showing posts with label fprog_ru. Show all posts

Thursday, 2 June 2011

Хвостовая "рекурсия" и взаимная хвостовая "рекурсия" на C


Хвостовая рекурсия — специальный случай рекурсии, при котором рекурсивный вызов функцией самой себя является её последней операцией.[1] Подобный вид рекурсии примечателен тем, что может быть легко заменён на итерацию, что реализовано во многих оптимизирующих компиляторах.
К сожалению, в таком языке как C, по стандарту, данная возможность является не положенной, в связи с чем, рекурсия останется как есть, и скушает много памяти. Ниже приведён код, в котором описывается небольшой набор типов данных и функций для того, что бы реализовать некоторое подобие хвостовой рекурсии ручками.
typedef struct ReturnValue (* RecurFunction) (void *);

struct ReturnValue {
  RecurFunction function;
  void * value;
};

void * recur(RecurFunction function, void * input) {
  struct ReturnValue tmp;
  tmp.value = input;
  tmp.function = function;
  do {
    tmp = tmp.function(tmp.value);
  } while (tmp.function != NULL);
  return tmp.value;
}
Основная идея этого решения следующая: функция во время своего вычисления, формирует структуру данных типа ReturnValue, в которой определяется в поле value результат вычисления или задаётся функция, которая должна быть вычислена в следующую "итерацию" и её аргументы. Ограничения и неудобства решения видны сразу, рекурсивная функция должна быть типа RecurFunction, на вход получать void* (а значит и преобразовывать типы), и её результат должен быть представлен типом ReturnValue. Это делает код довольно страшным...
Посмотрим на классический пример, а именно на вычисление факториала:
// Тип входных данных.
struct RecurFactorial {
  int n, acc;
};
struct ReturnValue factorial(void * input) {
  struct RecurFactorial * args = input; // Приведение типа данных.
  struct ReturnValue result;
  result.value = input; // Входные и результируещие данный одни и теже.
  if (args->n == 1) {
    // Функция на следующий запуск не устанавливается для завершения вычислений.
    result.function = NULL;
  } else {
    // Формируем результаты итерации.
    args->acc *= args->n;
    args->n -= 1;
    // Устанвливаем функцию на следующий запуск.
    result.function = factorial;
  }
  return result;
}

Теперь рассмотрим пример поинтереснее, с двумя взаимно-рекурсивными функциями. Реализуется алгоритм с примерно тем же поведением (и с ошибкой на списке нулевой длинны), что у кода на Haskell в этом посте про алгоритм определения конца списка.
struct RecurLoopInList {
  struct List * fastMarker, * slowMarker;
};

struct ReturnValue slowMarker(void * input);
struct ReturnValue fastMarker(void * input);

bool loopInList(struct List *head) {
  struct RecurLoopInList args;
  args.fastMarker = head->next;
  args.slowMarker = head;
  bool * resultP = recur(fastMarker, (void*)&args);
  bool result = *resultP;
  free(resultP);
  return result;
}

struct ReturnValue slowMarker(void * input) {
  struct RecurLoopInList * args = input;
  struct ReturnValue result;
  result.value = input;
  args->slowMarker = args->slowMarker->next;
  result.function = fastMarker;
  return result;
}

struct ReturnValue fastMarker(void * input) {
  struct RecurLoopInList * args = input;
  struct ReturnValue result;
  if (args->fastMarker->next == NULL ||
      args->fastMarker->next->next == NULL) {
    bool * ans = malloc(sizeof(bool));
    *ans = false;
    result.value = (void*)ans;
    result.function = NULL;
  } else if (args->fastMarker->next == args->slowMarker ||
             args->fastMarker->next->next == args->slowMarker) {
    bool * ans = malloc(sizeof(bool));
    *ans = true;
    result.value = (void*)ans;
    result.function = NULL;
  } else {
    args->fastMarker = args->fastMarker->next->next;
    result.value = (void*)input;
    result.function = slowMarker;
  }
  return result;
}

Целиком весь код можно увидеть тут: https://gist.github.com/1003337

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

Один хороший человек с ником Powerfox предложил решение через контексты.

Friday, 30 July 2010

5 + 1 главных особенностей, которые необходимо знать о конструкторах в Scala




Вольный перевод данного поста: Top 5 Things to Know About Constructors in Scala и плюс ещё одна особенность, на которую волей случая наткнулся.

Я играюсь со Scala в течение нескольких месяцев, и одной из вещей, с котороми пришлось бороться после перехода с Java были конструкторы. Они похожи на конструкторы в Java, но синтаксис имеет отличия.
Что бы помочь вам быстрее начать пользоваться Scala, приведу пять основных особенностей связанных с конструкторами. Начнём:

1. Как сделать конструктор с параметром?
public class Foo() {
  public Bar bar;
  public Foo(Bar bar) { 
    this.bar = bar; 
  }
}
На Scala это будет выглядеть так:
class Foo(val bar:Bar)
В данном случае val создаёт неизменяемое публичное поле, используте var для создание изменяемых полей.

2. Как сделать приватные поля?
public class Foo() {
  private final Bar bar;
  public Foo(Bar bar) { 
    this.bar = bar; 
  } 
}
На Scala это будет выглядеть так:
class Foo(private val bar: Bar)
Приватные поля не так необходимы как в Java и вы можете делать все поля публичными. В случае необходимости вы можете потом определить геттер и сеттер без внесения изменений в клиенты.

3. Как использовать super()?
public class Foo() extends SuperFoo {
  public Foo(Bar bar) { 
    super(bar); 
  } 
}
На Scala это будет выглядеть так:
class Foo(bar:Bar) extends SuperFoo(bar)b

4. Как создать более одного конструктора?
public class Foo {
  public Bar bar;
  public Foo() { 
    this(new Bar()); 
  } 
  public Foo(Bar bar) { 
    this. bar = bar;
  } 
}
На Scala это будет выглядеть так:
class Foo(val bar:Bar) {
  def this() = this(new Bar)
}
Второй конструктор (this()) делегирует свои обязанности другому конструктору.

5. Как сделать "bean style" геттер и сеттер?
public class Foo() {
  private Bar bar; 
  public Foo(Bar bar) { 
    this.bar = bar; 
  } 
  public Bar getBar() { 
    return bar; 
  } 
  public void setBar(Bar bar) { 
    this.bar = bar;
  }
}
На Scala это будет выглядеть так:
class Foo(@BeanProperty var bar:Bar)
Поле bar является публичным, что не представляет проблем в Scala (смотри выше).

(далее от переводчика)
6. Как сделать конструктор c несколькими выражений?
Данный пункт отмечен в примечание к посту, но я покажу его подробнее.

public class Foo() {
  private Int b; 
  private Int y; 
  public Foo(Int b) { 
    this.b = b; 
  } 
  public Foo() { 
    this.y = java.util.Random.nextInt();
    this.y += 1;
    this.b = b; 
  } 
}

На Scala это будет выглядеть так:

class Foo (val b:Int) {
  var y = 0
  def this() = {
    this(3)
    y = scala.util.Random.nextInt
    y += 1
  }
}
Язык Scala накладывает ограничение на метод this(): "- первым вызовом конструктора должен быть вызов другого конструктора, причем любого - важно только чтобы цепочка вызовов конструкторов в итоге привела к вызову первичного конструктора"
Спасибо javverwocky, за указание на ошибку и пример.




Thursday, 29 July 2010

Модель простейшего процессора на Haskell

Относительно недавно (немногим больше полугода), в университете было получено задание - написать модель простейшего процессора на не синтезируемом подмножестве Verilog, но в связи с личной неприязнью к этому языку, модель была написана на Haskell (в связи с не нулевой вероятностью, что меня попросят переписать, писалась по тактовая модель, что бы можно было скриптами сгенерировать часть кода на Verilog). По скольку тестировать модель как-то надо, а писать в маш кодах довольно уныло, модель писалась вместе с компилятором асм подобного языка, как следствие, в описаниях машинных команд есть фрагменты, необходимые для компилятора.




Описание процессора:

Сплошной линией обозначены линии данных. Пунктиром обозначены управляющие сигналы.




Основные характеристики:

Разрядность процессора: 8 бит. 
Организация памяти: гарвардская, с отдельными блоками памяти для команд и данных. 
Подсистема обработки прерываний и команды вызова подпрограмм отсутствует. 
Внешние устройства (для данной работы это светодиоды, двухпозиционные переключатели и тактовые кнопки) отображаются в адресное пространство данных. Работа с ними выполняется по опросу. 
Команды выполняются за 2 или 3 такта (в зависимости от типа команды): 1) Выборка команды 2) Выборка операндов 3) Выполнение команды. 




Регистры:

PC — счетчик команд
IR — регистр инструкций
AR — регистр адреса операнда
C — флаг переноса/заёма
Z — флаг нуля

Описание системы команд можно найти в файле Cmd.hs, функция cmd. Функциональность описана в комментариях с использование C синтаксиса. pmem и dmem - память программ и данных соответственно. arg - аргумент команды.




Модель.

Модель создавалась с учётом того, что бы в случае необходимости её можно было относительно легко перетащить в Verilog, а значит вариант с наивным case по командам отпадал вроде такого:
command 0x01 state = someFunctiom1 state
command 0x02 state = someFunctiom2 state
или в без точечном стиле (в основном будет использовать именно он):
command 0x01 = someFunctiom1
command 0x02 = someFunctiom2
Не подходит, так как в данном случае пришлось бы переписывать весь код полностью. Разбираясь с принципами работы процесса, довольно быстро становится очевидно, что наиболее простой способ описания работы команд - потактово задать функции, определяющие изменения состояния процессора после каждого из них. Функции на каждый такт можно получить при помощи композиции нескольких простейших функций, которые были бы в реальном процессоре представлены управляющими сигналами (назовём их микрокомандами, хотя ими они и не являются). К примеру, первый такт программы представляет из себя выборку команд, который может быть описан через композицию сигнала для инкриминирования регистра PC и чтения команды в регистр IR. В моделе это выглядит так:
nextCommand = incPC . pmem2IR




К сожалению, модель вычислений языка Haskell довольно сильно отличается от модели SDF, которая присутствует в Verilog (или отчасти присутствует), и мы вынуждены следить за соблюдением правильной последовательности операций при композиции функций. Использую этот подход, вся система команд может быть описана в одной таблице следующим образом:
cmd = [(0x01, Nop, NoneArg, [nop]), -- nope
       (0x00, Hlt, NoneArg, [hlt]), -- halt model
       (0x02, Ldm, DmemArg, [incPC . pmem2AR, dmem2acc]), -- acc = dmem[arg]; c = const; z = (dmem[arg] == 0)
       (0x03, Ldi, ValueArg, [incPC . pmem2acc]), -- acc = arg; c = const; z = (arg == 0)
...
В данном случае важен только первый (код команды) и последний (поведение) элемент кортежа (остальные необходимы для компилятора). Поведение команды задаётся списком функций, каждая из которых выполняется за такт. Значительная часть функций задаётся композицией микрокоманд которые имеют тип (State -> State).
Таким образом, описание микропроцессора сводится к определению: 
  • типа данных State - хранит состояние процессора в текущий момент; 
  • таблицы cmd, в которой описываются все команды; 
  • микрокоманды, путём композиции которых получаются собственно сами команды; 
Само моделирование производится следующим небольшим кусочком кода:




step (State {on = False}) = return ()
step state = step $ executeCommand state' commandFlow
    where 
      state'@(State{ir = hexCommand}) = nextCommand state
      commandFlow = commandFlowFromInfo $ getCommandInfo hexCommand
      executeCommand state [] = state
     executeCommand state (f:fs) = executeCommand (f state) fs




В нём происходит следующее, производится выборка команды (PC инкриминируется), из таблицы cmd выбираются команды, список функций команд по очереди применяется к текущему состоянию и далее по рекурсии. 




Явные недочёты: 

  • Внешние устройства "зашиты" в структуру модели. По хорошему их необходимо вынести из модели, и обрабатывать в step. (тут же использование Trace, для функциональности). 
  • Использование неправильных структур данных (в частности для cmd). 
  • В модуле отвечающем за моделирование захардкодено всё, что только можно. 
В ближайшее время будет описан разработанный в параллель компилятор, тогда же и будет полный код и продемонстрирована его работоспособность. 

Cmd.hs - http://gist.github.com/492325
SimpleRiscModel - http://gist.github.com/492327