FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour A thesis: AI brings us back to the Clipper spirit
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM
A thesis: AI brings us back to the Clipper spirit
Posted: Thu Aug 28, 2025 08:51 AM

Hello friends, here’s my experience so far with AI:

A thesis – AI brings us back to the Clipper spirit.

Most training data comes from the 2000s – shaped by classes, methods, and countless helper functions. That’s why AI often suggests this style.

But for short, online-living programs, pragmatism matters more than dogma: keep everything in one function, as simple as possible.

When we use AI, we must state it clearly in the prompt:

  • keep the code as simple as possible

  • keep everything inside one function

  • avoid unnecessary classes or helpers

Because it’s not the AI that decides the style – it’s us, through our questions.

And yes – we might end up eating more Spaghetti (code) again … but this time with pride.

The future belongs to simple code.

What do you think?

Best regards,

Otto

Posts: 563
Joined: Sun Oct 09, 2005 07:23 PM
Re: A thesis: AI brings us back to the Clipper spirit
Posted: Thu Aug 28, 2025 01:48 PM

You're right Otto; AI is based in old data. Sometimes, when you ask it where you can fetch information about webs, it gives you references to webs that don't exist now. My conclusion on this issue is: ask it for small pieces of code; small functions. You can build something bigger but only step by step and supervising every step.

Furthermore, it gives incoherent answers that some call "hallucinations" but which I think that are programmed to always give an answer, rather than acknowledging that they don't have it or that there isn't one. The seller needs to demonstrate that his product has a solution for every problem, even if that is not the actual case.

Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM
Re: A thesis: AI brings us back to the Clipper spirit
Posted: Thu Aug 28, 2025 01:50 PM
Below are two equivalent examples (same behavior), written in plain xBase “handbook style”.
They check: Does the room exist? → Any collision in occupancy? → return free / occupied.


Takeaways

Variant A reads exactly like the old manuals: SELECT, SEEK, FOUND(), DO WHILE !EOF().

Variant B is the same logic with explicit alias scoping. It avoids global work-area switches and can help in more modular/mixed code.

Both are one function, zero helpers—so an AI can generate/adjust them easily if we ask for it.

What do you think?


Here’s a copy-paste prompt add-on to keep the output in plain handbook style (Clipper/Harbour):
STYLE CONSTRAINTS — HANDBOOK STYLE (Clipper/Harbour, DBF/CDX)

• Write classic xBase code that any Clipper user can read at a glance.
• Keep everything in ONE function. No classes, helpers, wrappers, or frameworks.
• Use SELECT to switch work areas; save/restore previous area with nWA := Select() / SELECT(nWA).
• Use SEEK followed by FOUND() (not if dbSeek(...)). Show SET ORDER TO and SET SOFTSEEK ON/OFF explicitly.
• Iterate with dbGoTop(), DO WHILE !Eof(), dbSkip(). Keep control flow linear and explicit.
• Field access: in current area use bare field names; only use Alias->Field when reading a non-current area.
• Locking: RLock()/UNLOCK around REPLACE. No exception frameworks or BEGIN SEQUENCE blocks.
• Open/close explicitly: USE ... NEW ALIAS ..., and USE before every RETURN; restore SELECT(nWA).
• Avoid alias-scoping syntax like ADR->( dbSeek(...) ) or (cAlias)->(...). Prefer the manual’s SELECT style.
• No macros / #xtranslate “sugar”. No lambdas/closures. No OOP.
• Minimal comments; English, short. Prefer clarity over cleverness.
• Output ONLY the code block—no extra explanations or prose.

If you want the alias-scoped variant instead, switch just these two lines:
• Prefer alias scoping for operations: ADR->( dbSetOrder(...) ), ADR->( dbSeek(...) ), etc.
• Avoid changing the current work area with SELECT; keep operations scoped to the alias.


Variant A — Classic Clipper (SELECT + SEEK + FOUND)
FUNCTION CheckFree_Clipper( cZiNr, dFrom, dTo, nBuch )
   LOCAL cAdr := "x:\DATAWIN\adr.dbf"
   LOCAL cBeg := "x:\DATAWIN\belegung.dbf"
   LOCAL nWA  := Select()

   // Room exists?
   USE ( cAdr ) NEW ALIAS ADR
   IF ! Used( "ADR" )
      SELECT ( nWA ) ; RETURN .F.
   ENDIF
   SELECT ADR
   SET ORDER TO TAG a_zinr
   SET SOFTSEEK OFF
   SEEK cZiNr
   IF ! FOUND()
      USE
      SELECT ( nWA ) ; RETURN .F.
   ENDIF
   USE

   // Occupancy check
   USE ( cBeg ) NEW ALIAS BEG SHARED
   IF ! Used( "BEG" )
      SELECT ( nWA ) ; RETURN .F.
   ENDIF
   SELECT BEG
   SET ORDER TO TAG b_abreise
   ordScope( 0, PADR( ALLTRIM( cZiNr ), 5 ) )
   ordScope( 1, PADR( ALLTRIM( cZiNr ), 5 ) )
   dbGoTop()

   DO WHILE ! Eof()
      IF ( BEG->anreise < dTo ) .AND. ( BEG->abreise > dFrom ) .AND. ;
         ( nBuch == 0 .OR. BEG->buchungsnr <> nBuch )
         USE
         SELECT ( nWA ) ; RETURN .F.   // occupied
      ENDIF
      dbSkip()
   ENDDO

   USE
   SELECT ( nWA ) ; RETURN .T.         // free
Variant B — Explicit alias scoping (ADR->() / BEG->())
FUNCTION CheckFree_Alias( cZiNr, dFrom, dTo, nBuch )
   LOCAL cAdr := "x:\DATAWIN\adr.dbf"
   LOCAL cBeg := "x:\DATAWIN\belegung.dbf"

   // Room exists?
   USE ( cAdr ) NEW ALIAS ADR
   IF ! Used( "ADR" )
      RETURN .F.
   ENDIF
   ADR->( dbSetOrder( "a_zinr" ) )
   SET SOFTSEEK OFF
   ADR->( dbSeek( cZiNr ) )
   IF ! ADR->( Found() )
      ADR->( dbCloseArea() )
      RETURN .F.
   ENDIF
   ADR->( dbCloseArea() )

   // Occupancy check
   USE ( cBeg ) NEW ALIAS BEG SHARED
   IF ! Used( "BEG" )
      RETURN .F.
   ENDIF
   BEG->( dbSetOrder( "b_abreise" ) )
   BEG->( ordScope( 0, PADR( ALLTRIM( cZiNr ), 5 ) ) )
   BEG->( ordScope( 1, PADR( ALLTRIM( cZiNr ), 5 ) ) )
   BEG->( dbGoTop() )

   DO WHILE ! BEG->( Eof() )
      IF ( BEG->anreise < dTo ) .AND. ( BEG->abreise > dFrom ) .AND. ;
         ( nBuch == 0 .OR. BEG->buchungsnr <> nBuch )
         BEG->( dbCloseArea() )
         RETURN .F.   // occupied
      ENDIF
      BEG->( dbSkip() )
   ENDDO

   BEG->( dbCloseArea() )
   RETURN .T.         // free
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM
Re: A thesis: AI brings us back to the Clipper spirit
Posted: Thu Aug 28, 2025 01:59 PM
Here’s a compact OOP variant in Harbour/xBase style (single class, no helpers beyond the class). It mirrors the earlier logic: room exists? → conflict check → return a small result hash. Includes a tiny demo at the end.

One class, simple methods, explicit open/close per alias.
Returns small hashes (success/code/message) so you can hb_jsonEncode() when needed.
Swap paths/aliases to your environment; date parsing depends on your SET DATE/SET EPOCH.




/* OOP Demo — Room availability check */

CLASS TRoomAvailability
   DATA cAdrPath
   DATA cBegPath
   DATA cAdrAlias
   DATA cBegAlias

   METHOD New( cAdr, cBeg, cAdrAlias, cBegAlias ) CONSTRUCTOR
   METHOD CheckFree( cZiNr, dFrom, dTo, nBuch )
   METHOD ExistsRoom( cZiNr )
   METHOD FindConflicts( cZiNr, dFrom, dTo, nBuch )
   METHOD Open( cPath, cAlias, lShared )
   METHOD Close( cAlias )
ENDCLASS

METHOD New( cAdr, cBeg, cAdrAlias, cBegAlias ) CLASS TRoomAvailability
   ::cAdrPath  := IIF( HB_ISCHAR( cAdr ), cAdr, "" )
   ::cBegPath  := IIF( HB_ISCHAR( cBeg ), cBeg, "" )
   ::cAdrAlias := IIF( HB_ISCHAR( cAdrAlias ) .AND. ! Empty( cAdrAlias ), cAdrAlias, "ADR" )
   ::cBegAlias := IIF( HB_ISCHAR( cBegAlias ) .AND. ! Empty( cBegAlias ), cBegAlias, "BEG" )
RETURN Self

METHOD Open( cPath, cAlias, lShared ) CLASS TRoomAvailability
   LOCAL nSel
   IF Empty( cPath ) .OR. Empty( cAlias )
      RETURN .F.
   ENDIF
   nSel := Select( cAlias )
   IF nSel > 0
      ( cAlias )->( dbCloseArea() )
   ENDIF
   IF lShared == .T.
      USE ( cPath ) NEW ALIAS ( cAlias ) SHARED
   ELSE
      USE ( cPath ) NEW ALIAS ( cAlias )
   ENDIF
RETURN ! NetErr()

METHOD Close( cAlias ) CLASS TRoomAvailability
   IF ! Empty( cAlias ) .AND. Select( cAlias ) > 0
      ( cAlias )->( dbCloseArea() )
   ENDIF
RETURN NIL

METHOD ExistsRoom( cZiNr ) CLASS TRoomAvailability
   IF ! ::Open( ::cAdrPath, ::cAdrAlias, .F. )
      RETURN {=> "success"=>.F., "code"=>"DB_OPEN_FAILED", "message"=>"ADR open failed" }
   ENDIF
   ( ::cAdrAlias )->( dbSetOrder( "a_zinr" ) )
   SET SOFTSEEK OFF
   ( ::cAdrAlias )->( dbSeek( cZiNr ) )
   IF ( ::cAdrAlias )->( Found() )
      ::Close( ::cAdrAlias )
      RETURN {=> "success"=>.T. }
   ENDIF
   ::Close( ::cAdrAlias )
RETURN {=> "success"=>.F., "code"=>"INVALID_ROOM", "message"=>"Invalid room number" }

METHOD FindConflicts( cZiNr, dFrom, dTo, nBuch ) CLASS TRoomAvailability
   LOCAL cMsg
   IF ! ::Open( ::cBegPath, ::cBegAlias, .T. )
      RETURN {=> "success"=>.F., "code"=>"DB_OPEN_FAILED", "message"=>"BEG open failed" }
   ENDIF

   ( ::cBegAlias )->( dbSetOrder( "b_abreise" ) )
   ( ::cBegAlias )->( ordScope( 0, PADR( ALLTRIM( cZiNr ), 5 ) ) )
   ( ::cBegAlias )->( ordScope( 1, PADR( ALLTRIM( cZiNr ), 5 ) ) )
   ( ::cBegAlias )->( dbGoTop() )

   DO WHILE ! ( ::cBegAlias )->( Eof() )
      IF ( ( ::cBegAlias )->anreise < dTo ) .AND. ( ( ::cBegAlias )->abreise > dFrom ) .AND. ;
         ( nBuch == 0 .OR. ( ::cBegAlias )->buchungsnr <> nBuch )

         cMsg := "Entered " + DToC( dFrom ) + " to " + DToC( dTo ) + ;
                 " conflicts with " + DToC( ( ::cBegAlias )->anreise ) + " to " + DToC( ( ::cBegAlias )->abreise ) + ;
                 " (" + ( ::cBegAlias )->planname + ")"
         ::Close( ::cBegAlias )
         RETURN {=> "success"=>.F., "code"=>"OCCUPIED", "message"=>cMsg }
      ENDIF
      ( ::cBegAlias )->( dbSkip() )
   ENDDO

   ::Close( ::cBegAlias )
RETURN {=> "success"=>.T. }

METHOD CheckFree( cZiNr, dFrom, dTo, nBuch ) CLASS TRoomAvailability
   LOCAL r

   IF Empty( cZiNr ) .OR. Empty( dFrom ) .OR. Empty( dTo )
      RETURN {=> "success"=>.F., "code"=>"INVALID_INPUT", "message"=>"Room, arrival and departure are required" }
   ENDIF

   r := ::ExistsRoom( cZiNr )
   IF ! r[ "success" ]
      RETURN r
   ENDIF

   r := ::FindConflicts( cZiNr, dFrom, dTo, nBuch )
   IF ! r[ "success" ]
      RETURN r
   ENDIF

RETURN {=> "success"=>.T., "code"=>"OK", "message"=>"Room is free", ;
         "data"=>{=> "zi_nr"=>cZiNr, "from"=>DToC( dFrom ), "to"=>DToC( dTo ) } }

/* --- Tiny demo --- */
FUNCTION Demo_OOP()
   LOCAL o := TRoomAvailability():New( ;
      "x:\xwhdaten\DATAWIN\adr.dbf", ;
      "x:\xwhdaten\DATAWIN\belegung.dbf" )
   LOCAL h := o:CheckFree( "0101", CToD( "07.08.2025" ), CToD( "15.08.2025" ), 0 )
   ? hb_jsonEncode( h )
RETURN NIL
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM
Re: A thesis: AI brings us back to the Clipper spirit
Posted: Thu Aug 28, 2025 02:09 PM
Once more for comparison: CheckFree as a class or as a function.
You get exactly the same call convenience as in Demo_OOP—just in plain handbook style. Simply link the standalone function into your project and you’re done.
FUNCTION Demo_Functional()
   LOCAL h := CheckFree_Manual( ;
      "x:\xwhdaten\DATAWIN\adr.dbf", ;
      "x:\xwhdaten\DATAWIN\belegung.dbf", ;
      "0101", CToD( "07.08.2025" ), CToD( "15.08.2025" ), 0 )
   ? hb_jsonEncode( h )
RETURN NIL
OR OOP
UNCTION Demo_OOP()
   LOCAL o := TRoomAvailability():New( ;
      "x:\xwhdaten\DATAWIN\adr.dbf", ;
      "x:\xwhdaten\DATAWIN\belegung.dbf" )
   LOCAL h := o:CheckFree( "0101", CToD( "07.08.2025" ), CToD( "15.08.2025" ), 0 )
   ? hb_jsonEncode( h )
RETURN NIL
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM
Re: A thesis: AI brings us back to the Clipper spirit
Posted: Thu Aug 28, 2025 02:12 PM
Function-first is clearer—and it gives you real advantages for dev, maintenance, and AI-assisted changes.

Why the single FUNCTION wins
Fast to read for any xBase/Clipper dev (handbook style).
Fewer moving parts → fewer hidden states/side effects.
Smaller diff surface in reviews.
Easy to steer LLMs: “one function, no helpers, no classes”.
Simple to test: call the function, inspect the tiny hash result.
Drop-in reuse across routes/microservices.

Mini style stamp (put at top of your .prg)
/* STYLE: Handbook function, no classes/helpers.
   - One public FUNCTION per file
   - USE ... NEW ALIAS ... → SELECT ... → SET ORDER/SET SOFTSEEK
   - SEEK key, then IF FOUND()
   - dbGoTop() / DO WHILE !Eof() / dbSkip()
   - Always USE (close) before every RETURN
   - Return tiny hash: {=> "success", "code", "message", "data" }
*/

LLM prompt tail (to keep this style)

Write classic xBase in handbook style:
- single FUNCTION, no classes/helpers
- use SELECT to switch work areas; save/restore with nWA := Select() / SELECT(nWA)
- SEEK key then check FOUND(); show SET ORDER and SET SOFTSEEK explicitly
- dbGoTop(), DO WHILE !Eof(), dbSkip()
- close files (USE) before every RETURN
- return a tiny hash { success, code, message, data }
Output code only.

Continue the discussion