LINUX.ORG.RU

История изменений

Исправление quasimoto, (текущая версия) :

union в C - не ломает, т.к. и ломать нечего. Union вообще не содержит информации о типе, если её туда руками не вносить, какая тут диспатчеризация по типу?

Обычно (ну или иногда) union тегирован тегом типа, например:

#include <cstdio>

class IntOrString {
  public:
    enum { INT, STRING } tag;
    IntOrString(int i) : tag(INT) { content.as_int = i; }
    IntOrString(char *s) : tag(STRING) { content.as_string = s; }
    void operator=(int i) { tag = INT; content.as_int = i; }
    void operator=(char *s) { tag = STRING; content.as_string = s; }
    template <typename T> T get() const;
  private:
    union { int as_int; char *as_string; } content;
};

template <> const int* IntOrString::get() const
{
    return tag == INT ? &content.as_int : 0;
}

template <> const char* const* IntOrString::get() const
{
    return tag == STRING ? &content.as_string : 0;
}

#define typecase(X) switch ((X).tag)
#define begin_case(T) case (T) : {
#define end_case break; }

void test_it(const IntOrString &x)
{
    typecase (x) {

        begin_case (IntOrString::INT)
            const int *i = x.get<const int*>();
            if (i) printf("It is int: %d\n", *i);
        end_case

        begin_case (IntOrString::STRING)
            const char* const* s = x.get<const char* const*>();
            if (s) printf("It is string: %s\n", *s);
        end_case

    }
}

int main()
{
    IntOrString x = 123;
    test_it(x);
    x = "abc";
    test_it(x);
}

// ➜  ~  ./a.out                          
// It is int: 123
// It is string: abc

Получается немного безопаснее сырого объединения, но всё равно не удобно и можно ошибиться и при написании варианта, и при использовании.

boost::variant это то же тегированный union, но с выносом типов объединяемых вещей в параметры типа:

#include <cstdio>
#include <string>
#include <boost/variant.hpp>

int main()
{
    // (or int string)
    typedef boost::variant<int, std::string> IntOrString;

    // typecase
    struct TestIt : boost::static_visitor<> {
        void operator()(int i) const { printf("It is int: %d\n", i); }
        void operator()(std::string &s) const { printf("It is string: %s\n", s.c_str()); }
    };

    IntOrString x = 123;
    boost::apply_visitor(TestIt(), x);
    x = "abc";
    boost::apply_visitor(TestIt(), x);
}

Получается проще и безопаснее.

Either в Haskell и Scala это такой же ADT:

test :: Either Int String -> IO ()
test = either (printf "This is int: %d\n") (printf "This is string: %s\n")

main :: IO ()
main = do
  let x = Left 123
  test x
  let x = Right "abc"
  test x

Это слишком простая вещь, чтобы она как-то смогла сломать ООП.

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

trait IntOrString
case class MyInt(i: Int) extends IntOrString
case class MyString(s: String) extends IntOrString

object Test {

  def test(x: IntOrString) {
    x match {
      case MyInt(i) => println("This is int: " + i)
      case MyString(s) => println("This is string: " + s)
    }
  }

  def run {
    var x: IntOrString = MyInt(123)
    test(x)
    x = MyString("abc")
    test(x)
  }

}

// scala> Test.run
// This is int: 123
// This is string: abc

дописыванием

case class MyFoo() extends IntOrString

object Test2 {

  def run {
    var x: IntOrString = MyFoo()
    Test.test(x)
  }

}

// scala> Test2.run
// scala.MatchError: MyFoo() (of class MyFoo)

получаем исключение за счёт non-exhaustiveness в Test.run. Решается с помощью

- trait IntOrString
+ sealed trait IntOrString
- case class MyString(s: String) extends IntOrString
+ final case class MyString(s: String) extends IntOrString

Хотя в лиспе за non-exhaustiveness и исключения вообще не принято волноваться, так что там это и не проблема.

Я имел в виду, что если нам пришёл number, а у нас два метода: один для number, другой - для number or string, то неясно, что делать.

Очевидно, что для диспетчеризации (or number string) из type-level синонима должен превратиться в нормальный ADT (tagged union), так что number и (or number string) станут различимы в рантайме (тег там или боксинг - другой вопрос). Поэтому я и говорю «дорого». Таких вещей в стандартном CL не наблюдается - только предельный случай типа t и typecase.

Исправление quasimoto, :

union в C - не ломает, т.к. и ломать нечего. Union вообще не содержит информации о типе, если её туда руками не вносить, какая тут диспатчеризация по типу?

Обычно (ну или иногда) union тегирован тегом типа, например:

#include <cstdio>

class IntOrString {
  public:
    enum { INT, STRING } tag;
    IntOrString(int i) : tag(INT) { content.as_int = i; }
    IntOrString(char *s) : tag(STRING) { content.as_string = s; }
    void operator=(int i) { tag = INT; content.as_int = i; }
    void operator=(char *s) { tag = STRING; content.as_string = s; }
    template <typename T> T get() const;
  private:
    union { int as_int; char *as_string; } content;
};

template <> const int* IntOrString::get() const
{
    return tag == INT ? &content.as_int : 0;
}

template <> const char* const* IntOrString::get() const
{
    return tag == STRING ? &content.as_string : 0;
}

#define typecase(X) switch ((X).tag)
#define begin_case(T) case (T) : {
#define end_case break; }

void test_it(const IntOrString &x)
{
    typecase (x) {

        begin_case (IntOrString::INT)
            const int *i = x.get<const int*>();
            if (i) printf("It is int: %d\n", *i);
        end_case

        begin_case (IntOrString::STRING)
            const char* const* s = x.get<const char* const*>();
            if (s) printf("It is string: %s\n", *s);
        end_case

    }
}

int main()
{
    IntOrString x = 123;
    test_it(x);
    x = "abc";
    test_it(x);
}

// ➜  ~  ./a.out                          
// It is int: 123
// It is string: abc

Получается немного безопаснее сырого объединения, но всё равно не удобно и можно ошибиться и при написании варианта, и при использовании.

boost::variant это то же тегированный union, но с выносом типов объединяемых вещей в параметры типа:

#include <cstdio>
#include <string>
#include <boost/variant.hpp>

int main()
{
    // (or int string)
    typedef boost::variant<int, std::string> IntOrString;

    // typecase
    struct TestIt : boost::static_visitor<> {
        void operator()(int i) const { printf("It is int: %d\n", i); }
        void operator()(std::string &s) const { printf("It is string: %s\n", s.c_str()); }
    };

    IntOrString x = 123;
    boost::apply_visitor(TestIt(), x);
    x = "abc";
    boost::apply_visitor(TestIt(), x);
}

Получается проще и безопаснее.

Either в Haskell и Scala это такой же ADT:

test :: Either Int String -> IO ()
test = either (printf "This is int: %d\n") (printf "This is string: %s\n")

main :: IO ()
main = do
  let x = Left 123
  test x
  let x = Right "abc"
  test x

Это слишком простая вещь, чтобы она как-то смогла сломать ООП.

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

trait IntOrString
case class MyInt(i: Int) extends IntOrString
case class MyString(s: String) extends IntOrString

object Test {

  def test(x: IntOrString) {
    x match {
      case MyInt(i) => println("This is int: " + i)
      case MyString(s) => println("This is string: " + s)
    }
  }

  def run {
    var x: IntOrString = MyInt(123)
    test(x)
    x = MyString("abc")
    test(x)
  }

}

// scala> Test.run
// This is int: 123
// This is string: abc

дописыванием

case class MyFoo() extends IntOrString

object Test2 {

  def run {
    var x: IntOrString = MyFoo()
    Test.test(x)
  }

}

// scala> Test2.run
// scala.MatchError: MyFoo() (of class MyFoo)

получаем исключение за счёт non-exhaustiveness в Test.run. Решается с помощью

- trait IntOrString
+ sealed trait IntOrString
- case class MyString(s: String) extends IntOrString
+ final case class MyString(s: String) extends IntOrString

Хотя в лиспе за non-exhaustiveness и исключения вообще не принято волноваться, так что там это и не проблема.

Я имел в виду, что если нам пришёл number, а у нас два метода: один для number, другой - для number or string, то неясно, что делать.

Очевидно, что для диспетчеризации (or number string) из type-level синонима должен превратится в нормальный ADT (tagged union), так что number и (or number string) станут различимы в рантайме (тег там или боксинг - другой вопрос). Поэтому я и говорю «дорого». Таких вещей в стандартном CL не наблюдается - только предельный случай типа t и typecase.

Исходная версия quasimoto, :

union в C - не ломает, т.к. и ломать нечего. Union вообще не содержит информации о типе, если её туда руками не вносить, какая тут диспатчеризация по типу?

Обычно (ну или иногда) union тегирован тегом типа, например:

#include <cstdio>

class IntOrString {
  public:
    enum { INT, STRING } tag;
    IntOrString(int i) : tag(INT) { content.as_int = i; }
    IntOrString(char *s) : tag(STRING) { content.as_string = s; }
    void operator=(int i) { tag = INT; content.as_int = i; }
    void operator=(char *s) { tag = STRING; content.as_string = s; }
    template <typename T> T get() const;
  private:
    union { int as_int; char *as_string; } content;
};

template <> const int* IntOrString::get() const
{
    return tag == INT ? &content.as_int : 0;
}

template <> const char* const* IntOrString::get() const
{
    return tag == STRING ? &(content.as_string) : 0;
}

#define typecase(X) switch ((X).tag)
#define begin_case(T) case (T) : {
#define end_case break; }

void test_it(const IntOrString &x)
{
    typecase (x) {

        begin_case (IntOrString::INT)
            const int *i = x.get<const int*>();
            if (i) printf("It is int: %d\n", *i);
        end_case

        begin_case (IntOrString::STRING)
            const char* const* s = x.get<const char* const*>();
            if (s) printf("It is string: %s\n", *s);
        end_case

    }
}

int main()
{
    IntOrString x = 123;
    test_it(x);
    x = "abc";
    test_it(x);
}

// ➜  ~  ./a.out                          
// It is int: 123
// It is string: abc

Получается немного безопаснее сырого объединения, но всё равно не удобно и можно ошибиться и при написании варианта, и при использовании.

boost::variant это то же тегированный union, но с выносом типов объединяемых вещей в параметры типа:

#include <cstdio>
#include <string>
#include <boost/variant.hpp>

int main()
{
    // (or int string)
    typedef boost::variant<int, std::string> IntOrString;

    // typecase
    struct TestIt : boost::static_visitor<> {
        void operator()(int i) const { printf("It is int: %d\n", i); }
        void operator()(std::string &s) const { printf("It is string: %s\n", s.c_str()); }
    };

    IntOrString x = 123;
    boost::apply_visitor(TestIt(), x);
    x = "abc";
    boost::apply_visitor(TestIt(), x);
}

Получается проще и безопаснее.

Either в Haskell и Scala это такой же ADT:

test :: Either Int String -> IO ()
test = either (printf "This is int: %d\n") (printf "This is string: %s\n")

main :: IO ()
main = do
  let x = Left 123
  test x
  let x = Right "abc"
  test x

Это слишком простая вещь, чтобы она как-то смогла сломать ООП.

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

trait IntOrString
case class MyInt(i: Int) extends IntOrString
case class MyString(s: String) extends IntOrString

object Test {

  def test(x: IntOrString) {
    x match {
      case MyInt(i) => println("This is int: " + i)
      case MyString(s) => println("This is string: " + s)
    }
  }

  def run {
    var x: IntOrString = MyInt(123)
    test(x)
    x = MyString("abc")
    test(x)
  }

}

// scala> Test.run
// This is int: 123
// This is string: abc

дописыванием

case class MyFoo() extends IntOrString

object Test2 {

  def run {
    var x: IntOrString = MyFoo()
    Test.test(x)
  }

}

// scala> Test2.run
// scala.MatchError: MyFoo() (of class MyFoo)

получаем исключение за счёт non-exhaustiveness в Test.run. Решается с помощью

- trait IntOrString
+ sealed trait IntOrString
- case class MyString(s: String) extends IntOrString
+ final case class MyString(s: String) extends IntOrString

Хотя в лиспе за non-exhaustiveness и исключения вообще не принято волноваться, так что там это и не проблема.

Я имел в виду, что если нам пришёл number, а у нас два метода: один для number, другой - для number or string, то неясно, что делать.

Очевидно, что для диспетчеризации (or number string) из type-level синонима должен превратится в нормальный ADT (tagged union), так что number и (or number string) станут различимы в рантайме (тег там или боксинг - другой вопрос). Поэтому я и говорю «дорого». Таких вещей в стандартном CL не наблюдается - только предельный случай типа t и typecase.