文脈指向プログラミングを試してみる
はじめに
最近,文脈指向プログラミング(COP: Context-Oriented Programming)[1]という新しいパラダイムに注目しています。ちなみに、COPにおける文脈とは、プログラムの実行時の環境や状況などを意味します。
世の中には、文脈に依存して動作が変わるようなプログラムはたくさんあります。例えば、社外と社内の両方からアクセスされるサーバアプリケーションでは、社外からアクセスされた場合には、表示する情報を限定したり、特別なパスワードを要求したりする必要があるかもしれません。あるいは、社外や社内のサーバにアクセスするクライアントアプリケーションでは、社外にアクセスするときだけ、セキュアなコネクションを張る、といったことも考えられます。他にも、レスポンシブデザインのように表示対象がスマホやPCかによって、レイアウトアルゴリズムを切り替えたい、といったケースも考えられます。
このようなプログラムを実装する場合、従来のプログラミング言語では、if文の嵐になってしまったり、あるいは、オブジェクト指向やデザインパターンを上手く活用した場合でさえ、関連する振る舞いが様々なクラスのメソッドに散らばって実装されたりするなど、見通しの悪いプログラムになりがちです。COPを用いると、より自然にエレガントな形で記述することができるようになります。
PyContext: Pythonによる文脈指向プログラミング
前置きはこれくらいにして、COPを使った具体的な例を紹介して行きましょう。Pythonには、PyContext[2]というCOPのフレームワークがあるので、今回はこれを使います。PyContextはPyPIに登録されているので、pipで簡単にインストールすることができます。ただ、残念ながら現在のPyContextは、継承関係を持つクラスに適用すると上手く動かないという問題があります。そこで、今回は筆者が作成したパッチをあてたものを使用します。パッチコードは本稿の付録に掲載しますので、ご自由にお使いくだだい。
例1:Personクラス
最初に紹介する例は、文献[1]にあるのを少しアレンジしたものです。以下のようにPerson, Employer, Employeeという3つの簡単なクラスを考えます。
person.py::
class Person(object): def __init__(self, name, address): self.name = name self.address = address def __str__(self): return "Name: " + self.name class Employer(Person): pass class Employee(Person): def __init__(self, name, address, employer): super(Employee, self).__init__(name, address) self.employer = employer
Personは、名前(name)と住所(address)をメンバーとして備えています。また、__str__メソッドを実装していて、これは、自分の名前を整形した文字列として返します。EmployerとEmployeeは、Personから継承してそれぞれ定義します。Employeeはnameとaddressに加えて、employerをメンバーに持ちます。
ここで、例えば、アドレス帳に出力する場合は、住所情報も載せ、それ以外の場合は名前だけにしたいなど、__str__メソッドで住所情報も出力したいケースが出てきたとします。普通に考えると、__str__メソッドの中で、何らかのグローバルな変数を参照して、処理を分岐させる、といった実装になるかと思います。
COPでは、以下のように文脈に依存した処理を切り離して実装することができます。
address_layer.py::
from context import layers import person class Address(layers.Layer): pass class Person(Address, person.Person): @layers.instead def __str__(self, context): return context.proceed() + "; Address: " + self.address
COPでは、文脈に依存した振る舞いの違いをレイヤという概念でくくりだします。上の例では、住所表示をするという文脈を、Addressというレイヤクラスとして定義しています。さらに、PyContextでは、レイヤクラスと元々のクラスの2つを継承したクラスを定義し、その中で振る舞いの差異を実装します。上の例の__str__メソッドがそれに該当します。@layers.insteadというアノテーションは、__str__メソッドが、もともとの__str__メソッドの実装の代わりとして呼び出されることを指示しています。さらに、__str__メソッドにcontextというパラメータが追加されていることに注目してください。context.proceed()によって、元々の__str__メソッドを呼び出すことができます。
それでは、実際に動作を見てみましょう。
>>> import person >>> import address_layer >>> p = person.Person('Suzuki', 'Tokyo') >>> print p Name: Suzuki
住所表示の文脈で実行する場合、pythonのwith構文に、先ほど定義したAddressレイヤオブジェクトを指定します。
>>> with address_layer.Address(): ... print p ... Name: Suzuki; Address: Tokyo
ちゃんと、住所が表示されるようになりました。
これだけではあまり面白くないので、雇用関係を表示するレイヤも追加してみましょう。Employeeオブジェクトの場合、その雇用者の情報も出力するような__str__メソッドのバリエーションを定義します。
employment_layer.py::
from context import layers import person class Employment(layers.Layer): pass class Employee(Employment, person.Employee): @layers.instead def __str__(self, context): return context.proceed() + "; [Employer] %s" % self.employer
EmployerとEmployeeを定義して、試してみましょう。
>>> import employment_layer >>> yer = person.Employer('Yamada', 'Osaka') >>> yee = person.Employee('Sasaki', 'Chiba', yer) >>> print yee Name: Sasaki >>> with employment_layer.Employment(): ... print yee ... Name: Sasaki; [Employer] Name: Yamada
ちゃんと、雇用者の名前も表示されました。さらに、住所情報も合せて表示させてみましょう。以下のようにwithを入れ子にして、AddressとEmploymentのレイヤを指定します。
>>> with employment_layer.Employment(): ... with address_layer.Address(): ... print yee ... Name: Sasaki; Address: Chiba; [Employer] Name: Yamada; Address: Osaka
雇用者の住所情報も同時に出力されていることに注目してください。
例2: urlopenにリトライを追加する
次に、もう少し実用的な例を紹介したいと思います。Pythonには、URLを指定してWeb上のリソースを取得するための手段を提供するurllibというライブラリがあります。
>>> import urllib >>> f = urllib.urlopen("http://www.kompira.jp") >>> print f.read()
サーバが一時的にビジー状態だったり、ダウンしたりしている場合、あるいは、不安定なネットワーク上では、コネクションの接続に失敗する可能性があります。そういう状況でも確実にアクセスをしたい場合には、接続エラーが起こっても、何秒か待ってリトライするという手法が一般に用いられます。このような処理を上記の例に実装するとしたら、urlopenの呼び出しにおける接続エラーの例外をハンドルし、何秒かsleepしたのち、再び、urlopenを呼び出すように変更してやるのが一般的でしょう。しかし、このやり方では、urlretrieveなど、別のメソッドでもリトライが必要な場合、いちいち同じ処理を記述しなくてはならなくなります。
COPを使うと、スッキリと実現することができます。urllibモジュールは、より低レベルのhttplibモジュールのHTTPConnectionクラスのconnectメソッドを使って、urlopenなどを実装しているので、この部分の処理にレイヤを差し込むようにします。
http_auto_retry.py::
from context import layers import httplib import socket import time class HTTPAutoRetry(layers.Layer): def __init__(self, retry, interval=5): assert retry > 0 self.retry = retry self.interval = interval class HTTPConnection(HTTPAutoRetry, httplib.HTTPConnection): @layers.instead def connect(self, context): retry = context.layer.retry interval = context.layer.interval for _ in range(retry): try: return context.proceed() except socket.error: print 'connection retry after %s seconds ...' % context.layer.interval time.sleep(context.layer.interval) raise e
ここでは、HTTPAutoRetryというレイヤクラスを定義しています。このクラスは初期化のためのパラメータとして、リトライ回数(retry)、リトライ間隔(interval)を取り、それぞれメンバーとしてセットしています。connectメソッドの中からは、contextのlayer属性を参照することで、現在のレイヤオブジェクトを取得できます。上記の例では、これを用いてconnectメソッド中からretryとintervalの値を取得しています。
それでは、実際に動かしてみましょう。
>>> import urllib >>> from http_auto_retry import HTTPAutoRetry >>> def get(): ... f = urllib.urlopen('http://www.kompira.jp:8080') ... return f.read() >>> get() Traceback (most recent call last): ...(省略)... IOError: [Errno socket error] [Errno 111] Connection refused >>> with HTTPAutoRetry(3): ... get() ... connection retry after 5 seconds ... connection retry after 5 seconds ... connection retry after 5 seconds ... Traceback (most recent call last): ...(省略)... IOError: [Errno socket error] [Errno 111] Connection refused >>> with HTTPAutoRetry(2, 10): ... urllib.urlretrieve('http://www.kompira.jp:8080') ... connection retry after 10 seconds ... connection retry after 10 seconds ... Traceback (most recent call last): ...(省略)... IOError: [Errno socket error] [Errno 111] Connection refused
このように、HTTPAutoRetryの文脈で呼び出されたHTTP接続は、すべて接続リトライを行うように振る舞いを変えることができました。
まとめ
今回は、比較的新しいプログラミングパラダイムである文脈指向プログラミングを紹介しました。最近のWebアプリケーションは、様々な環境やデバイスに対応できるように、複雑化していく一方です。COPはこのようなアプリを実装する上での強力な切り札となる可能性を持っていると思います。似たような技術としてアスペクト指向プログラミング(AOP: Aspect-Oriented Programming)があり、ご存じの方も多いと思いますが、COPは研究の系譜的にはAOPの後継にあたる技術のようです。個人的にはAOPよりもCOPの方がプログラマにとって扱い易い気がするので、今後、きちんとした実装が出てきたら、もっと一般に広まっていく技術なのではないでしょうか。Kompiraの開発に適用していける部分もかなり多いので、少しずつ活用していきたいと思います。
参考文献など
[1] Robert Hirschfeld, et.al., "Context-oriented Programming", JOT, Vol.7, No.3, 2008.(http://www.jot.fm/issues/issue_2008_03/article4.pdf)
[2] Martin von Löwis, et.al., "Context-oriented programming: beyond layers", ICDL '07.(http://scg.unibe.ch/archive/papers/Loew07aPyContext.pdf)
その他、COPの研究グループのサイトは以下にあります。
付録:PyContextのパッチ
PyContext-1.0で、継承を上手く扱えるように修正したパッチを掲載します。変更のポイントは以下のとおりです。
- 新スタイルのクラスに対応
- 子クラス側でレイヤが差し込まれた時に、親クラスにレイヤが定義済みだと、そちらに差し込まれてしまうのを修正
- レイヤのディスパッチ処理において、クラス継承をたどって、すべてのレイヤを合成する処理を追加
--- /usr/lib/python2.6/site-packages/context/layers.py 2013-03-13 15:34:06.000000000 +0900 +++ layers.py 2013-03-13 15:36:26.491854687 +0900 @@ -51,10 +51,10 @@ context_stack = ContextStack() -class _Original: +class _Original(object): pass -class Context: +class Context(object): def __init__(self, target, name, finalfunc): self.target = target self.name = name @@ -136,9 +136,36 @@ raise NotImplementedError, func.__layer__ context.insert(func, layer, proceed) -def dispatch(self, name, args, kw): +def compose_layers(base, name): + layers = {} + + def _trav_old_style(cls): + for base in cls.__bases__: + for k in _trav_old_style(base): + yield k + yield cls + + def _trav_new_style(cls): + if len(cls.__mro__) > 1: + for k in _trav_new_style(cls.__mro__[1]): + yield k + yield cls + + if hasattr(base, '__mro__'): + g = _trav_new_style(base) + else: + g = _trav_old_style(base) + + for cls in g: + try: + layers.update(cls.__dict__['__layers__'][name]) + except KeyError: + continue + return layers + +def dispatch(cls, self, name, args, kw): #print "Invoking", name, "on", self - layers = self.__layers__[name] + layers = compose_layers(cls, name) context = Context(self, name, layers[_Original]) # build up evaluation chain: start with implicit layers, # starting with highest priority, per priority in the order @@ -156,6 +183,17 @@ add_dispatch(context, layer, layers.get(type(layer))) return context.proceed(*args, **kw) +def get_original(base, k): + try: + return base.__dict__[k] + except KeyError: + pass + for cls in base.__mro__: + try: + return cls.__dict__['__layers__'][k][_Original] + except KeyError: + continue + return getattr(base, k) class _MetaLayer(type): ignored = set(['__module__']) @@ -168,8 +206,8 @@ layer = bases[0] base = bases[1] try: - layers = base.__layers__ - except AttributeError: + layers = base.__dict__['__layers__'] + except KeyError: layers = base.__layers__ = {} for k, v in dict.items(): if k in cls.ignored: @@ -181,14 +219,14 @@ # hack to make sure the name gets bound properly def make_dispatch(name=k): def _dispatch(self, *args, **kw): - return dispatch(self, name, args, kw) + return dispatch(base, self, name, args, kw) return _dispatch - layers[k] = { _Original:getattr(base, k) } - base.__dict__[k] = make_dispatch() + layers[k] = { _Original: get_original(base, k) } + setattr(base, k, make_dispatch()) layers[k][layer] = v return None -class Layer: +class Layer(object): __metaclass__ = _MetaLayer priority = 0 @@ -201,7 +239,7 @@ def active(self): return False -class Disabled: +class Disabled(object): def __init__(self, klass): self.klass = klass
GraphDBについて
最近、あちこちでGraphDBというキーワードを耳にしますが、とある機会があってGraphDBについていろいろと調べてみたので、簡単に紹介してみたいと思います。なお、本調査にあたって参考にした文献をまとめて文末に挙げておきます。この記事は、基本的にはこれらの文献の内容にもとづいています。
GraphDBとは
GraphDBはその名のとおりデータモデルとしてグラフ構造を採用しているものを指します。グラフ構造と一口にいっても有向グラフ、無向グラフなど様々なものがありますが、GraphDBでは、プロパティグラフ(グラフの頂点や辺に複数のプロパティを含めることができる)やマルチグラフ(複数の種類の辺を含むもの)、ハイパーグラフ(n頂点間を結ぶ辺が許されるもの)、あるいはこれらの組み合わせなど、割と汎用的なモデルを採用することが多いようです。
ここでのポイントは、採用している「データモデル」がグラフ構造ということであり、実際のデータをどのような(物理)構造で格納するかは問わないということです。実際、いくつかのGraphDBはバックエンドにRDBを採用しています。データモデルがグラフ構造ということは、いいかえれば、ユーザーは辺と頂点を使って物事をモデル化することになります。一方で、RDBユーザーはタプルと関係を使ってモデル化します。この違いは、それぞれのDBが提供する問い合わせや更新操作の違いにも表れてきます。
ところで、アカデミックの世界では、GraphDBは1990年代前半に盛んに研究されていました。その後、一旦は廃れてしまったようですが、ここにきて、エンティティ間の関係性を重視するようなアプリケーション(ソーシャルネットワークやセマンティックWebなど)がトレンドになるとともに、再び注目を集めるようになっています。
GraphDBモデルの特徴
データ構造、操作言語、整合性制約というデータベースモデルの3つの基本的な要素にもとづいてGraphDBのモデルを特徴づけると以下のようになります。
- データやスキーマがグラフ(もしくはグラフの概念を一般化したようなデータ構造、ハイパーグラフなど)によって表現される。
- データ操作がグラフ変形によって表現される。あるいは、パスや近隣ノードなどを扱うプリミティブな操作を提供する。また、グラフのパターンマッチによって、ある構造をもった部分グラフを見つけることができる。
- グラフデータ構造に関連した整合性制約が扱える。ラベル名がユニークである、ノード上の型に関する制約、関数従属性など。
上記3つの要素のうち、整合性制約をきちんとサポートしているようなGraphDBはまだ少ないようです。
GraphDBの適用領域
GraphDBを用いるのに最も適した領域の一つは、複雑ネットワークと呼ばれる現実世界での巨大で複雑なネットワークを扱う分野のアプリケーションです。以下にいくつか例を挙げます。
- ソーシャルネットワーク
- ソーシャルネットワークでは、ノードが人やグループ、辺はそれらを結ぶ関係(友達、ビジネス、夫婦などなど)となります。そのようなネットワークの分析や可視化、データ処理などは活発な領域です。
- 科学技術ネットワーク
- インターネット、電力ネットワーク、エアラインの経路、電話ネットワーク、配送ネットワーク、地理情報システム、道路網、鉄道網、歩行者の交通量、などなど。
- 生物学的ネットワーク
- ゲノム科学、細胞内の化学反応の関係図、生物種の相同関係、食物連鎖、神経伝達ネットワーク、などなど。
複雑ネットワーク以外にも、従来のデータベースモデルでは表現が難しかった複雑なオブジェクト、あるいはオブジェクト間の関係を扱うようなアプリケーションはGraphDBが適しているかもしれません。
最近のGraphDB
ここではGraphDBの実装をいくつか紹介します。
- AllegroGraph
- 先駆者的な存在のGraphDBです。ただ、もともとはGraphDBとして登場しましたが、最近の開発では、セマンティックWebを指向しているようです。ソーシャルネットワーク分析のための特別な機能を提供しています。
- DEX
- 永続的、および、一時的なグラフを操作するためのJavaライブラリを提供しています。実装がビットマップにもとづいており、巨大なグラフでも良好なパフォーマンスが得られることを目指しています。
- HyperGraphDB
- ハイパーグラフモデル(辺の概念が2つ以上のノードを結べるように拡張されている)を実装したデータベースです。このモデルは高階な関係を自然に表現することができます。特に、知識表現、人工知能、生物情報学のような領域でのデータモデリングに有用です。
- InfiniteGraph
- 分散環境下でのラージスケールなグラフをサポートすることを指向したデータベースです。大量かつ分散したデータストアにまたがって効率的に関係を辿れることを目指しています。
- Neo4j
- 関係がファーストクラスオブジェクトであるネットワーク指向モデル(network-oriented model)に基づいています。オブジェクト指向API、グラフのためのディスクベースのストレージマネージャ、グラフ巡回のためのフレームワークを実装しています。また、Cypherという独自のクエリー言語も提供しています。
- Sones
- グラフを対象とした高いレベルのデータ抽象概念をサポートしているGraphDBです。SQL風の独自のクエリー言語と、分散ファイルシステムを提供しています。
おわりに
以上、最近流行りのGraphDBについて紹介しました。ソーシャルネットワークに代表されるように、エンティティ間の関係性に着目した分析やアプリケーションが増々重要になってきています。このように従来のリレーショナルモデルよりもグラフモデルを用いた方が自然にモデル化できるようなアプリケーション領域はたくさんあるかと思うので、今後、ますます広がっていくと思われます。
Kompiraの開発者としては、システム運用にGraphDBを使えのではないかと考えています。たとえば、システムの構成要素をGraphDBに格納しておくことで、運用にとって必要な情報を簡単に引き出せるようになると思います。具体的には、あるサーバにインストールされているミドルウェアをアップデートしようとした時、その影響がどこまで及ぶかをGraphDBに対する問い合わせによって把握するといったことができそうです。同様に障害発生時の影響分析にも活用できそうです。
また、個人的には問い合わせ言語について興味があるので、余裕があったら簡単な問い合わせ言語を実装してみようかと思っております。
参考文献
[1] Renzo Angles, Claudio Gutierrez, "Survey of Graph Database Models", http://swp.dcc.uchile.cl/TR/2005/TR_DCC-2005-010.pdf
[2] Renzo Angles, "A Comparison of Current Graph Database Models", http://dcc.utalca.cl/~rangles/files/gdm2012.pdf
PythonでHardware UUIDを取得する
ノードロックライセンス機能を実装するにあたって,ハードウェアUUIDを取得する必要があったため,少し調べてみました.
■ はじめに
いわゆるノードロックライセンスでは,コンピュータ固有のIDに紐づけてライセンスを発行するため,ハードウェアを識別するための何かが必要となります.そのための「何か」として簡単に思いつくものとしては,MACアドレスやIPアドレス,ホスト名などがあるかと思います.このうちIPアドレス,ホスト名は,簡単に取得することができますが,変更されやすい性質のものなので,今回の用途にはあまり向いていません.Pythonのuuidモジュールには,getnode()というハードウェアアドレス(MACアドレス)を取得する関数があるので,これを使うのが簡単な方法かもしれませんが,MACアドレスも,NICが故障するなどして交換したら変わってしまいます.
CPUID命令を用いて,CPU固有のシリアル番号(PSN)を取得し,これを利用するという手もありますが,調べてみたところ,PSNはPentium IIIにのみ実装されているあだ花的な機能なので,これも却下です.(ちなみに,PyCPUIDというパッケージを使えば,Pythonから簡単にCPUID命令の実行結果を取得することが可能です.)さらにいろいろ調べてみたところ(参考サイト[1]),ハードウェア固有のIDとして,Hardware UUIDが利用できそうなことが分かったので,今回はこれを利用しようと思います.
■ コマンドからのHardware UUID取得
Hardware UUIDは,dmidecodeコマンドを使って調べることが可能です.
# dmidecode | grep UUID
もしくは,hal-get-propertyコマンドを使って以下のようにしても取得できます.
$ hal-get-property --udi /org/freedesktop/Hal/devices/computer --key system.hardware.uuid
ただし,hal-get-propertyの実行には,messagebusとhaldaemonの各サービスが立ち上がっている必要があります.
# service messagebus start # service haldaemon start
■ HALライブラリを用いてのUUID取得
さて,上記のコマンドを,Popenなどで,Pythonから実行して結果を取得するという方法でも良いのですが,できればライブラリ経由などで直接取得したいところです.参考サイト[1]を見ると,PythonからHALライブラリ(libhal)を呼び出すサンプルコードがあったので,今回はこれを利用することにしました.以下がその抜粋です.
import ctypes from ctypes.util import find_library from ctypes import Structure class DBusError(Structure): _fields_ = [("name", ctypes.c_char_p), # ... 省略 ... class HardwareUuid(object): # ... 省略 ... @property def _uuid(self): if not self._uuid_: udi = ctypes.c_char_p("/org/freedesktop/Hal/devices/computer") key = ctypes.c_char_p("system.hardware.uuid") self._hal.libhal_device_get_property_string.restype = \ ctypes.c_char_p self._uuid_ = self._hal.libhal_device_get_property_string( self._ctx, udi, key, self._dbus_error) return self._uuid_
このPythonのコードは,ctypesというPythonの外部関数インターフェイス(FFI)用モジュールを使って,Cで実装されたライブラリ(libhal)の関数を呼び出しています.ただ,残念なことにこのコードをそのまま利用すると,セグメンテーションフォールトとなってしまいました.どうやらバグがあるようです.libhalのドキュメント(参考サイト[2],[3])をもとに上記コードを調べてみたところ,_uuidメソッド定義の最後の方にlibhal_device_get_property_stringを呼び出している部分で,DBusErrorオブジェクトを渡しているところがありますが,そこで必要なctypes.byrefが抜けているのが原因とわかりました.
最終的にはエラー処理のコードなどを追加して,以下のような形にしました.
import ctypes from ctypes.util import find_library from ctypes import Structure class DBusRuntimeError(Exception): pass class DBusError(Structure): _fields_ = [("name", ctypes.c_char_p), ("message", ctypes.c_char_p), ("dummy1", ctypes.c_int), ("dummy2", ctypes.c_int), ("dummy3", ctypes.c_int), ("dummy4", ctypes.c_int), ("dummy5", ctypes.c_int), ("padding1", ctypes.c_void_p),] class HardwareUuid(object): def __init__(self, dbus_error=DBusError): self._hal = ctypes.cdll.LoadLibrary(find_library('hal')) self._dbus_error = dbus_error() self._hal.dbus_error_init(ctypes.byref(self._dbus_error)) self._conn = self._hal.dbus_bus_get(ctypes.c_int(1), ctypes.byref(self._dbus_error)) if self._hal.dbus_error_is_set(ctypes.byref(self._dbus_error)): emesg = "Unable to connect to DBus: %s" % self._dbus_error.message raise DBusRuntimeError(emesg) self._ctx = self._hal.libhal_ctx_new() if not self._hal.libhal_ctx_set_dbus_connection(self._ctx, self._conn): emesg = "Error: %s" % self._dbus_error.message raise DBusRuntimeError(emesg) if not self._hal.libhal_ctx_init(self._ctx, ctypes.byref(self._dbus_error)): emesg = "Hal context initializing failure: %s" % self._dbus_error.message raise DBusRuntimeError(emesg) self._uuid_ = None def __call__(self): return self._uuid @property def _uuid(self): if not self._uuid_: udi = ctypes.c_char_p("/org/freedesktop/Hal/devices/computer") key = ctypes.c_char_p("system.hardware.uuid") self._hal.libhal_device_get_property_string.restype = ctypes.c_char_p self._uuid_ = self._hal.libhal_device_get_property_string( self._ctx, udi, key, ctypes.byref(self._dbus_error)) if self._hal.dbus_error_is_set(ctypes.byref(self._dbus_error)): emesg = "Error getting string property: %s" % self._dbus_error.message raise DBusRuntimeError(emesg) return self._uuid_
実行してみます。
>>> getuuid = HardwareUuid()
>>> getuuid()
'564D5934-8B4C-6465-D4B3-519D08DCC3C9'
めでたしめでたし.
■ 参考サイト
[1] http://stackoverflow.com/questions/2461141/get-a-unique-computer-id-in-python-on-windows-and-linux
[2] http://harmattan-dev.nokia.com/docs/platform-api-reference/showdoc.php?pkn=libhal&wb=daily-docs&url=Li94bWwvZGFpbHktZG9jcy9saWJoYWw%3D
[3] http://madison-project.wdfiles.com/local--files/tutorials/Hal_Tutorial.pdf