Code Review: try – catch

December 22, 2009 CodeReview 2 comments

Sometimes it is hard to convince that catch(…) is not so good!

Today I provided code review to one of my colleague and he was totally sure the he need try{/*code*/}catch(){} in few places.

First looked like:

try
{
   int someValueId = Convert.ToInt32(value);
   if (someValueId > 0)
   {
     return new ValidationResult(true, “Valid”);
   }
}
catch{}

I rewrote code to:

int serviceId;
Int32.TryParse(value, out serviceId);
if (serviceId > 0)
{
  return new ValidationResult(true, “Valid”);
}

Then I spent few minutes to convince him that this code is ok and that this is not good to have catch for everything.

Another code looked like:

string result = string.Empty;
try
{
    result = documentContent.Descendants(“SomeValueCode”).FirstOrDefault().Value;
}
catch (Exception)
{
    //He was trying to ensure me that since we are doing validation we should not throw an exception
}
//do some work here

Now I tried to explain that FirstOrDefault() returns us either first element of collection OR null, so if we got null we just need to do some another stuff,
but if some exception will occur we should allow it fly out OR write some specific catch for exceptions that could be thrown. Code which I propose looks like:

var resultElement = documentContent.Descendants(“SomeValueCode”).FirstOrDefault();
if(interventionCodeElement != null)
{
    var result = resultElement.Value;
    //do some work here
}

After conversation we decided that try(/*code*/)catch() is not so good.


2 comments


AssertDatesAreEqual

December 21, 2009 UnitTesting No comments

Have you ever faced with UT that failed once and then you always see it succeeded?

One of such tests could be test where you verify that date properties of some newly created entity are equal if they take DateTime.Now for that.

Today Hudson showed me that some test failed in project I worked near half a year ago, so test looked like:

var item1 = new Item();
var item2 = new Item();
//…
Assert.AreEqual(item1.CreatedDate, item2.CreatedDate);

Test failed because time difference between dates was greater than few milliseconds. This could occur when processor is too occupied.

So I changed assert call to more realistic:

public void AssertDatesAreEqual(DateTime dateTimeLeft, DateTime dateTimeRight)
{
     Assert.IsTrue(Math.Abs((dateTimeLeft dateTimeRight).Milliseconds) < 100,
    “TimeSpan difference between dates is greater than 100 milliseconds.”);
}


No comments


NHibernate: The column name COLUMN_ID is specified more than once in the SET clause

December 9, 2009 NHibernate 4 comments

Sometime ago I faced with NHibernate issue and spent much time on figuring out how to resolve it. Issue was with saving complicated entity.
Error:

The column name ‘RESOURCE_ID’ is specified more than once in the SET clause. A column cannot be assigned more than one value in the same SET clause. Modify the SET clause to make sure that a column is updated only once. If the SET clause updates columns of a view, then the column name ‘RESOURCE_ID’ may appear twice in the view definition.

With NHibernate Profiler I found that this fails on the next SQL: 

UPDATE SOME_TABLE SET SOME_COLUMN1 = ?, SOME_COLUMN2 = ?, RESOURCE_ID = ?, SOMETHING_ELSE = ?, RESOURCE_ID = ? WHERE SOME_TABLE_ID = ?

With better look you will see that there two times RESOURCE_ID mentioned so this looks like wrong mapping. But I was sure that everything is ok there.

This two References are wrong.
At first glance do you see why? At that moment I was not able also.

References(x => x.Resource)
        .Access.AsCamelCaseField(Prefix.Underscore)
        .WithForeignKey(“RESOURCE_ID”).TheColumnNameIs(“RESOURCE_ID”)
        .FetchType.Join();

References(x => x.ResourceRole)
        .Access.AsCamelCaseField(Prefix.Underscore)
        .WithColumns(“RESOURCE_ROLE_ID”, “RESOURCE_ID”)
        .FetchType.Join();

Here Resource has key (RESOURCE_ID).
ResourceRole has composite key (
ROLE_ID and RESOURCE_ID).

After long searching and many tries I found why mapping is not correct. Even have my explanation for it.

When we reference ResourceRole we already use RESOURCE_ID field, so when we setup Resource and say “Hey, there is one more RESOURCE_ID“, then NHibernate cannot find out how to update all this correctly.

Solution:
insert=”false” update=”false” attributes for one of references solves the issue.

In Fluent NHibernate it looks like:

.SetAttribute(“update”, “false”);


4 comments


NHibernate Criteria for Nullable property

December 7, 2009 NHibernate 2 comments

Image that you want to fetch friends from database by the First Name, Last Name and Age, which are properties of your class Friend. With NHibernate you could write query which will look like:

public IList<Friend> GetFriends(string firstName, string lastName, int? age)
{
    IList<Friend> friends = Session.CreateCriteria(typeof(Friend))
        .Add(Expression.Eq(“FirstName”, firstName))
        .Add(Expression.Eq(“LastName”, lastName))
        .Add(Expression.Eq(“Age”, age))
        .List<Friend>();

    return friends;
}

But it is wrong. Since Age is the Nullable type (int?), your call GetFriends(“Andriy”,”Buday”,null) will not get my record even if there is such in database and the column AGE is NULL there. So to request you need this: ageExpression = Expression.IsNull(“Age”); but it will not work for not null age.
For our luck there is AbstractCriterion so we can generalize our Expressions like here:

public IList<Friend> GetFriends(string firstName, string lastName, int? age)
{
    AbstractCriterion ageExpression;
    if (age == null)
    {
        ageExpression = Expression.IsNull(“Age”);
    }
    else
    {
        ageExpression = Expression.Eq(“Age”, age);
    }

    IList<Friend> friends = Session.CreateCriteria(typeof(Friend))
        .Add(Expression.Eq(“FirstName”, firstName))
        .Add(Expression.Eq(“LastName”, lastName))
        .Add(ageExpression)
        .List<Friend>();

    return friends;
}


2 comments


Travelling Salesman Problem with Prolog

December 6, 2009 Prolog 14 comments

Do you know what is the Travelling Salesman Problem? Or course you know if you have at least some technical education. Will you forget what about it this problem? Could be… But I’m 100% sure that I will never, after I did task that I’m going to describe. Hope that comments in code will be enough to keep you on track.

domains

/* will allow us cooperate with better names, for me this is like #typedef in C++ */

  town = symbol

  distance = unsigned

  rib = r(town,town,distance)

  tlist = town*

  rlist = rib*

predicates

  nondeterm way(town,town,rlist,distance)

  nondeterm route(town,town,rlist,tlist,distance)

  nondeterm route1(town,tlist,rlist,tlist,distance)

  nondeterm ribsmember(rib,rlist)

  nondeterm townsmember(town,tlist)

  nondeterm tsp(town,town,tlist,rlist,tlist,distance)

  nondeterm ham(town,town,tlist,rlist,tlist,distance)

  nondeterm shorterRouteExists(town,town,tlist,rlist,distance)

  nondeterm alltown(tlist,tlist)

  nondeterm write_list(tlist)

clauses

  /*

  Nothing special with write_list.

  If list is empty we do nothing,

  and if something there we write head and call ourselves for tail.

  */

  write_list([]).

  write_list([H|T]):-

    write(H,‘ ‘),

    write_list(T).

  /* Is true if town X is in list of towns… */

  townsmember(X,[X|_]).

  townsmember(X,[_|L]):-

    townsmember(X,L).

  /* Is true if rib X is in list of ribs…  */    

  ribsmember(r(X,Y,D),[r(X,Y,D)|_]).

  ribsmember(X,[_|L]):-

    ribsmember(X,L).   

  /* Is true if Route consists of all Towns presented in second argument */

  alltown(_,[]).

  alltown(Route,[H|T]):-

    townsmember(H,Route),

    alltown(Route,T).

 

  /* Is true if there is a way from Town1 to Town2, and also return distance between them */

  way(Town1,Town2,Ways,OutWayDistance):-

    ribsmember(r(Town1,Town2,D),Ways),

    OutWayDistance = D.

   

%/*

  /* If next is uncommented then we are using non-oriented graph*/

  way(Town1,Town2,Ways,OutWayDistance):-

    ribsmember(r(Town2,Town1,D),Ways), /*switching direction here…*/

    OutWayDistance = D.

%*/

 

  /* Is true if we could build route from Town1 to Town2 */

  route(Town1,Town2,Ways,OutRoute,OutDistance):-

    route1(Town1,[Town2],Ways,OutRoute,T1T2Distance),

%SWITCH HERE

    way(Town2,Town1,Ways,LasDist), /* If you want find shortest way comment this line*/

    OutDistance = T1T2Distance + LasDist. /* And make this: OutDistance = T1T2Distance.*/

   

  route1(Town1,[Town1|Route1],_,[Town1|Route1],OutDistance):-

    OutDistance = 0.

  /* Does the actual finding of route. We take new TownX town and if it is not member of PassedRoute,

  we continue searching with including TownX in the list of passed towns.*/

  route1(Town1,[Town2|PassedRoute],Ways,OutRoute,OutDistance):-

    way(TownX,Town2,Ways,WayDistance),

    not(townsmember(TownX,PassedRoute)),

    route1(Town1,[TownX,Town2|PassedRoute],Ways,OutRoute,CompletingRoadDistance),

    OutDistance = CompletingRoadDistance + WayDistance.

 

  shorterRouteExists(Town1,Town2,Towns,Ways,Distance):-

     ham(Town1,Town2,Towns,Ways,_,Other),

         Other < Distance.

  /* calling tsp(a,a,…. picks any one connected to a town and calls another tsp*/

  tsp(Town1,Town1,Towns,Ways,BestRoute,MinDistance):-

    way(OtherTown,Town1,Ways,_),

        tsp(Town1,OtherTown,Towns,Ways,BestRoute,MinDistance).

  /*Travelling Salesman Problem is Hammilton way which is the shortes of other ones.*/

  tsp(Town1,Town2,Towns,Ways,BestRoute,MinDistance):-

        ham(Town1,Town2,Towns,Ways,Route,Distance),

        not(shorterRouteExists(Town1,Town2,Towns,Ways,Distance)),

    BestRoute = Route,

    MinDistance = Distance.

  /*Hammilton route from Town1 to Town2 assuming that Town2->Town1 way exists.*/

  ham(Town1,Town2,Towns,Ways,Route,Distance):-

    route(Town1,Town2,Ways,Route,Distance),

%SWITCH HERE

    alltown(Route,Towns), % if you want simple road without including all towns you could uncomment this line

    write_list(Route),

    write(tD = “,Distance,n).

%   fail.

   

goal

/* EXAMPLE 1

  AllTowns = [a,b,c,d],

  AllWays = [r(a,b,1),r(a,c,10),r(c,b,2),r(b,c,2),r(b,d,5),r(c,d,3),r(d,a,4)],

*/

/* EXAMPLE 2 */

  AllTowns = [a,b,c,d,e],

  AllWays = [r(a,c,1),r(a,b,6),r(a,e,5),r(a,d,8),r(c,b,2),r(c,d,7),r(c,e,10),r(b,d,3),r(b,e,9),r(d,e,4)],

  tsp(a,a,AllTowns,AllWays,Route,Distance),

%SWITCH HERE

%  tsp(a,b,AllTowns,AllWays,Route,Distance),

  write(“Finally:n),

  write_list(Route),

  write(tMIN_D = “,Distance,n).

Let’s take a look on the following graph:

Here is output of my program: 

  a e d b c       D = 15
  a e d b c       D = 15
  a d e b c       D = 24
  a e b d c       D = 25
  a b e d c       D = 27
  a d b e c       D = 31
  a b d e c       D = 24
  Finally:
 a e d b c       MIN_D = 15
 Which is correct! :)
Also I did not mention before I faced with issue of getting minimum of the value returned from predicate.
And this link provided help for me:


14 comments


How to copy data from one database to another one using T-SQL

December 2, 2009 SQL No comments

If you want copy data from one database to another on the same server you could use next SQL:

INSERT INTO [DestinationDatabase].[dbo].[Table1] ([Column1], [Column2], [Column3])
    SELECT [Column1], [Column2], [Column3] FROM [SourceDatabase].[dbo].[Table2]

Also if you want to copy data with the schema of table, i.e. copy table structure and contents to another database you could use:

SELECT * INTO [DestinationDatabase].[dbo].[TABLE]
   FROM [SourceDatabase].[dbo].[TABLE]


No comments


Delphi resourcestring keyword

November 25, 2009 Delphi 1 comment

Delphi has a reserved word resourcestring for ensuring that strings allow localization.

resourcestring keyword works like this:

resourcestring
msg=’Hello, World’;
begin
ShowMessage (msg);
end.

Delphi automatically replaces the reference to msg with a call to the system function LoadResString(), which retrieves the string “Hello, World” from the resource part of the executable. The Delphi ITE achieves its translation mechanism by redirecting these fetches to other files.

By including gnugettext.pas in your project, these fetches are replaced with another function, which translates all the strings. The default is, that resourcestrings are translated using the default textdomain, i.e. default.mo. In case you want the system to search other mo files, if the translation isn’t found in default.mo just make some calls to the AddDomainForResourceString.

From this table:
http://www.netcoole.com/delphi2cs/statements.htm
we could see that C# corresponding statement is const string res = “bla-bla string”

At our project we have custom stringresourcetool which is able to generate classes with const strings properties basing on text files at design time in VS.

So next line in *.txt file:
msg=Hello, World
will generate:
public static string msg{get…}
which gets actual string from generated resource file.

When we are talking about resource files for globalization C# has easy way to fetch required strings. We could have files
like YourApplicationResources.uk-UA.resx for Ukrainian culture and so on… So application will be grabbing needed strings from needed files.

Interesting fact on theme:
“NASA’s Mars Climate Orbiter was lost on September 23, 1999 at a cost of $125 million because one engineering team used metric units, while another one used inches for a key spacecraft operation. When writing applications for international distribution, different cultures and regions must be kept in mind.”


1 comment


Implement IDisposable Correctly

November 16, 2009 .NET No comments

Running FxCop on some old stuff code gives me next error: “Implement IDisposable Correctly”.

And provided me with link to MSDN: http://msdn2.microsoft.com/ms244737(VS.90).aspx

I took look at existing code and it looked like:

        #region IDisposable Members
        void IDisposable.Dispose()
        {
            ShutdownServices();
        }
        #endregion

And here are lot of incorrect implementation of the IDisposable.
At first I added GC.SuppressFinalize(this); like here:

        void IDisposable.Dispose()
        {
            ShutdownServices();
            GC.SuppressFinalize(this);
        }

Of course it was not enough – it is needed to call Dispose(true) because my class is not sealed.
You also need to have re-overridden Finalize.

So code which passed FxCop verification looks like:

        #region IDisposable Members
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        ~ServiceLoader()
        {
            Dispose(false);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                ShutdownServices();
            }
        }
        #endregion

Please go to MSDN page and read it, since to know how to implement IDisposable correctly is important thing.


No comments


Use Arg ONLY within a mock method call while recording

November 15, 2009 RhinoMocks No comments

If you see this error when testing with RhinoMocks

System.InvalidOperationException: Use Arg<T> ONLY within a mock method call while recording. 0 arguments expected, 2 have been defined.

ensure that method on which you setuped expectation is virtual.
Actually this should be all you need.

I faced this when doing test like this:

[Test]
public void Start_Service_InitializeWasCalled()
{
    var service = MockRepository.GenerateMock<SpecificServiceBase>();
    //some setup here…
    service.Expect(x => x.Initialize(Arg<ServiceSettingElement>.Is.Anything, Arg<LogDelegate>.Is.Anything)).Repeat.Once();

    serviceControllerBase.Start();

    service.VerifyAllExpectations();
}

As you see I’m trying to verify that method Initialize was called after Start was triggered. Signature of method looks like:

public void Initialize(ServiceSettingElement configuration, LogDelegate logDelegate){}

After I changed it to:

public virtual void Initialize(ServiceSettingElement configuration, LogDelegate logDelegate){}

My test passed ok.

I want to mention that when testing with RhinoMocks you always need to pay attention on things like virtual methods, using interfaces instead of classes, also using protected instead of private to have possibility test those stuff with RhinoMocks.


No comments


Funny Prolog

October 25, 2009 Prolog No comments

Hello. Today I would like to speak a little bit not about C#. We will talk about strange language Prolog. At first I was interacted with it I was impressed that… there is no assignment operator in Prolog! Yes, that looks ugly, but even in general Prolog is not kind of languages we aware of.

Here’s a short look at how Prolog differs from traditional programming languages.
Prolog is descriptive.

Instead of a series of steps specifying how the computer must work to solve a problem, a Prolog program consists of a description of the problem.
Conceptually, this description is made up of two components:
1.  descriptions of the objects involved in the problem
2.  facts and rules describing the relations between these objects

The rules in a Prolog program specify relations between the given input data and the output which should be generated from that input.

Source: "C:Program FilesVIP52DOCGetstart.doc"

Actually there are operator like ‘=’, but we can use it only to not assigned before variables. But this is not matter of this short article. Its matter is just to describe few tasks solved by me, so they could provide some examples to you.

Let us start with following task: We need to build binary searching tree using data in file and then we will need to traverse tree to get sorted list.
As you see in this we will figure out how to work with files, lists in Prolog and also we will find out how build such structure as tree. Before we will start with this task I would like to mention that I’m using Prolog v5.2.

Next two lines are enough to have compliable program:

goal
write("hey, there!").

Now we will add treetype to domains. Following declaration means that tree can be written as functor tree or as functor empty. empty is required to mark empty leaf.

domains
treetype = tree(string,treetype,treetype);empty

As we are going to have binary tree we need to implement inserting strategy. At first it will insert value into empty tree, or if it is not empty it will insert in right if value is greater than current tree node otherwise it will insert into left one. So insert statements will look like:

predicates

  insert(string, treetype, treetype)

clauses
  % if current node is empty we will insert NewItem instead of it
  insert(NewItem, empty, tree(NewItem,empty,empty)):-!.
  % if NewItem is less than Element we insert it to left tree
  insert(NewItem, tree(Element,Left,Right), tree(Element,NewLeft,Right)):-

                NewItem < Element,
                !,
                insert(NewItem,Left,NewLeft).
  % otherwise we will insert it to the right tree

  insert(NewItem, tree(Element,Left,Right), tree(Element,Left,NewRight)):-
                insert(NewItem,Right,NewRight).

Next step is traversing. (If you could remember there are three types of traversing.) Let us imagine root node T with left tree L and right one R. Then traversing like T -> L -> R will be called preorder traversing. Code will look like:

predicates
traverse(treetype)
clauses

traverse(empty).
traverse(tree(Element,Left,Right)):-
write(Element, ‘n’),

traverse(Left),
traverse(Right).

New step was to read data from the file… And here I got puzzled being. I played with not(eof(input)) few hours and got nothing. What solved my issue was funny – I looked into folder ‘EXAMPLES’ with searching for ‘file’ string and found my answer, I will describe below, but one note here: Always when you are blocked or feel angry because you cannot resolve that bad error first thing go to examples especially if technology is new for you, otherwise search internet, but do not bump the wall during few hours.

So, when readln(S) fails at EOF we need only another similar clause which do nothing to be ok with our processing. See:

  read_input(Tree):-
                read_input_aux(empty,Tree).

  read_input_aux(Tree, NewTree):-
                readln(Ln),

                !,
                insert(Ln, Tree, Tree1),
                read_input_aux(Tree1, NewTree).

  read_input_aux(Tree, Tree). /* The first clause fails at EOF. */

To easily work with list I wrote following clauses. Would like to mention that Lists in Prolog goes with paradigm of Head and Tail. So having list = integer*, we could write [H|T] for our usings, and H always is one element, T is ending list. To mark emtpy we need [].

  write_list([]).

  write_list([H|T]):-

                write(H,’ ‘),

                write_list(T).

               

  append([],SecondList,SecondList).

  append([H|L1],SecondList,[H|L3]):-

                append(L1,SecondList,L3).

I changed traversing to pass ther List which I want to fill in with elements. To work with files you also need few lines. It is put global file variable, then use openread to open file, use readdevice to redirect standard input.

goal

  openread(input,"input.txt"),
  readdevice(input),
 
  read_input(Result),

  traverse(Result,OrderedList), 
  write_list(OrderedList),
 
  closefile(input).

For input file like "q w e r t y u i o p a …." (new line instead of ‘ ‘) result will be "a b c d e f g …".

 

Task number 2 is: Write Quick Sort agrorithm and apply it to some list. Yes, I know that there are lot of QuickSort examples in internet, but I haven’t used them – I just wrote my own and I think it is somekind unique. Here is code:

domains
  item = integer

  list = item*
predicates
  nondeterm write_list(list)
  nondeterm append(list,list,list)
  nondeterm quicksort(list, list)
  nondeterm partition(list, item, list, list)

clauses
  write_list([]).
  write_list([H|T]):-
                write(H,’ ‘),
                write_list(T).
  append([],SecondList,SecondList).

  append([H|L1],SecondList,[H|L3]):-
                append(L1,SecondList,L3).

partition([], Pivot, Smalls, Bigs).
partition([X|Xs], Pivot, Smalls, Bigs):-
                X < Pivot,

                !,
        partition(Xs, Pivot, Rest, Bigs),
        Smalls=[X|Rest].
partition([X|Xs], Pivot, Smalls, Bigs):-
        partition(Xs, Pivot, Smalls, Rest),

        Bigs=[X|Rest].

quicksort([],[]).
quicksort([X|Xs], SortedList):-
    partition(Xs, X, Smaller, Bigger),
    quicksort(Smaller, SortedSmaller),

    quicksort(Bigger, SortedBigger),
    append(SortedSmaller,[X|SortedBigger],SortedList).
goal
  quicksort([4,5,6,3,9,3,2,87,7,8], SortedList),
  write_list(SortedList).

Next 3rd task looks funny to work with it. It asks me to: Move List in Cycle for given number of elements in given direction.

domains
  item = integer
  list = item*
predicates

  nondeterm getNeededTail(list, integer, list, list)
  nondeterm append(list,list,list)
  nondeterm write_list(list)
  nondeterm listLen(list,integer)
  nondeterm moveList(list, integer, integer, integer, list, list)
clauses

  listLen([],0).
  listLen([H|T],N):-
                listLen(T,TailLen),
                N = TailLen+1.   
  append([],SecondList,SecondList).

  append([H|L1],SecondList,[H|L3]):-
                append(L1,SecondList,L3).
  write_list([]).
  write_list([H|T]):-
                write(H,’ ‘),

                write_list(T).
               
  getNeededTail(InputTail, 0, NeededTail, NeededHead):-
                NeededTail = InputTail.
  getNeededTail([H|T], N, NeededTail, NeededHead):-

                N1 = N-1,
                getNeededTail(T, N1, NeededTail, NotDiscoveredHead),
                NeededHead = [H|NotDiscoveredHead].
               
  moveList(ListToMove,N,Direction,Len,GoodTail, GoodHead):-

                Direction < 0,
                !,
                getNeededTail(ListToMove,N,GoodTail, GoodHead).
  moveList(ListToMove,N,ToRight,Len,GoodTail, GoodHead):-

                N1 = Len – N,
                getNeededTail(ListToMove,N1,GoodTail, GoodHead).

goal
  write("Please enter direction. Number > 0 to right:"),

  readint(Direction),
  write("N:"),
  readint(N),
  ListToMove = [1,2,3,4,5,6,7,8,9,0],

  listLen(ListToMove,Len),
  CorrectedN = N mod Len,
  moveList(ListToMove,CorrectedN,Direction,Len,GoodTail, GoodHead),
  append(GoodTail,GoodHead,MovedList),
  write_list(MovedList).

4th task is quite also easy. Next clauses allow me to have new list formed out of my input by cutting positive values and squaring negative ones:

  modifyList([],[]).
  modifyList([H|T], ResultList):-
                H > 0,
                !,

                modifyList(T,ResultList).
  modifyList([H|T], ResultList):-
                modifyList(T,ModifiedList),
                HSquared = H*H,
                ResultList = [HSquared|ModifiedList].

Do you know what is Expert System? Ok. I do not want to write some real word expert system, but I need at least some to try how it works. So I took example out of examples folder and tried to change it to solve some other problem. Example I took has system which decides what kind of animal is animal you have in mind. It asks few simple questions and saves them to answers database. Then using backtracking if it fails on facts of current checkin’ animal it goes to next animal and asks questions you haven’t answered yet. note here: You should NOT always trust examples which are provided to you, even if they comes with commercial product.Why? Example I found there has one bug: it doesn’t save negative answer for positive question. Understand? Ok… If you have question "Does it fly?" and answer is Yes system saves it to database so will not ask the same question, but if answer is No system will not save opposite and will ask same when will be checking other bird. This is code I’m talking about:

  ask(X,Y,yes):-
                !,

                write(X," it ",Y,’n’),
                readln(Reply),nl,
                frontchar(Reply,’y’,_),
                remember(X,Y,yes).

as you see if it fails on frontchar(Reply,’y’,_) it will save nothing. So this was what I changed to example by introducing new clause rememberYesAnswer, which saves Yes and allow go further or saves No and fail checking of current item.

  ask(X,Y,yes):-
                !,
                write(X," it ",Y,’n’),
                readln(Reply),nl,

                rememberYesAnswer(X,Y,Reply).
  rememberYesAnswer(X,Y,Reply):-
                frontchar(Reply,’y’,_),
                !,
                remember(X,Y,yes).

  rememberYesAnswer(X,Y,Reply):-

                remember(X,Y,no),
                fail.

My expert system might be useful :) for grand master who forgot name of chess figure but know how it looks. It desides what chess figure are you trying to describe, by asking quesitons like "Is figure tall?" or "Is it first row figure?". BTW: Windows 7 has very great game called "Chesss Titans". I played it when writing this:

global facts
  xpositive(symbol,symbol)
  xnegative(symbol,symbol)
predicates
  figure_is(symbol) – nondeterm (o)
  ask(symbol,symbol,symbol) – determ (i,i,i)
  rememberYesAnswer(symbol,symbol,symbol)

  rememberNoAnswer(symbol,symbol,symbol)
  remember(symbol,symbol,symbol) – determ (i,i,i)
  positive(symbol,symbol) – determ (i,i)
  negative(symbol,symbol) – determ (i,i)
  clear_facts – determ ()
  run – determ ()

clauses
  figure_is(king):-
                positive(is,tall),
                positive(is,at_firts_row),
                positive(has,cross_at_the_top),

                positive(does,move_few_cells).

  figure_is(queen):-
                positive(is,tall),
                positive(is,at_firts_row),
                positive(does,move_very_well).

 
  figure_is(rook):-
                negative(is,tall),
                positive(is,at_firts_row),
                positive(does,move_forward_backward_sideway).

  figure_is(bishop):-
                positive(is,tall),
                positive(is,at_firts_row),
                positive(does,move_diagonally_only).

  figure_is(knight):-

                positive(is,at_firts_row),
                positive(is,short),
                positive(does,move_like_letter_L).
               
  figure_is(pawn):-

        negative(is,tall),
                negative(is,at_firts_row),
                positive(does,move_step_forward).     
               
  positive(X,Y):-

                xpositive(X,Y),!.
  positive(X,Y):-
                not(xnegative(X,Y)),
                ask(X,Y,yes).

  negative(X,Y):-

                xnegative(X,Y),!.
  negative(X,Y):-
                not(xpositive(X,Y)),
                ask(X,Y,no).

  ask(X,Y,yes):-

                !,
                write(X," it ",Y,’n’),
                readln(Reply),nl,
                rememberYesAnswer(X,Y,Reply).

  ask(X,Y,no):-
                !,
                write(X," it ",Y,’n’),
                readln(Reply),nl,

                rememberNoAnswer(X,Y,Reply).
               
  rememberYesAnswer(X,Y,Reply):-
                frontchar(Reply,’y’,_),
                !,

                remember(X,Y,yes).
  rememberYesAnswer(X,Y,Reply):-
                remember(X,Y,no),
                fail.
                               

  rememberNoAnswer(X,Y,Reply):-
                frontchar(Reply,’n’,_),
                !,
                remember(X,Y,no).
  rememberNoAnswer(X,Y,Reply):-

                remember(X,Y,yes),
                fail.
               
  remember(X,Y,yes):-
                assertz(xpositive(X,Y)).

  remember(X,Y,no):-
                assertz(xnegative(X,Y)).

  clear_facts:-
                write("nnPlease press the space bar to exitn"),

                retractall(_,dbasedom),
                readchar(_).

  run:-
                figure_is(X),!,
                write("nYour figure may be a (an) ",X),

                nl,
                clear_facts.
  run:-
                write("nUnable to determine what"),

                write("your figure is.nn"),
                clear_facts.

goal
  run.

Also I have few other tasks, but will put them some other time. Thank you for attention.

 


No comments