banner

[Rule] Rules  [Home] Main Forum  [Portal] Portal  
[Members] Member Listing  [Statistics] Statistics  [Search] Search  [Reading Room] Reading Room 
[Register] Register  
[Login] Loginhttp  | https  ]
 
Forum Index Thảo luận hệ điều hành Windows Hướng dẫn Metaprogramming ( viết chương trình tạo ra chương trình khác )  XML
  [Programming]   Hướng dẫn Metaprogramming ( viết chương trình tạo ra chương trình khác ) 02/01/2008 14:27:51 (+0700) | #1 | 107689
[Avatar]
onlinehack
Member

[Minus]    0    [Plus]
Joined: 04/12/2007 23:07:12
Messages: 116
Location: Ma maison
Offline
[Profile] [PM]
Làm thế nào để viết một chương trình có thể tự viết một chương trình khác ?

Metaprogram là chương trình có thể tự tạo ra chương trình hoặc một phần của chương trình khác. Do đó Metaprogramming nghĩa là " viết metaprogram". Có nhiều metaprogram hữu dụng trong Linux: phổ biến nhất là các trình dịch ( GCC hay FORTRAN 77), thông dịch ( Perl hay Ruby) parse generator ( Bison ), assembler ( AS hay NASM ) và preprocessor ( CPP hay M4) . Thông thường, bạn sử dụng metaprogram để loại trừ hay giảm thiểu các việc buồn tẻ hoặc lỗi lập trình thường gặp. Lấy ví dụ, thay vì viết một chương trình mã máy bằng tay, bạn sử dụng các ngôn ngữ lập trình bậc cao như C, và sau đó làm cho trình dịch C dịch thành các chỉ dẫn máy cấp thấp tương đương.

Metaprogramming ban đầu dường như là một chủ đề cao cấp, chỉ phù hợp chỉ dành cho những chuyên gia , nhưng nó không thực sự khó khi bạn biết cách sử dụng các công cụ thích hợp.

Tạo mã nguồn

Để trình bày một ví dụ rất đơn giản về metaprogramming, chúng ta thừa nhận trường hợp hư cấu hoàn toàn sau:

Erika là một cô sinh vien năm nhất ngành khoa học máy tính cực thông minh. Cô biết vài ngôn ngữ lập trình, gồm cả C và Ruby. Trong lớp lập trình cơ bản của cô, Giáo sư Gomez, người hướng dẫn khóa học, nhận thấy cô đang chat trong máy laptop. Để trừng phạt, ông yêu cầu Erika viết một chương trình in ra 1000 dòng chữ sau:


1. Toi khong duoc phep chat trong lop.
2. Toi khong duoc phep chat trong lop.
...
999. Toi khong duoc phep chat trong lop.
1000. Toi khong duoc phep chat trong lop

Một giới hạn nữa là chương trình không được sử dụng các vòng lặp hay chỉ dẫn goto. Nó có thể chỉ là một hàm main lớn voi 1000 chi dan printf, ví dụ như sau :

Code:
#include <stdio.h>
int main(void) {
    printf("1. Toi khong duoc phep chat trong lop.\n");
    printf("2. Toi khong duoc phep chat trong lop.\n");

    /* 996 chi dan printf. */

    printf("999. Toi khong duoc phep chat trong lop.\n");
    printf("1000. Toi khong duoc phep chat trong lop.\n");
    return 0;
}


Giáo sư Gomez không quá ngây thơ, do đó ông ta tin rằng Erika sẽ viết chỉ dẫn printf chỉ dẫn một lần, copy nó vào clipboard, thực hiện 999 lần paste, và tự tay thay đổi số. Ông nghĩ rằng công việc buồn tẻ và lặp lại này sẽ đủ để dạy cô một bài học. Nhưng Erika ngay lập tức nhận ra cách dễ nhất - metaprogramming. Thay vì viết chương trình này bằng tay, sao không viết một chương trình khác viết chương trình đó tự động cho cô? Và cô đã viết đoạn script Ruby sau :

Code:
File.open('trungphat.c', 'w') do |output|
  output.puts '#include <stdio.h>'
  output.puts 'int main(void) {'
  1.upto(1000) do |i|
    output.puts "    printf(\"#{i}. " +
      "Toi khong duoc phep chat trong lop.\\n\");"
  end
  output.puts '    return 0;'
  output.puts '}'
end


Đoạn mã này tạo ra một file trungphat.c với 1000 dòng lệnh C

Dựa theo Linuxjournal

Còn tiếp...
[Up] [Print Copy]
  [Question]   Re: Hướng dẫn Metaprogramming ( viết chương trình tạo ra chương trình khác ) 02/01/2008 14:42:07 (+0700) | #2 | 107690
nccnm
Member

[Minus]    0    [Plus]
Joined: 23/09/2004 08:34:51
Messages: 6
Offline
[Profile] [PM]
Rất thú vị, cơ mà đoạn script trên vẫn phải dùng vòng lặp, liệu có một cách khác không. Erika có thể đánh lừa ông thầy bằng cách đưa cho ông ý chương trình C mà đoạn script kia sinh ra nhưng như thế thì bình thường quá. Trong khi đó bạn lại quảng cáo Erika rất thông minh smilie . Mong bài viết tiếp theo smilie
[Up] [Print Copy]
  [Question]   Re: Hướng dẫn Metaprogramming ( viết chương trình tạo ra chương trình khác ) 02/01/2008 19:12:41 (+0700) | #3 | 107703
StarGhost
Elite Member

[Minus]    0    [Plus]
Joined: 29/03/2005 20:34:22
Messages: 662
Location: The Queen
Offline
[Profile] [PM]

nccnm wrote:
Rất thú vị, cơ mà đoạn script trên vẫn phải dùng vòng lặp, liệu có một cách khác không. Erika có thể đánh lừa ông thầy bằng cách đưa cho ông ý chương trình C mà đoạn script kia sinh ra nhưng như thế thì bình thường quá. Trong khi đó bạn lại quảng cáo Erika rất thông minh smilie . Mong bài viết tiếp theo smilie  


Hì, người ta đang đưa ra ví dụ về meta programming, chứ còn dùng recursion thì nói làm gì đâu, Erika thông minh hay không chả liên quan gì cả.

Hi vọng onlinehack tiếp tục chủ để này.
Mind your thought.
[Up] [Print Copy]
  [Question]   Re: Hướng dẫn Metaprogramming ( viết chương trình tạo ra chương trình khác ) 02/01/2008 21:11:45 (+0700) | #4 | 107710
[Avatar]
onlinehack
Member

[Minus]    0    [Plus]
Joined: 04/12/2007 23:07:12
Messages: 116
Location: Ma maison
Offline
[Profile] [PM]
@all : cảm ơn vì đã chú ý. Hy vọng sau phần 2 này, các bạn đã có thể tự viết một metaprogram

Mặc dù ví dụ trên dường như có một chút bịa đặt, nó minh hoạ việc viết một chương trình tạo ra source một chương trình khác dễ dàng thế nào. Kỹ thuật này có thể được sử dụng nhiều hơn trong thực tế.
Giờ ta cùng nghĩ đến việc, bạn có một chương trình C cần include một ảnh PNG, nhưng vì một lí do nào đó, nền sử dụng chỉ chấp nhận 1 file, là file khả chạy. Do đó, dữ liệu phù hợp với file PNG phải được tích hợp trong chính chương trình. Để làm được điều đó, bạn có thể đọc file PNG trước và tạo ra đoạn mã C có tác dụng khai báo mảng, khởi đầu với dữ liệu tương ứng . Đoạn script Ruby sau minh hoạ chính xác điều đó :

Code:
INPUT_FILE_NAME = 'ljlogo.png'
OUTPUT_FILE_NAME = 'ljlogo.h'
DATA_VARIABLE_NAME = 'ljlogo'

File.open(INPUT_FILE_NAME, 'r') do |input|
  File.open(OUTPUT_FILE_NAME, 'w') do |output|
    output.print "unsigned char #{DATA_VARIABLE_NAME}[] = {"
    data = input.read.unpack('C*')
    data.length.times do |i|
      if i % 8 == 0
        output.print "\n    "
      end
      output.print '0x%02X' % data[i]
      output.print ', ' if i < data.length - 1
    end
    output.puts "\n};"
  end
end


Đoạn script đọc file ljlogo.png và tạo ra file mới là ljlogo.h. Đầu tiên, nó viết phần khai báo của biến ljlogo như một mảng unsigned char. Sau đó, nó đọc tất cả file đưa vào một lần và unpack tất cả các kí tự đưa vào thành các unsigned byte. Rồi ghi từng byte đó thành các số hexadecimal 2 kí tự , mỗi dòng 8 số . Như đòi hỏi, mỗi số được ngắt bởi một dấu phẩy, trừ số cuối cùng. Và phần cuối cùng là đoạn script viết dấu ngoặc đóng và dấu chấm phẩy

Sau đây là một mẫu file kết quả

Code:
unsigned char ljlogo[] = {
    0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
    0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,

    /* Khoảng vài trăm dòng ở đây. */

    0x0B, 0x13, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
    0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
};


Chương trình C sau minh hoạ cách bạn sử dụng code được tạo ra như C header file thông thường. Cần chú ý ( quan trọng ) rằng dữ liệu của file PNG sẽ được lưu ở bộ nhớ khi chương trình được load

Code:
#include <stdio.h>
#include "ljlogo.h"

/* In dữ liệu của mảng ljlogo theo các giá trị byte hexadecimal . */
int main(void) {
    int i;
    for (i = 0; i < sizeof(ljlogo); i++) {
        printf("%X ", ljlogo[i]);
    }
    return 0;
}


Bạn cũng có thể có một chương trình vừa tạo ra source code và khởi chạy nó ngay lập tức. Có vài ngôn ngữ có "khả năng" eval, cho phép bạn dịch và khởi chạy một phần source code chứa bên trong một chuỗi kí tự khi đang chạy. "Khả năng" này thường có trong các ngôn ngữ thông dịch, như Lisp, Perl, Ruby, Python và JavaScript. Trong đoạn mã Ruby này :

Code:
x = 3
s = 'x + 1'
puts eval(s)


Chuỗi 'x+1' được thông dịch và thi hành khi đoạn mã được chạy, in ra kết quả là 5.

Đoạn mã Ruby sau minh hoạ một cách tìm kiếm kết quả của việc cộng tất cả các số nguyên giữa 1 và 100. Thay vì sử dụng một phương pháp lặp thông thường, chúng ta tạo ra một chuỗi lớn bao gồm biểu thức “1+2+3+...+99+100” và kế tiếp là tính giá trị của nó:

Code:
puts eval((1..100).to_a.join('+'))


Hàm eval nên được sử dụng thận trọng. Nếu chuỗi được sử dụng làm đối số của eval đến từ một nguồn không tin cậy ( ví dụ như do người dùng nhập vào chẳng hạn ), nó có thể có những nguy hiểm tiềm tàng ( tưởng tuợng việc gì có thể xảy ra nếu chuỗi được đánh giá chứa expression rm -r * smilie ) Trong nhiều trường hợp, có thể thay thế "eval" để linh động, ít yếu kém về bảo mật hơn và không đòi hỏi tốc độ phân tích mã nguồn trong thời gian thi hành.

còn tiếp...
[Up] [Print Copy]
  [Question]   Re: Hướng dẫn Metaprogramming ( viết chương trình tạo ra chương trình khác ) 02/01/2008 21:58:20 (+0700) | #5 | 107711
[Avatar]
onlinehack
Member

[Minus]    0    [Plus]
Joined: 04/12/2007 23:07:12
Messages: 116
Location: Ma maison
Offline
[Profile] [PM]

nccnm wrote:
Rất thú vị, cơ mà đoạn script trên vẫn phải dùng vòng lặp, liệu có một cách khác không. Erika có thể đánh lừa ông thầy bằng cách đưa cho ông ý chương trình C mà đoạn script kia sinh ra nhưng như thế thì bình thường quá. Trong khi đó bạn lại quảng cáo Erika rất thông minh smilie . Mong bài viết tiếp theo smilie  


Chào nccnm, như phần 1 của bài viết, metaprogramming có tác dụng là loại trừ hoặc giảm thiểu các công việc buồn tẻ hay lỗi lập trình thường gặp ( eliminate or reduce a tedious or error-prone programming task ) .Đoạn script trên đã hoàn thành được tác vụ đó . Còn một kiểu logic khác, onlinehack vẫn chưa nghĩ ra smilie
[Up] [Print Copy]
  [Question]   Re: Hướng dẫn Metaprogramming ( viết chương trình tạo ra chương trình khác ) 02/01/2008 23:41:58 (+0700) | #6 | 107742
nccnm
Member

[Minus]    0    [Plus]
Joined: 23/09/2004 08:34:51
Messages: 6
Offline
[Profile] [PM]
Hi online hack, mình hiểu điều đó, nhưng thôi bàn sang chuyện kia sẽ làm loãng chủ đề smilie . Thêm một ví dụ về kiểu lập trình này: giả sử bạn cũng ở trong một môi trường mà chỉ có thể xài các file exe nhưng bạn muốn nghe nhạc từ các file mp3 cơ, bạn hoàn toàn có thể "dịch" các file mp3 này sang file exe rồi chạy file đó để nghe. Cũng tương tự như việc include file PNG trên. Có tham khảo source ở đây nếu như bạn biết .NET http://codeproject.com/KB/cs/mp3toexe.aspx
[Up] [Print Copy]
  [Question]   Re: Hướng dẫn Metaprogramming ( viết chương trình tạo ra chương trình khác ) 03/01/2008 14:36:05 (+0700) | #7 | 107959
[Avatar]
onlinehack
Member

[Minus]    0    [Plus]
Joined: 04/12/2007 23:07:12
Messages: 116
Location: Ma maison
Offline
[Profile] [PM]
@nccm : cảm ơn vì giới thiệu project đó smilie
Giờ ta cùng đi tiếp câu chuyện. Đầu tiên là về

Quines
Quine là một dạng chương trình tạo mã nguồn đặc biệt. http://www.catb.org/esr/jargon/ định nghĩa quine là một "chương trình tạo ra một bản sao chép mã nguồn của chính nó". Đây là một chương trình quine được viết bởi Ryan Davis, là môt trong những đoạn ngắn nhất của ngôn ngữ Ruby:

Code:
f="f=%p;puts f%%f";puts f%f


Chạy chương trình này , và nhận kết quả. Bạn có thể thử trong shell prompt như sau :

Code:
ruby -e 'f="f=%p;puts f%%f";puts f%f' | ruby


Bạn sử dụng tuỳ chọn -e từ dòng lệnh để chỉ định thi hành dòng code Ruby, và sau đó ta sử dụng pipe để gửi kết quả lại cho trình thông dịch. Kết quả lại một lần nữa chính là mã nguồn chương trình đó.

Chỉnh sửa chương trình trong thời gian thực
Các ngôn ngữ động , như Ruby, cho phép bạn chỉnh sửa các phần khác nhau của chương trình dễ dành trong thời gian thực mà không phải tạo ra mã nguồn rõ ràng như ta đã làm trước đó, core API và framework của Ruby, như Ruby on Rails, tận dụng điều kiện thuận lợi này để tự động hoá các tác vụ lập trình thông thường. Lấy ví dụ, khi định nghĩa lớp, bạn có thể sử dụng phương thức attr_accessor để tạo ra các phương thức truy cập đọc/ghi tự động cho một tên thuộc tính . Để dễ hiểu, bạn xem đoạn mã :

Code:
class Person
  attr_accessor :name
end


là tương đương với đoạn mã dài dòng hơn sau :

Code:
class Person
  def name
    @name
  end
  def name=(new_name)
    @name = new_name
  end
end


Đoạn mã trước có một hạn chế nhỏ : biến chương trình tương ứng @name không thực sự được tạo ra cho đến khi bạn tạo giá trị khởi đầu cho nó. Có nghĩa là bạn sẽ nhận giá trị null nếu bạn cố gắng đọc thuộc tính của name trước khi ghi vào nó. Nếu bạn không cẩn thận, nó có thể tạo ra các lỗi khó thấy trong chương trình của bạn. Cách dễ nhất để tránh vấn đề này là gán cho biến @name một giá trị có nghĩa bằng phương thức Person#initialize. Vì nó là một kịch bản khá thông thường, sẽ đẹp hơn nếu phương thức này được tạo ra một cách tự động, trong tình huống truy cập đọc/ghi.
Ta cùng định nghĩa phương thức attr_initialize làm việc đó sử dụng sự tiện lợi của metaprogramming.

Đầu tiên, xác định hai phương thức là chìa khoá để thực hiện phép metaprogramming mong muốn :

Code:
cls.define_method(name) { body }


Nó thêm một phương thức mới cho lớp . Nó lấy các đối số là tên phương thức ( là kí tự hoặc chuỗi ) và thân ( code block )

Code:
obj.instance_variable_set(name, value)


Đoạn mã trên ràng buộc một biến chương trình vào một giá trị xác định. Tên của biến chương trình cần là kí tự hay chuỗi, và nó nên có tiền tố @ .
Bây giờ, chúng ta đã sẵn sàng định nghĩa lớp attr_initialize như phần mở rộng của lớp Object do đó các lớp khác có thể sử dụng nó

Code:
require 'generator'

class Object
  def Object.attr_initialize(*attrs)
    define_method(:initialize) do |*args|
      if attrs.length != args.length
        raise ArgumentError,
          "wrong number of arguments " +
          "(#{args.length} for #{attrs.length})"
      end
      SyncEnumerator.new(attrs, args).each do
        |attr, arg|
        instance_variable_set("@#{attr}", arg)
      end
    end
    attr_accessor *attrs
  end
end


Phương thức attr_initialize nhận giá trị vào là số biến - tên các thuộc tính (attrs). Mỗi tên thuộc tính có vị trí dành riêng cho nó trong danh sách tham số phương thức khởi tạo đã được tạo ra theo trình tự để gán giá trị khởi đầu. Ta tạo phương thức mới bằng cách kiểm tra số đối số được nhận bằng số thuộc tính ta đã chỉ định ban đầu. Nếu không, sẽ tạo ra thông báo lỗi. Sau này, chúng ta sử dụng đối tượng SyncEnumerator để lặp theo danh sách thuộc tính đã khai báo ( attrs ) và danh sách đối số thực sự (args ) để thực hiện từng đối số - thuộc tính ràng buộc một bằng phương thức instance_variable_set. Cuối cùng, ta uỷ quyền cho phương thức attr_accessor theo trình tự tạo phương thức truy cập đọc/ghi cho tất cả các thuộc tính đã được khai báo.

Sau đây là cách ta sử dụng phương thức attr_initialize

Code:
class Student
  attr_initialize :name, :id, :address
end

s = Student.new('Erika', 123, '13 Fake St')
s.address = '13 Wrong Rd'
puts s.name, s.id, s.address


Kết quả sẽ là:

Code:
Erika
123
13 Wrong Rd


Tổng kết

Khi bạn đã quen thuộc với kĩ thuật này, metaprogramming không phức tạp như được nghe thấy ban đầu. Metaprogramming cho phép bạn giảm thiểu các việc buồn tẻ hoặc lỗi lập trình thường gặp. Bạn có thể sử dụng nó để khởi tạo các bảng dữ liệu, tạo ra các đoạn mã tự động mà không thể rút gọn trong một chương trình, hay đơn giản là thử nghiệm sự khéo léo bằng cách viết các đoạn mã tự tái tạo.

“Tôi thích viết các chương trình có thể viết chương trình viết được chương trình khác”—Richard Sites

Giờ mình xin kết thúc, chúc mọi người có những giờ phút thú vị với metaprogramming, chúng ta sẽ còn đề cập đến nó sau này

[Up] [Print Copy]
[digg] [delicious] [google] [yahoo] [technorati] [reddit] [stumbleupon]
Go to: 
 Users currently in here 
1 Anonymous

Powered by JForum - Extended by HVAOnline
 hvaonline.net  |  hvaforum.net  |  hvazone.net  |  hvanews.net  |  vnhacker.org
1999 - 2013 © v2012|0504|218|