caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
From: Berke Durak <berke.durak@exalead.com>
To: Caml-list List <caml-list@inria.fr>
Subject: Objects, dynamic cast, Obj.magic abuse and dragons
Date: Tue, 26 Feb 2008 12:35:10 +0100	[thread overview]
Message-ID: <47C3F96E.4080901@exalead.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 1727 bytes --]

Hello,

It seems that objects are back in fashion again on the list.

As records of functions or functors dont quite cut the mustard for some applications, I decided to give Ocaml's object 
system another try.  I tried to implement a conventional text adventure using those, of the "go north take sword kill 
dragon" kind.  Actually I had one such small prototype I wrote while learning Java, so I decided to port that one.

I've stumbled on a situation where I feel dynamic casting could be useful.  At least, that is the way I did it in the 
Java version.  I've worked around it using Obj.magic, but I can't check the class, so this could lead to nastiness.
I'd rather eat a runtime Class_cast_exception than a segmentation fault.

In this adventure, things can contain other things; a class physical has a list of things it contains, and an optional 
pointer to its container.

Persons, places and objects are things so they inherit from physical.  Hence, a forest is a place that can contain
a sword (an object), a dragon (a person) or another place (a small house).  Persons can be contained in places
or things (coffins).

The problem is that the main game loop gets the current location by taking the container of the hero... which is a 
physical.  However, it needs to call the place-specific method "go".

I'm submitting my example so that you can propose alternative solutions.  A few ideas:

* Add a go method in physical, raise an exception - not scalable, if I want to add other categories of things,
I'll have to add the corresponding method to physical.
* Parametrize physical with the type of contents.
* Use a sum type; but then it wouldn't be an object any more, and it's a centralized place.
-- 
Berke DURAK

[-- Attachment #2: adventure.ml --]
[-- Type: text/plain, Size: 5102 bytes --]

(* Adventure *)

exception QuitException

let mandatory = function
  | None -> raise Not_found
  | Some x -> x

let pf = Printf.printf

let split_at c u =
  let m = String.length u in
  let b = Buffer.create m in
  let rec loop0 r i =
    if i >= m then
      List.rev r
    else
      if u.[i] = c then
        loop0 r (i + 1)
      else
        loop1 r i
  and loop1 r i =
    if i = m or u.[i] = c then
      begin
        let x = Buffer.contents b in
        Buffer.clear b;
        loop0 (x::r) (i + 1)
      end
    else
      begin
        Buffer.add_char b u.[i];
        loop1 r (i + 1)
      end
  in
  loop0 [] 0

let ( & ) f x = f x

class ['subject] io in_channel () =
  object
    val mutable subject : 'subject option = None
    method set_subject s = subject <- Some s
    method get_subject = mandatory subject
    
    method read =
      pf "< %!";
      split_at ' ' & input_line in_channel

    method write u =
      pf ">>> %s\n%!" u
  end

class virtual command =
  object
    method virtual get_verb : string
    method virtual perform : string list -> unit
  end

class virtual describable =
  object
    method virtual describe : 'a . 'a io -> unit
    method virtual get_name : string
  end

class virtual physical =
  object(self)
    inherit describable as super
    val mass = 1.0
    val takeable = true
    val mutable contents : physical list = []
    val mutable container : physical option = None
    method contents = contents
    method container = mandatory container

    method add : physical -> unit = fun thing -> contents <- thing :: contents

    method remove (thing : physical) = contents <- List.filter ((<>) thing) contents

    method unlink =
      match container with
      | None -> ()
      | Some t -> t#remove (self :> physical)

    method put (target : physical) =
      self#unlink;
      container <- Some target;
      target#add (self :> physical)
      
  end

class biscuit =
  object
    inherit physical as super
    method describe io =
      io#write "A square biscuit with chocolate in it.  It is organic, or at least that's what the writing on it, presumably in edible, organic ink, says.";
    method get_name = "a chocolate biscuit"
    method to_string = "the biscuit"
  end

type direction = N | S | E | W

let invert = function
| N -> S
| S -> N
| E -> W
| W -> E

let connect p1 d p2 =
  p1#connect d p2;
  p2#connect (invert d) p1

class virtual place =
  object(self)
    inherit physical as super
    val mutable seen = false
    val mutable outlinks : (direction * place) list = []

    method go d = List.assoc d outlinks

    method connect d t = outlinks <- (d, t) :: outlinks

    method describe_items : 'a . 'a io -> unit = fun io ->
      io#write "You can see:";
      List.iter
        (fun p ->
          let (q : physical) = p in
          q#describe io
        )
        contents

    method virtual describe_place : 'a . 'a io -> unit

    method describe io =
      self#describe_place io;
      self#describe_items io
  end

class virtual person =
  object
    inherit physical as super
    val takeable = false
  end

class hero =
  object
    inherit person as super
    method get_name = "John"
    method describe io = io#write "John is a tall man."
  end

let sf = Printf.sprintf

class forest name () =
  object(self)
    inherit place as super
    method get_name = name
    method describe_place io =
      io#write (sf "You are in %s" self#get_name)
  end

class sword =
  object(self)
    inherit physical as super

    method describe io =
      io#write "A one-meter long, titanium alloy, gold-plated, emerald-incrusted, Dragon-repelling adventurer's heavy duty sword."

    method get_name = "A golden sword"
  end

class game io =
  let f1 = new forest "the big dark forest" () in
  let f2 = new forest "the small dark forest" () in
  let f3 = new forest "the small light forest" () in
  let f4 = new forest "the tropical forest" () in
  let h  = new hero in
  let _ = h#put (f1 :> physical) in
  let _ = connect f1 N f2 in
  let _ = connect f2 E f3 in
  let _ = connect f3 S f4 in
  let _ = connect f4 E f1 in
  let _ = io#set_subject h in
  let sw = new sword in
  let _ = sw#put (f1 :> physical) in
  let b = new biscuit in
  let _ = b#put (f4 :> physical) in
  object(self)
    method where =
      let wh' = h#container in
      (* XXXXXXXX here *)
      let wh : place = Obj.magic wh' in (* I'd like a dynamic cast here! *)
      (* XXXXXXXX *)
      (*let wh' = (h#container : physical <: place) in*)
      wh

    method go d =
      try
        let wh = self#where in
        let wh = wh#go d in
        h#put (wh : place :> physical)
      with
      | Not_found ->
          io#write "Can't go there"
      
    method run =
      let wh = self#where in
      wh#describe io;
      begin
        match io#read with
        | ["n"] -> self#go N
        | ["s"] -> self#go S
        | ["e"] -> self#go E
        | ["w"] -> self#go W
        | _ -> io#write "Wtf?"
      end
  end

let play () =
  let io = new io stdin () in
  let g = new game io in
  while true do
    g#run
  done

             reply	other threads:[~2008-02-26 11:35 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-02-26 11:35 Berke Durak [this message]
2008-02-26 12:14 ` [Caml-list] " Richard Jones
2008-02-26 14:28   ` Berke Durak
2008-02-26 14:48     ` Richard Jones
2008-02-26 14:59       ` Berke Durak
2008-02-27 13:26   ` Tiphaine.Turpin
2008-02-29 10:36     ` Berke Durak
2008-02-29 12:23       ` Tiphaine.Turpin
2008-02-26 12:48 ` ketti
2008-02-26 13:10 ` Berke Durak
2008-02-26 15:07 ` Dirk Thierbach
2008-02-26 16:25   ` Berke Durak
2008-02-27  7:37     ` Dirk Thierbach
2008-02-27 10:26       ` Berke Durak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=47C3F96E.4080901@exalead.com \
    --to=berke.durak@exalead.com \
    --cc=caml-list@inria.fr \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).