LINUX.ORG.RU
ФорумTalks

Более лучший Лисп

 


3

9

Что бы вы изменили и каким образом в своем любимом/нелюбимом диалекте Лиспа, чтобы он стал ещё лучше? Расскажите обо всех своих грязных фантазиях.

Лиспосрач (и те два унылых анонимуса) не приветствуется.

Перемещено tazhate из development

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

_если_ между типами A и B существует биекция A <=> B

То есть, vector<string> != vector<char *> (нет string для NULL), но vector<string> == list<string>. Так? А бывают ЯП, где это так и есть?

test $> x

Это как раз преобразование типов. Я имел в виду, что в CL я пишу (or null string integer) или (or string integer null), а chec-type и typep понимают, что эти типы идентичны. А в Haskell, если я часть функций определённых на (or string integer) определил с одним порядком, часть с другим (так как с точк зрения предметной области они неразличимы), то мне потом придётся в половину из них явным образом пихать преобразование, так как match по типу не делается.

Вот я и говорю, что с ней не нужно бороться - её нужно использовать как инструмент.

те же гетерогенные списки или массивы в Haskell

В Haskell - да, а вот в Scala уже ни чо - через Any.

В Scala, кстати, бороться и не приходится. Для меня у Scala только один минус — JVM. А как язык — очень стройный и красивый (а с literate programming в стиле http://circumflex.ru/api/2.0.2/circumflex-core/src/main/scala/circumflex.scal... вообще сказка).

В рантайме (там пример с «all args < 10» на это намекает)

То что можно — в compile-time (проверка константных выражений), то что нельзя — в рантайме. А по существу — действительно assert во всех местах, где класс что-либо обрабатывает (включая изменение полей).

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

То есть, vector<string> != vector<char *> (нет string для NULL), но vector<string> == list<string>. Так?

Да и да, если я не упускаю каких-то деталей.

А бывают ЯП, где это так и есть?

В применении к ЯП экстенсиональное равенство это сохранение логических инвариантов, то есть vector<A> == list<A>, в том смысле, что замена одного на другое не влияет на эти инварианты, так что с этой точки зрения эти два типа неразличимы.

Интенсиональное равенство в применении к ЯП это A == A, A == B, где B - цепочка синонимов до A, и всё, в общем-то. Могут быть случаи когда A == B не очевидно, но это так - например, разные структуры с одинаковыми layouts, в этом случае можно сделать unsafe [compile-time] cast, то есть «допустим, что A == B» - если мы правы, то всё будет хорошо, иначе _|_.

Ещё экстенсиональное равенство + конкретный вычислитель ставят вопрос об оптимальной реализации типа, в случае последовательностей это сложный вопрос, поэтому vector, list и прочие хоть и можно отождествить денотационно, но операционно, с точки зрения вычислителя, представления в памяти и т.п. это разные вещи. А вот для объединений и структур это простой вопрос - можно найти способ компиляции при котором объекты коммутирующих объединений или структур будут неразличимы операционно, как память (так что [compile-time] cast будет safe) и т.п.

А в Haskell, если я часть функций определённых на (or string integer) определил с одним порядком, часть с другим (так как с точк зрения предметной области они неразличимы), то мне потом придётся в половину из них явным образом пихать преобразование, так как match по типу не делается.

Как и в C, C++ и подобных языках - если or или and это настоящие типы (им можно нарисовать коммутативные диаграммы), то при простой компиляции получается как оно есть - это, в первую очередь, контроль за layouts, при хитрой компиляции с упорядочиванием по типам можно добиться неразличимости как в CL, но уже без такого контроля. Впрочем, в случае or-типов никаких layouts толком и нет, так что я даже не знаю - почему бы не сделать их неразличимыми.

Что касается or в CL - это в большей степени контракт с runtime проверками, поэтому его поведение может быть более свободным.

Для меня у Scala только один минус — JVM

Same here ;) Хотя, http://greedy.github.com/scala-llvm/ <- есть такое, но если допустить что оно будет доделано - получится вторая [недо] JVM или нет?

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

Same here ;) Хотя, http://greedy.github.com/scala-llvm/

llvm компилируется в exe. Если доделают, будет круто.

А с типизированными контейнерами есть ещё одна глобальная фигня. Если для обычных типов есть понятие subtype, то для контейнеров в коде

class A
{
};

class B : A
{
};

class C : B
{
};

B foo(std::vector<B> p)
{
};

void test()
{
   std::vector<B> a;
   std::vector<C> c;
   foo(a);
   foo(c);
}

неверны обе строчки вызова foo. Соответственно, пользоваться такими контейнерами можно только в том случае, если типов данных достаточно мало. Потому как функции max_age(vector<int<0,100>> age) придётся в любом коде определять возраст именно как целое от 0 до 100. Хотя в другом месте может быть от 16 до 50. Поэтому в Haskell я вообще не могу сделать тип (deftype age () (integer 0 100)). А почтиилюбая функция на контейнерах является шаблонной, то есть имеет тип вида list 'a -> 'a.

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

А с типизированными контейнерами есть ещё одна глобальная фигня

В С++. А в той же Scala

class A(xi: Int) {
  val x: Int = xi
}

class B(xi: Int, yi: String) extends A(xi) {
  val y: String = yi
}

object Test {

  def avg(xs: List[A]): Double = xs.foldLeft(.0)(_ + _.x) / xs.length

  def test: Double = {
    val xs: List[B] = List(new B(2, "a"), new B(3, "b"), new B(9, "c"))
    avg(xs)
  }

}

Поэтому в Haskell я вообще не могу сделать тип (deftype age () (integer 0 100))

Ну вообще можешь - как Fin типы. Но использовать неудобно, потому что зависимые типы в GHC неполноценны, а если ещё [16, 50] <: [0, 100], то есть зависимые типы + подтипирование, то ему вообще плохо станет, да.

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

в той же Scala

Это немножко читерство: List это не список, а immutable список. А с MutableList уже не работает. Например хочу я сделать.

def push(xs: MutableList[B], elem: B) : Unit = xs + elem

И сюда я уже MutableList[A] положить не могу, хоть в него и можно класть элементы типа B. Хотя, в целом, конечно, Scala для типизированного программирования лучший выбор.

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

Кстати, про статику и Lisp. Такие языки как Qi и Shen смотрел? Там и типы лисповые (с объединениями, зависимые, и т.д.) и статика есть. Правда я не понял как туда FFI пробрасывать, чтобы что-нибудь полезное запускать :-(

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

List это не список, а immutable список. А с MutableList уже не работает

List ковариантен, то есть List[+A], так что справедливо A <: B => List[A] <: List[ B ] и соответствующие конверсии. MutableList при этом инвариантен, то есть MutableList[A] - без конверсий, возможную ковариантность нужно задавать явно:

import scala.collection.mutable._

class A(xi: Int) {
  var x: Int = xi
  override def toString() = "A(" + x + ")"
}

class B(xi: Int, yi: String) extends A(xi) {
  var y: String = yi
  override def toString() = "B(" + x + ", \"" + y + "\")"
}

object Test {

  def push[T <: A](xs: MutableList[T], elem: T) {
    println("add elem: _ <: A with elem.x = " + elem.x)
    xs += elem
  }

  def test = {

    val xs: MutableList[A] = MutableList(new A(1), new B(2, "b"))
    val ys: MutableList[B] = MutableList(new B(3, "c"), new B(4, "d"))

    push(xs, new A(3))
    push(xs, new B(4, "d"))
    push(ys, new B(5, "e"))

    println(xs)
    println(ys)

  }

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

возможную ковариантность нужно задавать явно:

Ух ты! Спасибо! Эту фишку я просмотрел.

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