Saturday, April 28, 2012

Scheme without special forms: a metacircular adventure

A good programming language will have many libraries building on a small set of core features. Writing and distributing libraries is much easier than dealing with changes to a language implementation. Of course, the choice of core features affects the scope of things we can build as libraries. We want a very small core that still allows us to build anything.

The lambda calculus can implement any computable function, and encode arbitrary data types. Technically, it's all we need to instruct a computer. But programs also need to be written and understood by humans. We fleshy meatbags will soon get lost in a sea of unadorned lambdas. Our languages need to have more structure.

As an example, the Scheme programming language is explicitly based on the lambda calculus. But it adds syntactic special forms for definitions, variable binding, conditionals, etc. Scheme also lets the programmer define new syntactic forms as macros translating to existing syntax. Indeed, lambda and the macro system are enough to implement some of the standard special forms.

But we can do better. There's a simple abstraction which lets us define lambda, Lisp or Scheme macros, and all the other special forms as mere library code. This idea was known as "fexprs" in old Lisps, and more recently as "operatives" in John Shutt's programming language Kernel. Shutt's PhD thesis [PDF] has been a vital resource for learning about this stuff; I'm slowly making my way through its 416 pages.

What I understand so far can be summarized by something self-contained and kind of cool. Here's the agenda:

  • I'll describe a tiny programming language named Qoppa. Its S-expression syntax and basic data types are borrowed from Scheme. Qoppa has no special forms, and a small set of built-in operatives.

  • We'll write a Qoppa interpreter in Scheme.

  • We'll write a library for Qoppa which implements enough Scheme features to run the Qoppa interpreter.

  • We'll use this nested interpreter to very slowly compute the factorial of 5.

All of the code is on GitHub, if you'd like to see it in one place.

Operatives in Qoppa

An operative is a first-class value: it can be passed to and from functions, stored in data structures, and so forth. To use an operative, you apply it to some arguments, much like a function. The difference is that

  1. The operative receives its arguments as unevaluated syntax trees, and

  2. The operative also gets an argument representing the variable-binding environment at the call site.

Just as Scheme's functions are constructed by the lambda syntax, Qoppa's operatives are constructed by vau. Here's a simple example:

(define quote
    (vau (x) env

We bind a single argument as x, and bind the caller's environment as env. (Since we don't use env, we could replace it with _, which means to ignore the argument in that position, like Haskell's _ or Kernel's #ignore.) The body of the vau says to return the argument x, unevaluated.

So this implements Scheme's quote special form. If we evaluate the expression (quote x) we'll get the symbol x. As it happens, quote is used sparingly in Qoppa. There is usually a cleaner alternative, as we'll see.

Here's another operative:

(define list (vau xs env
    (if (null? xs)
        (quote ())
            (eval env (car xs))
            (eval env (cons list (cdr xs)))))))

This list operative does the same thing as Scheme's list function: it evaluates any number of arguments and returns them in a list. So (list (+ 2 2) 3) evaluates to the list (4 3).

In Scheme, list is just (lambda xs xs). In Qoppa it's more involved, because we must explicitly evaluate each argument. This is the hallmark of (meta)programming with operatives: we selectively evaluate using eval, rather than selectively suppressing evaluation using quote.

The last part of this code deserves closer scrutiny:

(eval env (cons list (cdr xs)))

What if the caller's environment env contains a local binding for the name list? Not to worry, because we aren't quoting the name list. We're building a cons pair whose car is the value of list... an operative! Supposing xs is (1 2 3), the expression

(cons list (cdr xs))

evaluates to the list

(<some-value-representing-an-operative> 2 3)

and that's what eval sees. Just like lambda, evaluating a vau expression captures the current environment. When the resulting operative is used, the vau body gets values from this captured static environment, not the dynamic argument of the caller. So we have lexical scoping by default, with the option of dynamic scoping thanks to that env parameter.

Compare this situation with Lisp or Scheme macros. Lisp macros build code which refers to external stuff by name. Maintaining macro hygiene requires constant attention by the programmer. Scheme's macros are hygienic by default, but the macro system is far more complex. Rather than writing ordinary functions, we have to use one of several special-purpose sublanguages. Operatives provide the safety of Scheme macros, but (like Lisp macros) they use only the core computational features of the language.

Implementing Qoppa

Now that you have a taste of what the language is like, let's write a Qoppa interpreter in Scheme.

We will represent an environment as a list of frames, where a frame is simply an association list. Within the vau body in

( (vau (x) _ x) 3 )

the current environment would be something like

( ;; local frame
  ((x 3))

  ;; global frame
  ((cons <operative>)
   (car  <operative>)
   ...) )

Here's a Scheme function to build a frame from some names and the corresponding values.

(define (bind param val) (cond
    ((and (null? param) (null? val))
    ((eq? param '_)
    ((symbol? param)
        (list (list param val)))
    ((and (pair? param) (pair? val))
            (bind (car param) (car val))
            (bind (cdr param) (cdr val))))
        (error "can't bind" param val))))

We allow names and values to be arbitrary trees, so for example

    '((a b) . c)
    '((1 2) 3 4))

evaluates to

((a 1)
 (b 2)
 (c (3 4)))

(If you'll recall, (x . y) is the pair formed by (cons 'x 'y), an improper list.) The generality of bind means our argument-binding syntax — in vau, lambda, let, etc. — will be richer than Scheme's.

Next, a function to find a (name value) entry, given the name and an environment. This just invokes assq on each frame until we find a match.

(define (m-lookup name env)
    (if (null? env)
        (error "could not find" name)
        (let ((binding (assq name (car env))))
            (if binding
                (m-lookup name (cdr env))))))

We also need a representation for operatives. A simple choice is that a Qoppa operative is represented by a Scheme procedure that takes the operands and current environment as arguments. Now we can write the Qoppa evaluator itself.

(define (m-eval env exp) (cond
    ((symbol? exp)
        (cadr (m-lookup exp env)))
    ((pair? exp)
        (m-operate env (m-eval env (car exp)) (cdr exp)))

(define (m-operate env operative operands)
    (operative env operands))

The evaluator has only three cases. If exp is a symbol, it refers to a value in the current environment. If it's a cons pair, the car must evaluate to an operative and the cdr holds operands. Anything else evaluates to itself: numbers, strings, Booleans, and Qoppa operatives (represented by Scheme procedures).

Instead of the traditional eval and apply we have "eval" and "operate". Thanks to our uniform representation of operatives, the latter is very simple.

Qoppa builtins

Now we need to populate the global environment with useful built-in operatives. vau is the most significant of these. Here is its corresponding Scheme procedure.

(define (m-vau static-env vau-operands)
    (let ((params    (car   vau-operands))
          (env-param (cadr  vau-operands))
          (body      (caddr vau-operands)))

        (lambda (dynamic-env operands)
                        (cons env-param   params)
                        (cons dynamic-env operands))

When applying vau, you provide a parameter tree, a name for the caller's environment, and a body. The result of applying vau is an operative which, when applied, evaluates that body. It does so in the environment captured by vau, extended with arguments.

Here's the global environment:

(define (make-global-frame)
    (define (wrap-primitive fun)
        (lambda (env operands)
            (apply fun (map (lambda (exp) (m-eval env exp)) operands))))
        (list 'vau m-vau)
        (list 'eval    (wrap-primitive m-eval))
        (list 'operate (wrap-primitive m-operate))
        (list 'lookup  (wrap-primitive m-lookup))
        (list 'bool    (wrap-primitive (lambda (b t f) (if b t f))))
        (list 'eq?     (wrap-primitive eq?))
        ; more like these

(define global-env (list (make-global-frame)))

Other than vau, each built-in operative evaluates all of its arguments. That's what wrap-primitive accomplishes. We can think of these as functions, whereas vau is something more exotic.

We expose the interpreter's m-eval and m-operate, which are essential for building new features as library code. We could implement lookup as library code; providing it here just prevents some code duplication.

The other functions inherited from Scheme are:

  • Type predicates: null? symbol? pair?

  • Pairs: cons car cdr set-car! set-cdr!

  • Arithmetic: + * - / <= =

  • I/O: error display open-input-file read eof-object

Scheme as a Qoppa library

The Qoppa interpreter uses Scheme syntax like lambda, define, let, if, etc. Qoppa itself supports none of this; all we get is vau and some basic data types. But this is enough to build a Qoppa library which provides all the Scheme features we used in the interpreter. This code starts out very cryptic, and becomes easier to read as we have more high-level features available. You can read through the full library if you like. This section will go over some of the more interesting parts.

Our first task is a bit of a puzzle: how do you define define? It's only possible because we expose the interpreter's representation of environments. We can push a new binding onto the top frame of env, like so:

(set-car! env
        (cons <name> (cons <value> null))
        (car env)))

We use this idea twice, once inside the vau body for define, and once to define define itself.

((vau (name-of-define null) env
    (set-car! env (cons
        (cons name-of-define
            (cons (vau (name exp) defn-env
                    (set-car! defn-env (cons
                        (cons name (cons (eval defn-env exp) null))
                        (car defn-env))))
        (car env))))
    define ())

Next we'll define Scheme's if, which evaluates one branch or the other. We do this in terms of the Qoppa builtin bool, which always evaluates both branches.

(define if (vau (b t f) env
    (eval env
        (bool (eval env b) t f))))

We already saw the code for list, which evaluates each of its arguments. Many other operatives have this behavior, so we should abstract out the idea of "evaluate all arguments". The operative wrap takes an operative and returns a transformed version of that operative, which evaluates all of its arguments.

(define wrap (vau (operative) oper-env
    (vau args args-env
        (operate args-env
            (eval    oper-env operative)
            (operate args-env list args)))))

Now we can implement lambda as an operative that builds a vau term, evals it, and then wraps the resulting operative.

(define lambda (vau (params body) static-env
        (eval static-env
            (list vau params '_ body)))))

This works just like Scheme's lambda:

(define fact (lambda (n)
    (if (<= n 1)
        (* n (fact (- n 1))))))

Actually, it's incomplete, because Scheme's lambda allows an arbitrary number of expressions in the body. In other words Scheme's

(lambda (x) a b c)

is syntactic sugar for

(lambda (x) (begin a b c))

begin evaluates its arguments in order left to right, and returns the value of the last one. In Scheme it's a special form, because normal argument evaluation happens in an undefined order. By contrast, the Qoppa interpreter implements a left-to-right order, so we'll define begin as a function.

(define last (lambda (xs)
    (if (null? (cdr xs))
        (car xs)
        (last (cdr xs)))))

(define begin (lambda xs (last xs)))

Now we can mutate the binding for lambda to support multiple expressions.

(define set! (vau (name exp) env
        (lookup name env)
        (list (eval env exp)))))

(set! lambda
    ((lambda (base-lambda)
        (vau (param . body) env
            (eval env (list base-lambda param (cons begin body)))))

Note the structure

((lambda (base-lambda) ...) lambda)

which holds on to the original lambda operative, in a private frame. That's right, we're using lambda to save lambda so we can overwrite lambda. We use the same approach when defining other sugar, such as the implicit lambda in define.

There are some more bits of Scheme we need to implement: cond, let, map, append, and so forth. These are mostly straightforward; read the code if you want the full story. By far the most troublesome was Scheme's apply function, which takes a function and a list of arguments, and is supposed to apply the function to those arguments. The problem is that our functions are really operatives, and expect to call eval on each of their arguments. If we already have the values in a list, how do we pass them on?

Qoppa and Kernel have very different solutions to this problem. In Kernel, "applicatives" (things that evaluate all their arguments) are a distinct type from operatives. wrap is the primitive constructor of applicatives, and its inverse unwrap is used to implement apply. This design choice simplifies apply but complicates the core evaluator, which needs to distinguish applicatives from operatives.

For Qoppa I implemented wrap as a library function, which we saw before. But then we don't have unwrap. So apply takes the uglier approach of quoting each argument to prevent double-evaluation.

(define apply (wrap (vau (operative args) env
    (eval env (cons
        (map (lambda (x) (list quote x)) args))))))

In either Kernel or Qoppa, you're not allowed to apply apply to something that doesn't evaluate all of its arguments.


The code we saw above is split into two files:

  • qoppa.scm is the Qoppa interpreter, written in Scheme

  • prelude.qop is the Qoppa code which defines wrap, lambda, etc.

I defined a procedure execute-file which reads a file from disk and runs each expression through m-eval. The last line of qoppa.scm is

(execute-file "prelude.qop")

so the definitions in prelude.qop are available immediately.

We start by loading qoppa.scm into a Scheme interpreter. I'm using Guile here, but I've actually tested this with a variety of R5RS implementations.

$ guile -l qoppa.scm
guile> (m-eval global-env '(fact 5))
$1 = 120

This establishes that we've implemented the features used by fact, such as define and lambda. But did we actually implement enough to run the Qoppa interpreter? To test this, we need to go deeper.

guile> (execute-file "qoppa.scm")
$2 = done
guile> (m-eval global-env '(m-eval global-env '(fact 5)))
$3 = 120

This is factorial implemented in Scheme, implemented as a library for Qoppa, implemented in Scheme, implemented as a library for Qoppa, implemented in Scheme (implemented in C). Of course it's outrageously slow; on my machine this (fact 5) takes about 5 minutes. But it demonstrates that a tiny language of operatives, augmented with an appropriate library, can provide enough syntactic features to run a non-trivial Scheme program. As for how to do this efficiently, well, I haven't got far enough into the literature to have any idea.


  1. This comment has been removed by a blog administrator.

  2. This comment has been removed by the author.

    1. `apply' could still be made beautiful if you define it as an operative.

  3. Look for Black on and related work on that page and elsewhere by Kenichi Asai.

  4. I'll have to read this in detail some other time, but I already like it for using two of the most obscure Greek letters ever. (One of those oddball facts that's clogging up my brain: many metrical anomalies in Homer are due to unwritten vaus.)

  5. Nice article, thanks for the information. It's very complete information. I will bookmark for next reference
    jaring futsal | jaring golf | jaring pengaman proyek |
    jaring pengaman bangunan | jaring pengaman gedung


  6. الفك والتركيب والتغليف والتحرك من خلال شركة نقل اثاث بجدة متميزة تحتاج الي ركن البيت للاستفادة من خدماتها التي لا تقدم غير خدمات نقل اثاث بجدة رائعة لتلبية طلبات العميل. فرؤية الشركة لدينا وضعت طريقة جيدة وضعت كخبراء في نقل الخدمات التي يحتاجها العملاء عند التعاون مع شركة نقل عفش بجدة فاختيار المواد الخاصة بك التي تستخدم في تغليف الاثاث فهذه المواد أو الأشياء ستكون موجودة على عتبة المنزل للتحرك بكل آمن و بقوة إلى المكان الذي ترغب فيه
    شركة نقل الاثاث بالدمام
    استخدام الأصول الأولى داخل الاستراتيجية التي تستخدمها شركة نقل عفش بالدمام لدينا تدفعنا إلى التفكير في إعطاء نوع من الخدمات المتميزة الي العملاء التي تترك نجاحا مباشرا يمكن أن تنقش على نفسية الزبائن التي تقدم لهم خدمة نقل العفش.فلدينا ميل إلى أن يشار جيدا إلى العاملون والفنين والمغلفون ان يقوموا بعملهم في شركة نقل اثاث بالدمام نتيجة للمعيار والتنمية والعقل
    شركة نقل الاثاث بمكة
    مجموعة معينة لدينا في شركة نقل عفش بمكة تساعدنا في خدمتك في أي وقت من اليوم، وأنهم على استعداد للبقاء على طول هذه الخطوط لاعطاءك خدمة متميزة عن باقي شركات نقل اثاث بمكة التي توجد بهذه المنطقة فلا أحد لدينا في خدمات النقل يترك شيئا حتي لا نقع في قليلا من السخط.
    شركة تخزين الاثاث بالرياض
    التعبئة والتغليف يبدأ بالعمل المتميز من اجل الحصول علي شركة تخزين عفش بالرياض متميزة تمتلك مستودعات تخزين اثاث بالرياض تحافظ علي الممتلكات الخاصة بكم من الخراب. كما ان التعبئة والتغليف على استعداد فريد للتعامل مع كل نوع من الأدوات والأشياء الزجاجية والمواد التي تميل إلى الإضرار أو تحمل بانتظام أسوأ جزء في عملية الحركة. فخدمات شركة تخزين اثاث بالرياض تقوم بإرسال أفضل مجموعة يتقن الوصول إليها في الوقت المناسب لتلبية طلب العميل وفقا لمتطلبات محددة خاصة بك في هذا المجال بسبب الطريقة التي كانت لدينا مجموعة من الخبراء في كيفية ادارة مستودعات تخزين اثاث منذ فترة طويلة في هذه الصناعة لفترة طويلة جدا.
    شركة تخزين اثاث
    شركة تخزين عفش


  7. Thank you for sharing in this article
    I can learn a lot and could also be a reference
    I am happy to find your website and can join to comment

    Share and Klick Info Blokwalking. Hammer Of Thor
    => Jual Hammer Of Thor Di Bogor
    => Jual Hammer Of Thor Di Medan
    => Jual Hammer Of Thor Di Semarang
    => Jual Hammer Of Thor Di Bandung
    => Jual Hammer Of Thor Di Bandar Lampung
    Obat Good Man | Obat pembesar penis | Vimax Asli | Vimax Extender

  8. شكرا معلومات مفيدة يمكنك زيارة ايضا لمعلومات قيمة اخرى قم بزيارة :

    شركة غسيل خزانات بالمدينة المنورة
    افضل شركة تنظيف

  9. Pretty! This was an extremely wonderful article. Thank you for supplying this info.

  10. -------------------------
    يتم الغسل الجيد للخزان بايدي عمال شركتنا الماهرين جدا و المدربين للقيام بهذه الخدمة و الذين بدورهم يقوموا باستخدام القفازات المستخدمة للتنظيف و ايضا نضارات حتي نضمن وقاية جميع العمال و انهم لن يصابوا باي ضرر اثناء القيام بخدمة تنظيف الخزانات في مدينة المدينة المنورة المدينة المنورة
    شركة تنظيف الخزانات في المدينة المنورة
    شركة غسيل خزانات بالمدينة المنورة
    افضل خدمة لتنظيف الخزانات في مدينة المدينة المنورة

    من خدماتنا:
    أفضل الشركات لتركيب المكيفات في المدينة المنورة
    تركيب مكيفات سبلت
    شركة تنظيف مكيفات بالمدينة المنورة
    شركة تنظيف كنب

  11. I seriously love your site.. Excellent colors & theme. Did you develop this web site yourself? Please reply back as I’m looking to create my own blog and want to know where you got this from or just what the theme is called. Thank you!
    Net Worth Culture
    Joe Rogan Net Worth
    Mark Zuckerberg Net Worth
    Tom Cruise Net Worth

  12. Good post thanks for share information.
    utsa blackboard

  13. BSER will announce Rajasthan Board 8th Result 2021 in the first week of June2021. More than 11 lakh students will be waiting for the 8th board. RBSE 8th Result 2021 Onlystudents who appear for March 2021 will be able to access 2021 Class 8 result.

  14. I really happy found this website eventually. Really informative, Thanks for sharing here.
    If you are an AOL user, and you want to Change AOL Password in MacBook Pro. Follow the steps mentioned to change the AOL password. If you are unable to change the AOL password, you can contact our AOL email support team via the toll-free number. They will help you with alternative solutions to fix these issues. For more information about AOL email support, you can also visit our website.

  15. Hello
    Please i just took up macaw breeding as a hobby after my mom passed away because they were her favorite birds. Despite the fact that they are very intelligent, am finding it very difficult getting them to mate.
    For any information CLICK HERE AFRICAN GRAY PARROT FOR SALE we shall get back to you. contact-about-us.THANKS

  16. Hello
    Please i just took up LABRADOR PUPPIES breeding as a hobby after my mom passed away because they were her favorite PUPPIES. Despite the fact that they are very intelligent, am finding it very difficult getting them to mate.

  17. When you begin installing the Quickbooks tool hub then you must consent to all of the windows that appear one after another in order to complete the installation.

  18. The dachshund was bred in Germany hundreds of years ago to hunt badgers. "Dach" means badger and "hund" means dog. The three varieties of dachshund, smooth-,Dachshund puppies for sale wire-,and long-coated, originated at different times. The smooth was the first and arose from a mixture of a miniature French pointer and a pinscher. The breed also comes in two sizes: standard and miniature, with the standard the original size.
    The dachshund has short, strong legs that enable the dog to dig out prey and go inside burrows. Larger versions of the breed were used to chase deer or fox. Smaller dachshunds Dachshund puppy for salewere bred for hunting hares and ferrets.
    The breed is still used for hunting, primarily in Europe, but in North America this dog is usually a family pet. In fact, it is one of the most popular AKC breeds.
    Miniatures are not a separate AKC classification but compete in a class division for "11 pounds and under at 12 months of age and older." Weight of the standard size is usually between 16 and 32 pounds. There is no height standard for the dachshund but they are usually under nine indachshunds puppies for saleches in height.All three types are known for their long backs and short muscular legs, which explains the unflattering nicknames "sausage hound" or "hot dog." They also have a long muzzle, long and droopy ears, and a tail carried in line with the back.
    The dachshund's coat may be shades of red, black, chocolate, white or gray. Some have tan markings or are spotted or dappled. Dachshunds live about 12 to 15 years.toy poodle for saleespite their size, dachshunds are known for their courageous nature and will take on animals much larger than themselves. Some may be aggressive toward strangers and other dogs.
    As family dogs, dachshunds are loyal companions and good watchdogs. They are good with children if treated well. They can be slightly difficult to train.
    Some dachshund fanciers say there are personality differences among the different varieties of the breed. For instance, the long-coat dachshund is reportedly calmerteacup poodles for sale than the smooth-coat variety, and the wire-coat dachshund is more outgoing and clown-like.Dachshunds were bred as hunters so it is no surprise that many of them like to dig. Some are also barkers, and, in one survey, dachshunds ranked high for destructiveness.

  19. Dachshunds are bred and shown in two sizes: Standard and Miniature. Standard Dachshunds of all varieties (Smooth, Wirehair, and Longhair) usually weigh between 16 and 32 pounds. Miniature Dachshunds of all varieties weigh 11 pounds and under atteacup poodle for sale maturity. Dachshunds that weigh between 11 and 16 pounds are called Tweenies. While this isn't an official classification, Tweenies are not penalized in the show ring. Some people who breed exceptionally small Dachshunds advertise them as Toy Dachshunds, but this is purely a poodles for salemarketing term, not a recognized designation.The Dachshund is described as clever, lively, and courageous to the point of rashness. He's bred for perseverance, which is another way of saying that he can be stubborn. Dachshunds have a reputation for being dachshund puppies saleentertaining and fearless, but what they want most is to cuddle with their people. For many Dachshund people, this characteristic outweighs having to deal with the breed's insistence on having his own way. The Dachshund personality can also vary with coat type. Because the wirehaired Dachshunds have terrier in their background, they can be mischievous troublemakers. Longhairs are calm and quiet, and Smooths havedachshund for sale a personality that lies somewhere in between. Some Mini Dachshunds can be nervous or shy, but this isn't correct for the breed. Avoid puppies that show these characteristics.Like every dog, Dachshunds need early socialization-exposure to many different people, dachshund puppies for sale near mesights, sounds, and experiences-when they're young. Socialization helps ensure that your Dachshund puppy grows up to be a well-rounded dog. Enrolling him in a puppy kindergarten class is a great start. Inviting visitors over regularly, and taking him to busy parks, stores that allow dogs, and on leisurely strolls to meet neighbors will also help him polish his social skills.

  20. The breed became very popular in the early 1900s, and in 1913 and 1914, https://oneshoppharmacy.comthey were among the 10 most popular entries in the Westminster Kennel Club Show. During World War I, however, the breed fell on hard times in the U.S. and England because they were poodle for saleclosely associated with Germany. Dachshund owners sometimes were called traitors and their dogs stoned. After
    World War I, some U.S. breedersdachshunds for sale imported some Dachshunds from Germany and the breed started to become popular once again. The breed faced a similar fate during World War II, but not nearly so severely as during World War I.
    In the 1950s, Dachshunds became one of the most popular family dogs in the U.S. again, a status they have enjoyed ever
    since. While Dachshunds mini dachshund puppy for salerarely are used as hunting dogs in the U.S. or Great Britain, in other parts of Europe, especially France, they still are considered hunting dogs. Today the Dachshund ranks sixth among the 155 breeds and varieties recognized by the AKC.Dachshund dogs love apartment dachshund puppies for sale Because of their small size, they don’t need a backyard, but they
    do enjoy going on walks outside. Dachshunds also love a challenge, and as long as you incorporate plenty of opportunities to chase and find things, you’llminiature dachshund for sale have a happy dog.These dogs love their human parents, and really don’t want them to leave. Your dog may struggle more with separation anxiety, and when they’re missing you, they’re likely to chew. When you do leave your home without your dog, you may want to use a kennel.

  21. Most valuable and fantastic blog I really appreciate your work which you have done about the electricians,many thanks and keep it up. Very useful info. Hope to see more posts soon! I really like to read this post, it shares lots of information to readers.a

    Graphic Design company in USA

    Label Design

    Box Packaging Design

    Packaging Host

    Die Cut Stickers

    Static Cling

    Lahore Smart City Payment Plan

    Hosting Mart

    Lahore Smart City

  22. ความเเตกต่างของบ้านที่มีหลาดหลายสไตล์ เเตกล่ะเเบบ เเต่ล่ะอย่างนั้นก็มีรูปแบบที่เเตกต่าง สาวยงามแบ่งเเยกอกกันไป โดยการเเต่งบ้านนั้นมีหลากหลายสไตล์ทำให้ยากต่อการตัดสินใจ วันนี้ทาง gladdanahu เข้าเว็บไซต์ เราได้รวบรวมบ้านสไตล์ต่างๆ เเละเทคนิคในการตกเเต่งบ้านที่จะเป็นตัวช่วยในการตัดสินใจสำหรับใครที่ยังลังเลอยู่ รับรองว่าถูกใจอย่างเเน่นอน.

  23. I think this is an informative post and it is very useful and knowledgeable. therefore, I would like to thank you for the efforts you have made in writing this article.
    สมัคร 123bet

  24. Nice post. We doassignmenthelp is a group of skilled professional writers in the United State who provide college assignment helper services, as well as Assignment Help Canada, UK, and java homework Help. Our assignment writers strive to deliver 100% plagiarism-free assistance. Our commitment to the key values has helped us grow from the most promising college assignment aid to the US's most popular assignment help. Contact us immediately for a reasonable pricing .

  25. هل تريد تحميل  لعبة جاتا 7 يمكنك تحميل اللعبة بكل سهولة من خلال موقعنا تحميل العاب الكمبيوتر موقعنا متخصص في كثير من الألعاب يمكنك تحميل لعبة جاتا جميع الإصدارات من خلال موقعنا وغيرها من الألعاب الكمبيوتر .

  26. จุดเด่นของ เกมสล็อตทดลองเล่นอย่าง ลึกซึ้ง ทำให้เกมของเรา มีคุณภาพทั้งเรื่อง กราฟิกสีสันของเกม ซึ่งทาง BETFLIX เสนอและสอนวิธีเล่น

  27. Whenever you need top Premium-Class Products Skin Care for men - Мужской крем для ног
    you welcome visit икеа израиль Thanks a lot 1001совет