LINUX.ORG.RU

Решил попробовать написать некоторую вариацию restarts

 , ,


1

7

Для Ъ: перезапуски Lisp по-простому - более общая концепция exceptions, при которой стек не раскручивается, то-есть обработка исключительной ситуации происходит в месте возникновения, а не выше по стеку полностью прервав исполнение. То-есть концептуально алгоритм все еще может продолжить работу если код который его вызывал предложил способы разрулить нестандартные ситуации по мере выполнения. Отличие от лямбд переданных во внутрь алгоритма заключается просто в том, что они явно не передаются, а задаются в форме похожей на обычный exception и работают через 100500 уровней.

  object RestartContext{
    private val contextHolder = new ThreadLocal[PartialFunction[Any,Any]]{
      override def initialValue() = Map()
    }

    def context = contextHolder.get

    def context_=(value:PartialFunction[Any,Any]){
      contextHolder.set(value)
    }
  }

  class CatchHolder[T](body: =>T){
    def catchRestarts(catchBody: PartialFunction[Any,Any]):T={
      val oldContext = RestartContext.context
      RestartContext.context = catchBody.orElse(oldContext)
      try{
        body
      } finally {
        RestartContext.context = oldContext
      }
    }
  }

  def tryRestarts[T](body: =>T) = new CatchHolder[T](body)

  case class NotFoundRestartException() extends RuntimeException
  
  def raiseRestart[T](restart:T):Any={
    RestartContext.context.lift(restart) match {
      case Some(result)=>result
      case None => throw NotFoundRestartException()
    }
  }

Используем

  case class WhatIsNext(value:Int)

  abstract sealed trait CuriousRestart
  case class NextValue(next:Int) extends CuriousRestart
  case object HaveNoIdea extends CuriousRestart  

  def printNext(value:Int){
    // Cannot decide itself, this function is retarded
    raiseRestart(WhatIsNext(value)) match {
      case NextValue(next) => println("Next for %s is %s".format(value,next))
      case HaveNoIdea => throw new IllegalArgumentException("Unable to find next for %s".format(value))
    }
  }
  
  def main(argv:Array[String]){

    tryRestarts{
      printNext(10)
      printNext(20)
    }catchRestarts{
      case WhatIsNext(value) if (value<15) => NextValue(value+1)
      case WhatIsNext(_) => HaveNoIdea
    }
  }

Кейс классы необязательны, просто хотел выразить суть

Есть ряд моментов которые выглядят уныло. Испытываю физическую боль от вида [Any,Any], но что тут можно придумать? Ну и ThreadLocal - вынужденное зло. Может кто-то посоветуют как сделать без угрызений совести?

Еще есть ощущение что возможно с грохотом отвалится если просочатся некоторые замкнувшие контекст лямбды с рестартом, но это вроде уже бред.

В отличии от лиспа тут не предусмотрен ручной выбор функции, все в коде.

P.S. Кастую еще имплементации рестартов (возможно ваши) на других ЯП в которых их изначально нету.

★★★★★

Последнее исправление: vertexua (всего исправлений: 5)
Ответ на: комментарий от den73

SLIME даёт не только completion

Я вообще не говорил про autocompletion.

а ещё: подсказку по параметрам, показывает определение, показывает справку по стандартным функциям ...

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

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

Представьте гипотетически что макрос начали применять повсеместно. Тем, чем становится пользоваться проще начинают пользоваться чаще. Сколько будет сокрушающихся лисперов что образ выражения в памяти теперь сильно отличается от вида в коде? Психологические факторы нужно в таких вопросах учитывать, многие вещи воспринимаются не так как подсказывает здравый смысл (пользуешься - ожидай). Вы такими макросами повышаете уровень ЯП на уровне исходного кода, а ним как раз просто манипулировать из-за простоты формы

Ах-ха-ха, что за идиот!

Собрались жабер, джангист и делфятник Лисп обсуждать... Ах-ха-ха! Точечек нету, видите ли, формочки не удобно рисовать.

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

Например, если я напишу (declare (type mytype myvalue)), то myvalue уже не может быть nil-ом, в отличие от C,Java или Delphi. Т.е, нужно или писать тип (or mytype null) с неизвестными последствиями для оптимальности, либо делать null object для каждого типа. Оба решения - уродство. Это - большая проблема, если внедрять статическую типизацию

Странно, по-моему в статическом мире как раз стараются уйти от неявных nil'ов в сторону option-types, дабы проще было исключить NullPointerException на этапе компиляции.

http://confluence.jetbrains.net/display/Kotlin/Null-safety

http://en.wikipedia.org/wiki/Nullable_type

In contrast, object pointers can be set to NULL by default in most common languages, meaning that the pointer or reference points to nowhere, that no object is assigned (the variable does not point to any object). Nullable references were invented by C.A.R. Hoare in 1965 as part of the Algol W language. Hoare later described their invention as a «billion dollar mistake».[4] This is because object pointers that can be NULL require the user to check the pointer before using it and require specific code to handle the case when the object pointer is NULL. In some languages, like Objective-C,[5] however, NULL object pointers can be used without problems.

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

Чего прицепился? В лиспе методы CLOS сводятся к обычным функциям. Добавление части имени класса - это просто один из способов избежать конфликта имен. Да и просто удобно это становится, когда освоишься с методой. Понятно, что за функция. Легко найти по автокомплиту в LispWorks.

Что касается пассажа про производительность, то могу сказать простую вещь: мне IDE обычно мешает набирать код. Используя Emacs, как правило, я набираю быстрее, чем в IntelliJ IDEA. А используя jEdit, обычно получается быстрее набирать, чем в Visual Studio. Не вижу в этом ничего необычного. Ни редактор Emacs, ни редактор jEdit не мешают своим автокомплитом и прочими мешунчиками. Это, пожалуй, главное, что мне нравится в легковестных редакторах.

Кстати, редактор LispWorks как раз не мешает, что очень приятно. Listener и прочие тулзы же делают LispWorks очень убодным. Не знаю, насколько правомерно среду LispWorks причислять к IDE, но для меня она находится больше в лагере Emacs/jEdit, чем в лагере монструозных Visual Studio и IntelliJ IDEA. С Visual Studio я имею дело каждый рабочий день уже второй год, а до этого много лет проработал с jEdit.

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

Чего прицепился? В лиспе методы CLOS сводятся к обычным функциям. Добавление части имени класса - это просто один из способов избежать конфликта имен. Да и просто удобно это становится, когда освоишься с методой. Понятно, что за функция. Легко найти по автокомплиту в LispWorks.

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

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

Везде нужен компромисс. Никто не говорит, что всегда следует добавлять часть имени класса. Но если ты точно знаешь, что имеешь дело с node, и все методы могут быть определены только для класса node и его производных rect-node, geometry-node, shape-node, diagram-node и т.д, то почему бы обобщенную функцию взятия координаты x не назвать просто node-x? Я думаю, такая метода помогает писать большие по размеру приложения.

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

Везде нужен компромисс. Никто не говорит, что всегда следует добавлять часть имени класса. Но если ты точно знаешь, что имеешь дело с node, и все методы могут быть определены только для класса node и его производных rect-node, geometry-node, shape-node, diagram-node и т.д, то почему бы обобщенную функцию взятия координаты x не назвать просто node-x? Я думаю, такая метода помогает писать большие по размеру приложения.

А как можно запретить специализировать методы для других классов, не связанных с node?

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

Добавление части имени класса - это просто один из способов избежать конфликта имен.

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

мне IDE обычно мешает набирать код

Чем emacs + slime не ide?

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

Причем здесь это?

При том, что с твои кодом будет работать сторонний разработчик и ты сам через 2-3 месяца забудешь, работает там функция _только_ с node (или не только) и в какой фазе Луны. О том, что префикс в названии функции может устареть, не забывай: вначале работала с -node, позже с -node-n2, -node-with-bj-and-wh

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

Странно, по-моему в статическом мире как раз стараются уйти от неявных nil'ов

Ну, с практической точки зрения хорошо то, что null pointer exception отлавливается аппаратно и можно отловить исключение (в крайнем случае, упасть в дебаггер). Видимо, данная проверка (на защиту памяти) не замедляет исполнения, хотя точно я не знаю. null - это «объект может быть, а может и не быть». Практика работы в SQL хорошо показывает, что null полезен. Null, равный нулевому адресу для всех типов, более полиморфен, чем частные null объекты (представим себе хотя бы проблему с множественным наследованием - null какого из предков брать?), его проще сравнивать и он занимает меньше места в памяти. С точки зрения выразительности, ситуация,когда «объекта может не быть», встречается очень часто. Хотя задуматься есть о чём.

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

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

При том, что с твои кодом будет работать сторонний разработчик и ты сам через >2-3 месяца забудешь, работает там функция _только_ с node (или не только) и в >какой фазе Луны.

В лиспе это можно решить как раз с помощью интроспекции. Я тоже думаю о таком выходе, собственно, мои крышечки и делают ничто иное, как подставляют тип в качестве префикса. Мысля в понятиях Delphi (в С++ будет то же самое и в Java, насколько я знаю, тоже), функция, определённая в интерфейсе, или вирт.ф-я, впервые определённая в некотором базовом классе, должна иметь префикс этого интерфейса или класса. Скажем, является дурным тоном специализировать её для не-потомков класса (и, может быть, удастся это даже предотвратить, если есть куда внедриться с проверкой).

Хотя автор затронул интересную тему, почему бы к ней не вернуться?

den73 ★★★★★
()

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

Если же рестарт нужен именно как рестарт, а состояние стека необязательно знать, то это - просто обработка исключений + цикл.

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

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

рядом с котором мамонты резвятся на лужайках и огонь (пакеты) являются случайной неосвоенной роскошью

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

1. свой ридер, позволяющий перехватить чтение символа.

2. свой defpackage со следующими возможностями:

2.1. автоматически разруливает конфликты консервативным способом: если символ конфликтует, его запрещаем для чтения. Свой символ в пакете с таким именем будет (с помощью :shadow), но мой ридер не даст его прочитать, а значение и функция у него будут такие, чтобы он сразу выдавал ошибку при любом использовании в коде. Аналогичный способ решения конфликтов имён применяется в SQL и представляется наилучшим для масштабирования.

2.2 Перечень экспортов у меня может представлять из себя строку, т.е. (def-merge-packages::! :foo :export «foo:s1 ; а здесь комментарий foo:s2 ») тем самым, можно прямо в редакторе нажать M-. и пройтись по определениям пакета. Обычно пишут #:s1, который никуда не ведёт.

2.3 Локальные никнеймы пакетов. Допустим, в моём пакете my-package-1, я могу обращаться к пакету cl-user по имени c, а в остальных пакетах - не могу. Этому помогает ридер.

3. Команда complete-package-name, понимающая локальные никнеймы из п.2.3.

Однако, пакеты не заменят статической типизации с точкой. Дело в том, что динамическая переменная является двусмысленной, это - в её природе, и за это надо платить. Может некоторое время везти, пока двусмысленность не успеет проявиться в виде одноимённых, но разных методов, но это время быстро закончится. И тогда - один хрен - вместо имени типа в mystruct-myfield придётся бубнить mypackage::myfield. Даже если будет ясно, что за родовую функцию мы вызвали - нажми на нём M-. и получишь не один метод, а целую пачку, которую всё равно всю придётся перелистать, чтобы понять, как работает код. И, если тип переменной не задан, то и количество вариантов поведения кода равно количеству методов. Если же тип задан, то количество методов уменьшается. Даже если IDE не может прочитать декларацию, то хотя бы сам программист может пролистать явно неподходящие методы, которые заведомо не будут вызваны (хотя это и будет потерей времени). Т.е., даже в условиях, когда родовая функция не принадлежит классу и у нас нет IDE, ограничение типа переменной выгодно.

Статическая типизация хороша тем, что мы ОДИН раз во время написания кода называем тип переменной, а потом МНОГО раз, во время написания и исполнения, этой информацией пользуемся, экономя текст и силы.

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

korvin_

А как можно запретить специализировать методы для других классов, не связанных с node?

Proof of concept:

(defclass a () ())

(defgeneric f (x))

(defmethod sb-mop:add-method :before ((function (eql #'f)) method)
  (let ((class (car (sb-mop:method-specializers method)))
        (a (find-class 'a)))
    (unless (find a (sb-mop:compute-class-precedence-list class))
      (error "Class ~s is not a subclass of ~s" class a))))
anonymous
()
Ответ на: комментарий от den73

1. свой ридер

Так пекеджи в CL фундаментально сломаны именно потому, что сделаны на ридере. И тут уж как ни крути, а модульность через ридер - это жуткие костыли, и тому, кто придумал такую хуйню, следует оторвать руки. С другой стороны, учитывая, что рантайм в CL очень кривой и негибкий, другого способа-то, наверное, и не было. Не сделаешь конфетки из говна.

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

А кто гарантирует, что потом не вызовут remove-method какой-нибудь?

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