Is there a reason to have a bottom type in a programming language?

A bottom type is a construct primarily appearing in mathematical type theory. It is also called the empty type. It is a type that has no values, but is a subtype of all types.

If a function’s return type is the bottom type, that means that it does not return. Period. Maybe it loops forever, or maybe it throws an exception.

What is the point of having this weird type in a programming language? It’s not that common, but it is present in some, such as Scala and Lisp.

14

I’ll take a simple example: C++ vs Rust.

Here is a function used to throw an exception in C++11:

[[noreturn]] void ThrowException(char const* message,
                                 char const* file,
                                 int line,
                                 char const* function);

And here is the equivalent in Rust:

fn formatted_panic(message: &str, file: &str, line: isize, function: &str) -> !;

On a purely syntactic matter, the Rust construct is more sensible. Note that the C++ construct specifies a return type even though it also specifies it is not going to return. That’s a bit weird.

On a standard note, the C++ syntax only appeared with C++11 (it was tacked on top), but various compilers had been providing various extensions for a while, so that third party analysis tools had to be programmed to recognize the various ways this attribute could be written. Having it standardized is obviously clearly superior.


Now, as for the benefit?

The fact that a function does not return can be useful for:

  • optimization: one can prune any code after it (it won’t return), there is no need to save the registers (as it won’t be necessary to restore them), …
  • static analysis: it eliminates a number of potential execution paths
  • maintainability: (see static analysis, but by humans)

10

Karl’s answer is good. Here is an additional use that I don’t think anyone else has mentioned. The type of

if E then A else B

should be a type that includes all the values in the type of A and all the values in the type of B. If the type of B is Nothing, then the type of the if expression can be the type of A. I’ll often declare a routine

def unreachable( s:String ) : Nothing = throw new AssertionError("Unreachable "+s) 

to say that code is not expected to be reached. Since its type is Nothing, unreachable(s) can now be used in any if or (more often) switch without affecting the type of result. For example

 val colour : Colour := switch state of
         BLACK_TO_MOVE: BLACK
         WHITE_TO_MOVE: WHITE
         default: unreachable("Bad state")

Scala has such a Nothing type.

Another use case for Nothing (as mentioned in Karl’s answer) is List[Nothing] is the type of lists each of whose members has type Nothing. Thus it can be the type of the empty list.

The key property of Nothing that makes these use cases work is not that it has no values –although in Scala, for example, it does have no values– it is that it is a subtype of every other type.

Suppose you have a language where every type contains the same value — let’s call it (). In such a language the unit type, which has () as its only value, could be a subtype of every type. That doesn’t make it a bottom type in the sense that the OP meant; the OP was clear that a bottom type contains no values. However, as it is a type that is a subtype of every type, it can play much the same role as a bottom type.

Haskell does things a bit differently. In Haskell, an expression that never produces a value can have the type scheme forall a.a. An instance of this type scheme will unify with any other type, so it effectively acts as a bottom type, even though (standard) Haskell has no notion of subtyping. For example, the error function from the standard prelude has type scheme forall a. [Char] -> a. So you can write

if E then A else error ""

and the type of the expression will be the same as the type of A, for any expression A.

The empty list in Haskell has the type scheme forall a. [a]. If A is an expression whose type is a list type, then

if E then A else []

is an expression with the same type as A.

2

Types form a monoid in two ways, together making a semiring. That’s what’s called algebraic data types. For finite types, this semiring directly relates to the semiring of natural numbers (including zero), which means you count how many possible values the type has (excluding “nonterminating values”).

  • The bottom type (I’ll call it Vacuous) has zero values.
  • The unit type has one value. I’ll call both the type and its single value ().
  • Composition (which most programming languages support quite directly, through records / structs / classes with public fields) is a product operation. For instance, (Bool, Bool) has four possible values, namely (False,False), (False,True), (True,False) and (True,True).
    The unit type is the identity element of the composition operation. E.g. ((), False) and ((), True) are the only values of type ((), Bool), so this type is isomorphic to Bool itself.
  • Alternative types are somewhat neglected in most languages (OO languages kind-of support them with inheritance), but they are no less useful. An alternative between two types A and B basically has all the values of A, plus all the values of B, hence sum type. For instance, Either () Bool has three values, I’ll call them Left (), Right False and Right True.
    The bottom type is the identity element of the sum: Either Vacuous A has only values of the form Right a, because Left ... doesn’t make sense (Vacuous has no values).

What’s interesting about these monoids is that, when you introduce functions to your language, the category of these types with the functions as morphisms is a monoidal category. Amongst other things, this allows you to define applicative functors and monads, which turn out to be an excellent abstraction for general computations (possibly involving side-effects etc.) within otherwise purely functional terms.

Now, actually you can get quite far with worrying only one side of the issue (the composition monoid), then you don’t really need the bottom type explicitly. For instance, even Haskell did for a long time not have a standard bottom type. Now it has, it’s called Void.

But when you consider the full picture, as a bicartesian closed category, then the type system is actually equivalent to the whole lambda calculus, so basically you have the perfect abstraction over everything possible in a Turing-complete language. Great for embedded domain-specific languages, for instance there’s a project about directly coding electronic circuits this way.

Of course, you may well say that this is all theoretists’ general nonsense. You don’t need to know about category theory at all to be a good programmer, but when you do, it gives you powerful and ridiculously general ways to reason about code, and proove invariants.


mb21 reminds me to note that this should not be confused with bottom values. In lazy languages like Haskell, every type contains a bottom “value”, denoted . This isn’t a concrete thing that you could ever explicitly pass around, instead it’s what’s “returned” for example when a function loops forever. Even Haskell’s Void type “contains” the bottom value, thus the name. In that light, Haskell’s bottom type really has one value and its unit type has two values, but in category-theory discussion this is generally ignored.

1

Maybe it loops forever, or maybe it throws an exception.

Sounds like a useful type to have in those situations, rare though they may be.

Also, even though Nothing (Scala’s name for the bottom type) can have no values, List[Nothing] does not have that restriction, which makes it useful as the type of an empty list. Most languages get around this by making an empty list of strings a different type than an empty list of integers, which kind of makes sense, but makes an empty list more verbose to write, which is a big drawback in a list-oriented language.

6

It is useful for static analysis to document the fact that a particular code path is not reachable. For example if you write the following in C#:

int F(int arg) {
 if (arg != 0)
  return arg + 1; //some computation
 else
  Assert(false); //this throws but the compiler does not know that
}
void Assert(bool cond) { if (!cond) throw ...; }

The compiler will complain that F does not return anything in at least one code path. If Assert were to be marked as non-returning the compiler would not need to warn.

In some languages, null has the bottom type, since the subtype of all types nicely defines what languages use null for (despite the mild contradiction of having null be both itself and a function that returns itself, avoiding the common arguments about why bot should be uninhabited).

It can also be used as a catch-all in function types (any -> bot) to handle dispatch gone awry.

And some languages allow you to actually resolve bot as an error, which can be used to provide custom compiler errors.

9

Yes this is a quite useful type; while its role would be mostly interior to the type system, there are some occasion where the bottom type would appear in openly.

Consider a statically typed language in which conditionals are expressions (so the if-then-else construction doubles as the ternary operator of C and friends, and there might be a similar multi-way case statement). Functional programming language have this, but it happens in certain imperative languages as well (ever since ALGOL 60). Then all branch expressions must ultimately produce the type of the whole conditional expression. One could simply require their types to be equal (and I think this is the case for the ternary operator in C) but this is overly restrictive especially when the conditional can also be used as conditional statement (not returning any useful value). In general one wants each branch expression to be (implicitly) convertible to a common type that will be the type of the full expression (possibly with more or less complicated restrictions to allow that common type to be effectively found by the complier, cf. C++, but I won’t go into those details here).

There are two kinds of situations where a general kind of conversion will allow necessary flexibility of such conditional expressions. One is already mentioned, where the result type is the unit type void; this is naturally a super-type of all other types, and allowing any type to be (trivially) converted to it makes it possible to use the conditional expression as conditional statement. The other involves cases where the expression does return a useful value, but one or more branches are incapable of producing one. They will usually raise an exception or involve a jump, and requiring them to (also) produce a value of the type of the whole expression (from an unreachable point) would be pointless. It is this kind of situation that can be gracefully handled by giving exception-raising clauses, jumps, and calls that will have such an effect, the bottom type, the one type that can be (trivially) converted into any other type.

I would suggest writing such a bottom type as * to suggest its convertibility to arbitrary type. It may serve other useful purposes internally, for instance when trying to deduce a result type for a recursive function that does not declare any, the type inferencer could assign the type * to any recursive call to avoid a chicken-and-egg situation; the actual type will be determined by non-recursive branches, and the recursive ones will be converted to the common type of the non-recursive ones. If there are no non-recursive branches at all, the type will remain *, and correctly indicate that the function has no possible way of ever returning from the recursion. Other than this and as result type of exception throwing functions, one can use * as component type of sequences of length 0, for instance of the empty list; again if ever an element is selected from an expression of type [*] (necessarily empty list), then the resulting type * will correctly indicated that this can never return without an error.

7

In some languages, you can annotate a function to tell both the compiler and developers that a call to this function isn’t going to return (and if the function is written in a way that it can return, the compiler won’t allow it). That’s a useful thing to know, but in the end you can call a function like this like any other. The compiler can use the information for optimisation, to give warnings about dead code, and so on. So there is no very compelling reason to have this type, but no very compelling reason to avoid it either.

In many languages, a function can return “void”. What that exactly means depends on the language. In C it means the function returns nothing. In Swift, it means the function returns an object with only one possible value, and since there is only one possible value that value takes zero bits and doesn’t actually require any code. In either case, that’s not the same as “bottom”.

“bottom” would be a type with no possible values. It can never exist. If a function returns “bottom”, it cannot actually return, because there is no value of type “bottom” that it could return.

If a language designer feels like it, then there is no reason to not have that type. The implementation is not difficult (you can implement it exactly like a function returning void and marked as “doesn’t return”). You can’t mix pointers to functions returning bottom with pointers to functions returning void, because they are not the same type).

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa

Is there a reason to have a bottom type in a programming language?

A bottom type is a construct primarily appearing in mathematical type theory. It is also called the empty type. It is a type that has no values, but is a subtype of all types.

If a function’s return type is the bottom type, that means that it does not return. Period. Maybe it loops forever, or maybe it throws an exception.

What is the point of having this weird type in a programming language? It’s not that common, but it is present in some, such as Scala and Lisp.

14

I’ll take a simple example: C++ vs Rust.

Here is a function used to throw an exception in C++11:

[[noreturn]] void ThrowException(char const* message,
                                 char const* file,
                                 int line,
                                 char const* function);

And here is the equivalent in Rust:

fn formatted_panic(message: &str, file: &str, line: isize, function: &str) -> !;

On a purely syntactic matter, the Rust construct is more sensible. Note that the C++ construct specifies a return type even though it also specifies it is not going to return. That’s a bit weird.

On a standard note, the C++ syntax only appeared with C++11 (it was tacked on top), but various compilers had been providing various extensions for a while, so that third party analysis tools had to be programmed to recognize the various ways this attribute could be written. Having it standardized is obviously clearly superior.


Now, as for the benefit?

The fact that a function does not return can be useful for:

  • optimization: one can prune any code after it (it won’t return), there is no need to save the registers (as it won’t be necessary to restore them), …
  • static analysis: it eliminates a number of potential execution paths
  • maintainability: (see static analysis, but by humans)

10

Karl’s answer is good. Here is an additional use that I don’t think anyone else has mentioned. The type of

if E then A else B

should be a type that includes all the values in the type of A and all the values in the type of B. If the type of B is Nothing, then the type of the if expression can be the type of A. I’ll often declare a routine

def unreachable( s:String ) : Nothing = throw new AssertionError("Unreachable "+s) 

to say that code is not expected to be reached. Since its type is Nothing, unreachable(s) can now be used in any if or (more often) switch without affecting the type of result. For example

 val colour : Colour := switch state of
         BLACK_TO_MOVE: BLACK
         WHITE_TO_MOVE: WHITE
         default: unreachable("Bad state")

Scala has such a Nothing type.

Another use case for Nothing (as mentioned in Karl’s answer) is List[Nothing] is the type of lists each of whose members has type Nothing. Thus it can be the type of the empty list.

The key property of Nothing that makes these use cases work is not that it has no values –although in Scala, for example, it does have no values– it is that it is a subtype of every other type.

Suppose you have a language where every type contains the same value — let’s call it (). In such a language the unit type, which has () as its only value, could be a subtype of every type. That doesn’t make it a bottom type in the sense that the OP meant; the OP was clear that a bottom type contains no values. However, as it is a type that is a subtype of every type, it can play much the same role as a bottom type.

Haskell does things a bit differently. In Haskell, an expression that never produces a value can have the type scheme forall a.a. An instance of this type scheme will unify with any other type, so it effectively acts as a bottom type, even though (standard) Haskell has no notion of subtyping. For example, the error function from the standard prelude has type scheme forall a. [Char] -> a. So you can write

if E then A else error ""

and the type of the expression will be the same as the type of A, for any expression A.

The empty list in Haskell has the type scheme forall a. [a]. If A is an expression whose type is a list type, then

if E then A else []

is an expression with the same type as A.

2

Types form a monoid in two ways, together making a semiring. That’s what’s called algebraic data types. For finite types, this semiring directly relates to the semiring of natural numbers (including zero), which means you count how many possible values the type has (excluding “nonterminating values”).

  • The bottom type (I’ll call it Vacuous) has zero values.
  • The unit type has one value. I’ll call both the type and its single value ().
  • Composition (which most programming languages support quite directly, through records / structs / classes with public fields) is a product operation. For instance, (Bool, Bool) has four possible values, namely (False,False), (False,True), (True,False) and (True,True).
    The unit type is the identity element of the composition operation. E.g. ((), False) and ((), True) are the only values of type ((), Bool), so this type is isomorphic to Bool itself.
  • Alternative types are somewhat neglected in most languages (OO languages kind-of support them with inheritance), but they are no less useful. An alternative between two types A and B basically has all the values of A, plus all the values of B, hence sum type. For instance, Either () Bool has three values, I’ll call them Left (), Right False and Right True.
    The bottom type is the identity element of the sum: Either Vacuous A has only values of the form Right a, because Left ... doesn’t make sense (Vacuous has no values).

What’s interesting about these monoids is that, when you introduce functions to your language, the category of these types with the functions as morphisms is a monoidal category. Amongst other things, this allows you to define applicative functors and monads, which turn out to be an excellent abstraction for general computations (possibly involving side-effects etc.) within otherwise purely functional terms.

Now, actually you can get quite far with worrying only one side of the issue (the composition monoid), then you don’t really need the bottom type explicitly. For instance, even Haskell did for a long time not have a standard bottom type. Now it has, it’s called Void.

But when you consider the full picture, as a bicartesian closed category, then the type system is actually equivalent to the whole lambda calculus, so basically you have the perfect abstraction over everything possible in a Turing-complete language. Great for embedded domain-specific languages, for instance there’s a project about directly coding electronic circuits this way.

Of course, you may well say that this is all theoretists’ general nonsense. You don’t need to know about category theory at all to be a good programmer, but when you do, it gives you powerful and ridiculously general ways to reason about code, and proove invariants.


mb21 reminds me to note that this should not be confused with bottom values. In lazy languages like Haskell, every type contains a bottom “value”, denoted . This isn’t a concrete thing that you could ever explicitly pass around, instead it’s what’s “returned” for example when a function loops forever. Even Haskell’s Void type “contains” the bottom value, thus the name. In that light, Haskell’s bottom type really has one value and its unit type has two values, but in category-theory discussion this is generally ignored.

1

Maybe it loops forever, or maybe it throws an exception.

Sounds like a useful type to have in those situations, rare though they may be.

Also, even though Nothing (Scala’s name for the bottom type) can have no values, List[Nothing] does not have that restriction, which makes it useful as the type of an empty list. Most languages get around this by making an empty list of strings a different type than an empty list of integers, which kind of makes sense, but makes an empty list more verbose to write, which is a big drawback in a list-oriented language.

6

It is useful for static analysis to document the fact that a particular code path is not reachable. For example if you write the following in C#:

int F(int arg) {
 if (arg != 0)
  return arg + 1; //some computation
 else
  Assert(false); //this throws but the compiler does not know that
}
void Assert(bool cond) { if (!cond) throw ...; }

The compiler will complain that F does not return anything in at least one code path. If Assert were to be marked as non-returning the compiler would not need to warn.

In some languages, null has the bottom type, since the subtype of all types nicely defines what languages use null for (despite the mild contradiction of having null be both itself and a function that returns itself, avoiding the common arguments about why bot should be uninhabited).

It can also be used as a catch-all in function types (any -> bot) to handle dispatch gone awry.

And some languages allow you to actually resolve bot as an error, which can be used to provide custom compiler errors.

9

Yes this is a quite useful type; while its role would be mostly interior to the type system, there are some occasion where the bottom type would appear in openly.

Consider a statically typed language in which conditionals are expressions (so the if-then-else construction doubles as the ternary operator of C and friends, and there might be a similar multi-way case statement). Functional programming language have this, but it happens in certain imperative languages as well (ever since ALGOL 60). Then all branch expressions must ultimately produce the type of the whole conditional expression. One could simply require their types to be equal (and I think this is the case for the ternary operator in C) but this is overly restrictive especially when the conditional can also be used as conditional statement (not returning any useful value). In general one wants each branch expression to be (implicitly) convertible to a common type that will be the type of the full expression (possibly with more or less complicated restrictions to allow that common type to be effectively found by the complier, cf. C++, but I won’t go into those details here).

There are two kinds of situations where a general kind of conversion will allow necessary flexibility of such conditional expressions. One is already mentioned, where the result type is the unit type void; this is naturally a super-type of all other types, and allowing any type to be (trivially) converted to it makes it possible to use the conditional expression as conditional statement. The other involves cases where the expression does return a useful value, but one or more branches are incapable of producing one. They will usually raise an exception or involve a jump, and requiring them to (also) produce a value of the type of the whole expression (from an unreachable point) would be pointless. It is this kind of situation that can be gracefully handled by giving exception-raising clauses, jumps, and calls that will have such an effect, the bottom type, the one type that can be (trivially) converted into any other type.

I would suggest writing such a bottom type as * to suggest its convertibility to arbitrary type. It may serve other useful purposes internally, for instance when trying to deduce a result type for a recursive function that does not declare any, the type inferencer could assign the type * to any recursive call to avoid a chicken-and-egg situation; the actual type will be determined by non-recursive branches, and the recursive ones will be converted to the common type of the non-recursive ones. If there are no non-recursive branches at all, the type will remain *, and correctly indicate that the function has no possible way of ever returning from the recursion. Other than this and as result type of exception throwing functions, one can use * as component type of sequences of length 0, for instance of the empty list; again if ever an element is selected from an expression of type [*] (necessarily empty list), then the resulting type * will correctly indicated that this can never return without an error.

7

In some languages, you can annotate a function to tell both the compiler and developers that a call to this function isn’t going to return (and if the function is written in a way that it can return, the compiler won’t allow it). That’s a useful thing to know, but in the end you can call a function like this like any other. The compiler can use the information for optimisation, to give warnings about dead code, and so on. So there is no very compelling reason to have this type, but no very compelling reason to avoid it either.

In many languages, a function can return “void”. What that exactly means depends on the language. In C it means the function returns nothing. In Swift, it means the function returns an object with only one possible value, and since there is only one possible value that value takes zero bits and doesn’t actually require any code. In either case, that’s not the same as “bottom”.

“bottom” would be a type with no possible values. It can never exist. If a function returns “bottom”, it cannot actually return, because there is no value of type “bottom” that it could return.

If a language designer feels like it, then there is no reason to not have that type. The implementation is not difficult (you can implement it exactly like a function returning void and marked as “doesn’t return”). You can’t mix pointers to functions returning bottom with pointers to functions returning void, because they are not the same type).

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật