История изменений
Исправление 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.